Bun

vs esbuild

Bun's bundler API is inspired heavily by esbuild. Migrating to Bun's bundler from esbuild should be relatively painless. This guide will briefly explain why you might consider migrating to Bun's bundler and provide a side-by-side API comparison reference for those who are already familiar with esbuild's API.

There are a few behavioral differences to note.

  • Bundling by default. Unlike esbuild, Bun always bundles by default. This is why the --bundle flag isn't necessary in the Bun example. To transpile each file individually, use Bun.Transpiler.
  • It's just a bundler. Unlike esbuild, Bun's bundler does not include a built-in development server or file watcher. It's just a bundler. The bundler is intended for use in conjunction with Bun.serve and other runtime APIs to achieve the same effect. As such, all options relating to HTTP/file watching are not applicable.

Performance

With a performance-minded API coupled with the extensively optimized Zig-based JS/TS parser, 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

Bun and esbuild both provide a command-line interface.

esbuild <entrypoint> --outdir=out --bundle
bun build <entrypoint> --outdir=out

In Bun's CLI, simple boolean flags like --minify do not accept an argument. Other flags like --outdir <path> do accept an argument; these flags can be written as --outdir out or --outdir=out. Some flags like --define can be specified several times: --define foo=bar --define bar=baz.

esbuildbun build
--bundlen/aBun always bundles, use --no-bundle to disable this behavior.

--define:K=V

--define K=V

Small 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" and "cjs" currently, but more module formats are planned. esbuild defaults to "iife".

--loader:.ext=loader

--loader .ext:loader

Bun supports a different set of built-in loaders than esbuild; see Bundler > Loaders for a complete reference. The esbuild loaders dataurl, binary, base64, copy, and empty are not yet implemented.

The syntax for --loader is slightly different.

esbuild app.ts --bundle --loader:.svg=text
bun build app.ts --loader .svg:text
--minify--minifyNo differences
--outdir--outdirNo differences
--outfile--outfile
--packages--packagesNo differences
--platform--targetRenamed to --target for consistency with tsconfig. Does not support neutral.
--serven/aNot applicable
--sourcemap--sourcemapNo differences
--splitting--splittingNo differences
--targetn/aNo supported. Bun's bundler performs no syntactic down-leveling at this time.
--watch--watchNo differences
--allow-overwriten/aOverwriting is never allowed
--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
--entry-names--entry-namingRenamed for consistency with naming in JS API
--global-namen/aNot applicable, Bun does not support iife output at this time
--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 will use the jsx transform. Otherwise, it uses jsxDEV. For any to Bun uses jsxDEV. The bundler does not support preserve.
--jsx-factory--jsx-factory
--jsx-fragment--jsx-fragment
--jsx-import-source--jsx-import-source
--jsx-side-effectsn/aJSX is always assumed to be side-effect-free
--keep-namesn/aNot supported
--keyfilen/aNot applicable
--legal-commentsn/aNot supported
--log-leveln/aNot supported. This can be set 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
--metafilen/aNot supported
--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 yet.
--sourcemap--sourcemapNo differences
--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()
absWorkingDirn/aAlways set to process.cwd()
aliasn/aNot supported
allowOverwriten/aAlways false

assetNames

naming.asset

Uses same templating syntax as esbuild, but [ext] must be included explicitly.

Bun.build({
  entrypoints: ["./index.tsx"],
  naming: {
    asset: "[name].[ext]",
  },
});
bannern/aNot supported
bundlen/aAlways true. Use Bun.Transpiler to transpile without bundling.
charsetn/aNot supported

chunkNames

naming.chunk

Uses same templating syntax as esbuild, but [ext] must be included explicitly.

Bun.build({
  entrypoints: ["./index.tsx"],
  naming: {
    chunk: "[name].[ext]",
  },
});
colorn/aBun returns logs in the logs property of the build result.
conditionsn/aNot supported. Export conditions priority is determined by target.
definedefine
dropn/aNot supported

entryNames

naming or naming.entry

Bun supports a naming key that can either be a string or an object. Uses same templating syntax as esbuild, but [ext] must be included explicitly.

