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.

Bundling 10 copies of three.js from scratch, with sourcemaps and minification

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

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.

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