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:
Bundling by default. Unlike esbuild, Bun bundles by default; no --bundle flag is needed. To transpile each file
individually, use Bun.Transpiler.
Bundler only. Unlike esbuild, Bun's bundler has no built-in development server. Use it with Bun.serve and other
runtime APIs to get the same effect. esbuild's HTTP options don't apply.
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=outIn 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 |
--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.
Long term, we aim for feature parity with esbuild's API. If something doesn't work, file an issue to help us prioritize.
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