Bundling by default. Unlike esbuild, Bun always bundles by default. That’s why the
--bundle flag doesn’t appear in Bun examples. To transpile each file individually, use Bun.Transpiler.It’s just a bundler. Unlike esbuild, Bun’s bundler does not include a built-in dev server or file watcher. It’s just a bundler. The bundler is designed to be used with
Bun.serve and other runtime APIs to achieve the same effect. As such, all HTTP/file-watching related options are not applicable.Performance
With a performance-first API and heavily optimized Zig-based JS/TS parser, Bun’s bundler is 1.75x faster than esbuild on esbuild’s three.js benchmark.Bundle 10 copies of three.js from scratch with source maps and minification
Why migrate?
Here are some reasons you might want to migrate from esbuild to Bun’s bundler:Speed
Bun’s bundler is significantly faster than esbuild, especially for large projects. The Zig-based implementation and careful optimizations make builds complete faster.Native features
Bun includes native support for:- TypeScript and JSX: No configuration needed
- CSS bundling: Built-in CSS processing with modern features
- HTML processing: Bundle complete web applications
- Macros: Run code at build time
- Framework features: React Fast Refresh, server components
Unified toolchain
Bun provides a complete JavaScript runtime and toolkit:- Bundler
- Runtime
- Test runner
- Package manager
Modern defaults
Bun uses modern defaults:- ESM by default
- Modern JavaScript syntax
- Fast native loaders for common file types
CLI API
Both Bun and esbuild provide command-line interfaces.--minify don’t accept arguments. Other flags with arguments like --outdir <path> accept arguments; these flags can be written as --outdir out or --outdir=out. Some flags like --define can be specified multiple times: --define foo=bar --define bar=baz.
| esbuild | bun build | Notes |
|---|---|---|
--bundle | N/A | Bun always bundles; use --no-bundle to disable. |
--define:K=V | --define K=V | Minor syntax difference; no colon.esbuild --define:foo=barbun build --define foo=bar |
--external:<pkg> | --external <pkg> | Minor syntax difference; no colon.esbuild --external:reactbun build --external react |
--format | --format | Bun currently supports "esm" and "cjs", with more module formats planned. esbuild defaults to "iife". |
--loader:.ext=loader | --loader .ext:loader | Bun’s set of built-in loaders differs from esbuild’s; see Bundler > Loaders for complete reference. esbuild’s dataurl, binary, base64, copy, and empty loaders are not yet implemented.The --loader syntax is slightly different.esbuild app.ts --bundle --loader:.svg=textbun build app.ts --loader .svg:text |
--minify | --minify | No difference |
--outdir | --outdir | No difference |
--outfile | --outfile | No difference |
--packages | --packages | No difference |
--platform | --target | Renamed to --target for consistency with tsconfig. Does not support neutral. |
--serve | N/A | Not applicable |
--sourcemap | --sourcemap | No difference |
--splitting | --splitting | No difference |
--target | N/A | Not supported. Bun’s bundler currently doesn’t support syntax downleveling. |
--watch | --watch | No difference |
Additional flags
| esbuild | bun build | Notes |
|---|---|---|
--allow-overwrite | N/A | Not allowed |
--analyze | N/A | Not supported |
--asset-names | --asset-naming | Renamed for consistency with JS API |
--banner | --banner | Only applies to js bundles |
--footer | --footer | Only applies to js bundles |
--charset=utf8 | N/A | Not supported |
--chunk-names | --chunk-naming | Renamed for consistency with JS API |
--drop | --drop | |
| N/A | --feature | Bun-specific. Feature flags for compile-time dead code elimination via import { feature } from "bun:bundle" |
--entry-names | --entry-naming | Renamed for consistency with JS API |
--global-name | N/A | Not applicable; Bun doesn’t currently support iife output |
--ignore-annotations | --ignore-dce-annotations | |
--inject | N/A | Not supported |
--jsx | --jsx-runtime <runtime> | Supports "automatic" (jsx transform) and "classic" (React.createElement) |
--jsx-dev | N/A | Bun reads defaults from tsconfig.json’s compilerOptions.jsx. If the value is "react-jsx" or the environment variable 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 | N/A | JSX is always assumed to be side-effect free |
--keep-names | N/A | Not supported |
--legal-comments | N/A | Not supported |
--log-level | N/A | Not supported; can be set via bunfig.toml using logLevel |
--main-fields | N/A | Not supported |
--metafile | --metafile | Bun supports both JSON and Markdown metafiles |
--minify-whitespace | --minify-whitespace | |
--minify-identifiers | --minify-identifiers | |
--minify-syntax | --minify-syntax | |
--out-extension | N/A | Not supported |
--outbase | --root | |
--public-path | --public-path | |
--sourcemap | --sourcemap | No difference |
--tsconfig | --tsconfig-override |
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 template syntax as esbuild, but must explicitly include [ext]. |
banner | N/A | Not supported |
bundle | N/A | Always on. To transpile without bundling, use Bun.Transpiler. |
charset | N/A | Not supported |
chunkNames | naming.chunk | Uses the same template syntax as esbuild, but must explicitly include [ext]. |
color | N/A | Bun returns log messages in the logs property of the build result. |
conditions | N/A | Not supported. Export condition priority is determined by target. |
define | define | |
drop | N/A | Not supported |
entryNames | naming or naming.entry | Bun supports a naming key that can be either a string or an object. Uses the same template syntax as esbuild, but must explicitly include [ext]. |
entryPoints | entrypoints | Casing difference |
external | external | No difference |
footer | N/A | Not supported |
format | format | Currently only supports "esm". Plans to support "cjs" and "iife". |
globalName | N/A | Not supported |
ignoreAnnotations | N/A | Not supported |
inject | N/A | Not supported |
jsx | jsx | Not supported in JS API; configure via tsconfig.json |
jsxDev | jsxDev | Not supported in JS API; configure via tsconfig.json |
jsxFactory | jsxFactory | Not supported in JS API; configure via tsconfig.json |
jsxFragment | jsxFragment | Not supported in JS API; configure via tsconfig.json |
jsxImportSource | jsxImportSource | Not supported in JS API; configure via tsconfig.json |
jsxSideEffects | jsxSideEffects | Not supported in JS API; configure via tsconfig.json |
keepNames | N/A | Not supported |
legalComments | N/A | Not supported |
loader | loader | Bun’s set of built-in loaders differs from esbuild’s; see Bundler > Loaders for complete reference. |
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 | Bun supports boolean, string (path), or object (with json/markdown paths) |
minify | minify | In Bun, minify can be a boolean or object with granular control. |
minifyIdentifiers | minify.identifiers | |
minifySyntax | minify.syntax | |
minifyWhitespace | minify.whitespace | |
nodePaths | N/A | Not supported |
outExtension | N/A | Not supported |
outbase | root | |
outdir | outdir | |
outfile | outfile | |
packages | packages | |
platform | target | |
plugins | plugins | Bun’s plugin API is similar but not identical to esbuild’s |
preserveSymlinks | N/A | Not supported |
publicPath | publicPath | |
pure | N/A | Not supported |
resolveExtensions | N/A | Not supported |
sourceRoot | N/A | Not supported |
sourcemap | sourcemap | No difference |
sourcesContent | N/A | Not supported |
splitting | splitting | |
stdin | N/A | Not supported; Bun doesn’t currently support stdin input |
supported | N/A | Not supported |
target | N/A | Not supported; Bun doesn’t do syntax downleveling |
treeShaking | N/A | Always enabled |
tsconfig | tsconfig | |
write | N/A | Always true when outdir is specified |
Plugin API
Bun’s plugin API is similar to esbuild’s but not identical. Both useonLoad and onResolve hooks, but the exact API differs:
esbuild
Bun
- Bun requires explicit
loaderspecification inonLoad - Bun supports additional lifecycle hooks:
onStart,onEnd,onBeforeParse - Bun’s plugin system is shared between runtime and bundler
Migration checklist
-
Update your build script
- Change
esbuildtobun buildin CLI commands - Change
esbuild.build()toBun.build()in JavaScript
- Change
-
Remove
--bundleflag- Bun bundles by default
- Add
--no-bundleif you need transpilation only
-
Update loader syntax
- Change
--loader:.svg=textto--loader .svg:text
- Change
-
Update naming options
assetNames→naming.assetchunkNames→naming.chunkentryNames→naming.entry
-
Update platform option
--platform→--target
-
Check unsupported features
- Remove
--serve(useBun.serveinstead) - Remove
--target(syntax downleveling not supported) - Remove
--injectif used
- Remove
-
Update plugins
- Review plugin API differences
- Add explicit
loadertoonLoadreturns - Test plugin behavior
-
Test your build
- Run the new build command
- Verify output files
- Test in browsers/environments
Example migration
Before (esbuild)
package.json
After (Bun)
package.json
Getting help
If you encounter issues during migration:- Check the Bun documentation
- Search GitHub issues
- Ask in the Bun Discord
- File a bug report if you find a problem