esbuild
Migration guide from esbuild to Bun's bundler
Bun's bundler API is heavily inspired by esbuild. This page is a side-by-side comparison of the two APIs.
A few behaviors differ:
Performance
Bun's bundler is 1.75x faster than esbuild on esbuild's three.js benchmark.
CLI API
# esbuild
esbuild <entrypoint> --outdir=out --bundle
# bun
bun build <entrypoint> --outdir=out
In Bun's CLI, boolean flags like --minify take no argument. Flags that take one, like --outdir <path>, can be written as --outdir out or --outdir=out. Some flags, like --define, can be repeated: --define foo=bar --define bar=baz.
| esbuild | bun build | Notes |
|---|---|---|
--bundle | n/a | Bun always bundles; use --no-bundle to disable it. |
--define:K=V | --define K=V | Small syntax difference; no colon.esbuild --define:foo=barbun build --define foo=bar |
--external:<pkg> | --external <pkg> | Small syntax difference; no colon.esbuild --external:reactbun build --external react |
--format | --format | Bun supports "esm", "cjs", and "iife". esbuild defaults to "iife". |
--loader:.ext=loader | --loader .ext:loader | Bun supports a different set of built-in loaders than esbuild; see loaders. The esbuild loaders dataurl, binary, base64, copy, and empty are not implemented.The syntax for --loader differs.esbuild app.ts --bundle --loader:.svg=textbun build app.ts --loader .svg:text |
--minify | --minify | No differences |
--outdir | --outdir | No differences |
--outfile | --outfile | No differences |
--packages | --packages | No differences |
--platform | --target | Renamed to --target for consistency with tsconfig. Does not support neutral. |
--serve | n/a | Not applicable |
--sourcemap | --sourcemap | Supports linked (the default when no value is given), external, inline, and none. Does not support esbuild's both. |
--splitting | --splitting | No differences |
--target | n/a | Not supported. Bun's bundler performs no syntactic down-leveling. |
--watch | --watch | No differences |
--allow-overwrite | n/a | Bun never allows overwriting |
--analyze | n/a | Not supported |
--asset-names | --asset-naming | Renamed for consistency with naming in JS API |
--banner | --banner | Only applies to js bundles |
--footer | --footer | Only applies to js bundles |
--certfile | n/a | Not applicable |
--charset=utf8 | n/a | Not supported |
--chunk-names | --chunk-naming | Renamed for consistency with naming in JS API |
--color | n/a | Always enabled |
--drop | --drop | |
| n/a | --feature | Bun-specific. Enables feature flags for compile-time dead-code elimination through import { feature } from "bun:bundle" |
--entry-names | --entry-naming | Renamed for consistency with naming in JS API |
--global-name | n/a | Not supported |
--ignore-annotations | --ignore-dce-annotations | |
--inject | n/a | Not supported |
--jsx | --jsx-runtime <runtime> | Supports "automatic" (uses jsx transform) and "classic" (uses React.createElement) |
--jsx-dev | n/a | Bun reads compilerOptions.jsx from tsconfig.json to determine a default. If compilerOptions.jsx is "react-jsx", or if NODE_ENV=production, Bun uses the jsx transform. Otherwise, it uses jsxDEV. The bundler does not support preserve. |
--jsx-factory | --jsx-factory | |
--jsx-fragment | --jsx-fragment | |
--jsx-import-source | --jsx-import-source | |
--jsx-side-effects | --jsx-side-effects | |
--keep-names | --keep-names | |
--keyfile | n/a | Not applicable |
--legal-comments | n/a | Not supported |
--log-level | n/a | Not supported. You can set the log level in bunfig.toml as logLevel. |
--log-limit | n/a | Not supported |
--log-override:X=Y | n/a | Not supported |
--main-fields | n/a | Not supported |
--mangle-cache | n/a | Not supported |
--mangle-props | n/a | Not supported |
--mangle-quoted | n/a | Not supported |
--metafile | --metafile | |
--minify-whitespace | --minify-whitespace | |
--minify-identifiers | --minify-identifiers | |
--minify-syntax | --minify-syntax | |
--out-extension | n/a | Not supported |
--outbase | --root | |
--preserve-symlinks | n/a | Not supported |
--public-path | --public-path | |
--pure | n/a | Not supported |
--reserve-props | n/a | Not supported |
--resolve-extensions | n/a | Not supported |
--servedir | n/a | Not applicable |
--source-root | n/a | Not supported |
--sourcefile | n/a | Not supported. Bun does not support stdin input. |
--sources-content | n/a | Not supported |
--supported | n/a | Not supported |
--tree-shaking | n/a | Always true. Bun also tree-shakes the exports of import() / require() targets down to the names the importing code reads (see splitting); esbuild keeps every export of a dynamically imported module. |
--tsconfig | --tsconfig-override | |
--version | n/a | Run bun --version to see the version of Bun. |
JavaScript API
| esbuild.build() | Bun.build() | Notes |
|---|---|---|
absWorkingDir | n/a | Always set to process.cwd() |
alias | n/a | Not supported |
allowOverwrite | n/a | Always false |
assetNames | naming.asset | Uses the same templating syntax as esbuild, but you must include [ext] explicitly.ts<br/>Bun.build({<br/> entrypoints: ["./index.tsx"],<br/> naming: {<br/> asset: "[name].[ext]",<br/> },<br/>});<br/> |
banner | banner | Only applies to js bundles |
bundle | n/a | Always true. Use Bun.Transpiler to transpile without bundling. |
charset | n/a | Not supported |
chunkNames | naming.chunk | Uses the same templating syntax as esbuild, but you must include [ext] explicitly.ts<br/>Bun.build({<br/> entrypoints: ["./index.tsx"],<br/> naming: {<br/> chunk: "[name].[ext]",<br/> },<br/>});<br/> |
color | n/a | Bun returns logs in the logs property of the build result. |
conditions | conditions | No differences |
define | define | |
drop | drop | |
entryNames | naming or naming.entry | Bun supports a naming key that can either be a string or an object. Uses the same templating syntax as esbuild, but you must include [ext] explicitly.ts<br/>Bun.build({<br/> entrypoints: ["./index.tsx"],<br/> // when string, this is equivalent to entryNames<br/> naming: "[name].[ext]",<br/><br/> // granular naming options<br/> naming: {<br/> entry: "[name].[ext]",<br/> asset: "[name].[ext]",<br/> chunk: "[name].[ext]",<br/> },<br/>});<br/> |
entryPoints | entrypoints | Capitalization difference |
external | external | No differences |
footer | footer | Only applies to js bundles |
format | format | Supports "esm", "cjs", and "iife". |
globalName | n/a | Not supported |
ignoreAnnotations | ignoreDCEAnnotations | |
inject | n/a | Not supported |
jsx | jsx.runtime | Supports "automatic" and "classic" |
jsxDev | jsx.development | |
jsxFactory | jsx.factory | |
jsxFragment | jsx.fragment | |
jsxImportSource | jsx.importSource | |
jsxSideEffects | jsx.sideEffects | |
keepNames | minify.keepNames | |
legalComments | n/a | Not supported |
loader | loader | Bun supports a different set of built-in loaders than esbuild; see loaders. Bun does not implement the esbuild loaders dataurl, binary, base64, copy, and empty. |
logLevel | n/a | Not supported |
logLimit | n/a | Not supported |
logOverride | n/a | Not supported |
mainFields | n/a | Not supported |
mangleCache | n/a | Not supported |
mangleProps | n/a | Not supported |
mangleQuoted | n/a | Not supported |
metafile | metafile | |
minify | minify | In Bun, minify can be a boolean or an object.ts<br/>await Bun.build({<br/> entrypoints: ['./index.tsx'],<br/> // enable all minification<br/> minify: true<br/><br/> // granular options<br/> minify: {<br/> identifiers: true,<br/> syntax: true,<br/> whitespace: true<br/> }<br/>})<br/> |
minifyIdentifiers | minify.identifiers | See minify |
minifySyntax | minify.syntax | See minify |
minifyWhitespace | minify.whitespace | See minify |
nodePaths | n/a | Not supported |
outExtension | n/a | Not supported |
outbase | root | Different name |
outdir | outdir | No differences |
outfile | outfile | No differences |
packages | packages | No differences |
platform | target | Supports "bun", "node" and "browser" (the default). Does not support "neutral". |
plugins | plugins | Bun's plugin API is a subset of esbuild's. Some esbuild plugins work with Bun without modification. |
preserveSymlinks | n/a | Not supported |
publicPath | publicPath | No differences |
pure | n/a | Not supported |
reserveProps | n/a | Not supported |
resolveExtensions | n/a | Not supported |
sourceRoot | n/a | Not supported |
sourcemap | sourcemap | Supports "none", "linked", "inline", and "external" |
sourcesContent | n/a | Not supported |
splitting | splitting | No differences |
stdin | n/a | Not supported |
supported | n/a | Not supported |
target | n/a | No support for syntax downleveling |
treeShaking | treeShaking | Defaults to true |
tsconfig | tsconfig | |
write | n/a | Set to true if outdir/outfile is set, otherwise false |
Plugin API
Bun's plugin API is designed to be esbuild-compatible. Bun doesn't support esbuild's entire plugin API surface, but it implements the core functionality. Many third-party esbuild plugins work with Bun without modification.
In both Bun and esbuild, you define plugins with a builder object.
import type { BunPlugin } from "bun";
const myPlugin: BunPlugin = {
name: "my-plugin",
setup(builder) {
// define plugin
},
};
The builder object's methods hook into parts of the bundling process. Bun implements onStart, onEnd, onResolve, and onLoad; it does not implement the esbuild hooks onDispose and resolve. Bun partially implements initialOptions: the object is read-only and exposes only a subset of esbuild's options. Use config (the same thing in Bun's BuildConfig format) instead.
import type { BunPlugin } from "bun";
const myPlugin: BunPlugin = {
name: "my-plugin",
setup(builder) {
builder.onStart(() => {
/* called when the bundle starts */
});
builder.onResolve({/* onResolve.options */}, args => {
return {/* onResolve.results */};
});
builder.onLoad({/* onLoad.options */}, args => {
return {/* onLoad.results */};
});
builder.onEnd(result => {
/* called when the bundle is complete */
});
},
};
onResolve
- 🟢
filter - 🟢
namespace
- 🟢
path - 🟢
importer - 🟢
namespace - 🟢
resolveDir - 🟢
kind - 🔴
pluginData
- 🟢
namespace - 🟢
path - 🔴
errors - 🟢
external - 🔴
pluginData - 🔴
pluginName - 🔴
sideEffects - 🔴
suffix - 🔴
warnings - 🔴
watchDirs - 🔴
watchFiles
onLoad
- 🟢
filter - 🟢
namespace
- 🟢
path - 🟢
namespace - 🔴
suffix - 🔴
pluginData
- 🟢
contents - 🟢
loader - 🔴
errors - 🔴
pluginData - 🔴
pluginName - 🔴
resolveDir - 🔴
warnings - 🔴
watchDirs - 🔴
watchFiles