RuntimePackage ManagerBundlerTest RunnerLinterFormatterGuidesReferenceBlogFeedback

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

terminal
# 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.

esbuildbun buildNotes
--bundlen/aBun always bundles; use --no-bundle to disable it.
--define:K=V--define K=VSmall syntax difference; no colon.
esbuild --define:foo=bar
bun build --define foo=bar
--external:<pkg>--external <pkg>Small syntax difference; no colon.
esbuild --external:react
bun build --external react
--format--formatBun supports "esm", "cjs", and "iife". esbuild defaults to "iife".
--loader:.ext=loader--loader .ext:loaderBun 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=text
bun build app.ts --loader .svg:text
--minify--minifyNo differences
--outdir--outdirNo differences
--outfile--outfileNo differences
--packages--packagesNo differences
--platform--targetRenamed to --target for consistency with tsconfig. Does not support neutral.
--serven/aNot applicable
--sourcemap--sourcemapSupports linked (the default when no value is given), external, inline, and none. Does not support esbuild's both.
--splitting--splittingNo differences
--targetn/aNot supported. Bun's bundler performs no syntactic down-leveling.
--watch--watchNo differences
--allow-overwriten/aBun never allows overwriting
--analyzen/aNot supported
--asset-names--asset-namingRenamed for consistency with naming in JS API
--banner--bannerOnly applies to js bundles
--footer--footerOnly applies to js bundles
--certfilen/aNot applicable
--charset=utf8n/aNot supported
--chunk-names--chunk-namingRenamed for consistency with naming in JS API
--colorn/aAlways enabled
--drop--drop
n/a--featureBun-specific. Enables feature flags for compile-time dead-code elimination through import { feature } from "bun:bundle"
--entry-names--entry-namingRenamed for consistency with naming in JS API
--global-namen/aNot supported
--ignore-annotations--ignore-dce-annotations
--injectn/aNot supported
--jsx--jsx-runtime <runtime>Supports "automatic" (uses jsx transform) and "classic" (uses React.createElement)
--jsx-devn/aBun 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
--keyfilen/aNot applicable
--legal-commentsn/aNot supported
--log-leveln/aNot supported. You can set the log level in bunfig.toml as logLevel.
--log-limitn/aNot supported
--log-override:X=Yn/aNot supported
--main-fieldsn/aNot supported
--mangle-cachen/aNot supported
--mangle-propsn/aNot supported
--mangle-quotedn/aNot supported
--metafile--metafile
--minify-whitespace--minify-whitespace
--minify-identifiers--minify-identifiers
--minify-syntax--minify-syntax
--out-extensionn/aNot supported
--outbase--root
--preserve-symlinksn/aNot supported
--public-path--public-path
--puren/aNot supported
--reserve-propsn/aNot supported
--resolve-extensionsn/aNot supported
--servedirn/aNot applicable
--source-rootn/aNot supported
--sourcefilen/aNot supported. Bun does not support stdin input.
--sources-contentn/aNot supported
--supportedn/aNot supported
--tree-shakingn/aAlways 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
--versionn/aRun bun --version to see the version of Bun.

JavaScript API

esbuild.build()Bun.build()Notes
absWorkingDirn/aAlways set to process.cwd()
aliasn/aNot supported
allowOverwriten/aAlways false
assetNamesnaming.assetUses 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/>
bannerbannerOnly applies to js bundles
bundlen/aAlways true. Use Bun.Transpiler to transpile without bundling.
charsetn/aNot supported
chunkNamesnaming.chunkUses 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/>
colorn/aBun returns logs in the logs property of the build result.
conditionsconditionsNo differences
definedefine
dropdrop
entryNamesnaming or naming.entryBun 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/>
entryPointsentrypointsCapitalization difference
externalexternalNo differences
footerfooterOnly applies to js bundles
formatformatSupports "esm", "cjs", and "iife".
globalNamen/aNot supported
ignoreAnnotationsignoreDCEAnnotations
injectn/aNot supported
jsxjsx.runtimeSupports "automatic" and "classic"
jsxDevjsx.development
jsxFactoryjsx.factory
jsxFragmentjsx.fragment
jsxImportSourcejsx.importSource
jsxSideEffectsjsx.sideEffects
keepNamesminify.keepNames
legalCommentsn/aNot supported
loaderloaderBun 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.
logLeveln/aNot supported
logLimitn/aNot supported
logOverriden/aNot supported
mainFieldsn/aNot supported
mangleCachen/aNot supported
manglePropsn/aNot supported
mangleQuotedn/aNot supported
metafilemetafile
minifyminifyIn 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/>
minifyIdentifiersminify.identifiersSee minify
minifySyntaxminify.syntaxSee minify
minifyWhitespaceminify.whitespaceSee minify
nodePathsn/aNot supported
outExtensionn/aNot supported
outbaserootDifferent name
outdiroutdirNo differences
outfileoutfileNo differences
packagespackagesNo differences
platformtargetSupports "bun", "node" and "browser" (the default). Does not support "neutral".
pluginspluginsBun's plugin API is a subset of esbuild's. Some esbuild plugins work with Bun without modification.
preserveSymlinksn/aNot supported
publicPathpublicPathNo differences
puren/aNot supported
reservePropsn/aNot supported
resolveExtensionsn/aNot supported
sourceRootn/aNot supported
sourcemapsourcemapSupports "none", "linked", "inline", and "external"
sourcesContentn/aNot supported
splittingsplittingNo differences
stdinn/aNot supported
supportedn/aNot supported
targetn/aNo support for syntax downleveling
treeShakingtreeShakingDefaults to true
tsconfigtsconfig
writen/aSet 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.

myPlugin.ts
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.

myPlugin.ts
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

onLoad

  • 🟢 filter
  • 🟢 namespace