Bun.build({
  entrypoints: ["./index.tsx"],
  // when string, this is equivalent to entryNames
  naming: "[name].[ext]",

  // granular naming options
  naming: {
    entry: "[name].[ext]",
    asset: "[name].[ext]",
    chunk: "[name].[ext]",
  },
});
entryPointsentrypointsCapitalization difference
externalexternalNo differences
footern/aNot supported
formatformatOnly supports "esm" currently. Support for "cjs" and "iife" is planned.
globalNamen/aNot supported
ignoreAnnotationsn/aNot supported
injectn/aNot supported
jsxjsxNot supported in JS API, configure in tsconfig.json
jsxDevjsxDevNot supported in JS API, configure in tsconfig.json
jsxFactoryjsxFactoryNot supported in JS API, configure in tsconfig.json
jsxFragmentjsxFragmentNot supported in JS API, configure in tsconfig.json
jsxImportSourcejsxImportSourceNot supported in JS API, configure in tsconfig.json
jsxSideEffectsjsxSideEffectsNot supported in JS API, configure in tsconfig.json
keepNamesn/aNot supported
legalCommentsn/aNot supported
loaderloaderBun supports a different set of built-in loaders than esbuild; see Bundler > Loaders for a complete reference. The esbuild loaders dataurl, binary, base64, copy, and empty are not yet implemented.
logLeveln/aNot supported
logLimitn/aNot supported
logOverriden/aNot supported
mainFieldsn/aNot supported
mangleCachen/aNot supported
manglePropsn/aNot supported
mangleQuotedn/aNot supported
metafilen/aNot supported

minify

minify

In Bun, minify can be a boolean or an object.

await Bun.build({
  entrypoints: ['./index.tsx'],
  // enable all minification
  minify: true

  // granular options
  minify: {
    identifiers: true,
    syntax: true,
    whitespace: true
  }
})
minifyIdentifiersminify.identifiersSee minify
minifySyntaxminify.syntaxSee minify
minifyWhitespaceminify.whitespaceSee minify
nodePathsn/aNot supported
outExtensionn/aNot supported
outbaserootDifferent name
outdiroutdirNo differences
outfileoutfileNo differences
packagesn/aNot supported, use external
platformtargetSupports "bun", "node" and "browser" (the default). Does not support "neutral".
pluginspluginsBun's plugin API is a subset of esbuild's. Some esbuild plugins will work out of the box with Bun.
preserveSymlinksn/aNot supported
publicPathpublicPathNo differences
puren/aNot supported
reservePropsn/aNot supported
resolveExtensionsn/aNot supported
sourceRootn/aNot supported
sourcemapsourcemapSupports "inline", "external", and "none"
sourcesContentn/aNot supported
splittingsplittingNo differences
stdinn/aNot supported
supportedn/aNot supported
targetn/aNo support for syntax downleveling
treeShakingn/aAlways true
tsconfign/aNot supported
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 the core functionality is implemented. Many third-party esbuild plugins will work out of the box with Bun.

Long term, we aim for feature parity with esbuild's API, so if something doesn't work please file an issue to help us prioritize.

Plugins in Bun and esbuild are defined with a builder object.

import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "my-plugin",
  setup(builder) {
    // define plugin
  },
};

The builder object provides some methods for hooking into parts of the bundling process. Bun implements onResolve and onLoad; it does not yet implement the esbuild hooks onStart, onEnd, and onDispose, and resolve utilities. initialOptions is partially implemented, being read-only and only having a subset of esbuild's options; use config (same thing but with Bun's BuildConfig format) instead.

import type { BunPlugin } from "bun";
const myPlugin: BunPlugin = {
  name: "my-plugin",
  setup(builder) {
    builder.onResolve(
      {
        /* onResolve.options */
      },
      args => {
        return {
          /* onResolve.results */
        };
      },
    );
    builder.onLoad(
      {
        /* onLoad.options */
      },
      args => {
        return {
          /* onLoad.results */
        };
      },
    );
  },
};

onResolve

options

🟢filter
🟢namespace

arguments

🟢path
🟢importer
🔴namespace
🔴resolveDir
🔴kind
🔴pluginData

results

🟢namespace
🟢path
🔴errors
🔴external
🔴pluginData
🔴pluginName
🔴sideEffects
🔴suffix
🔴warnings
🔴watchDirs
🔴watchFiles

onLoad

options

🟢filter
🟢namespace

arguments

🟢path
🔴namespace
🔴suffix
🔴pluginData

results

🟢contents
🟢loader
🔴errors
🔴pluginData
🔴pluginName
🔴resolveDir
🔴warnings
🔴watchDirs
🔴watchFiles