Bun's bundler API is inspired heavily by [esbuild](https://esbuild.github.io/). 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`](/docs/api/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 an 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](https://github.com/oven-sh/bun/tree/main/bench/bundle). {% image src="/images/bundler-speed.png" caption="Bundling 10 copies of three.js from scratch, with sourcemaps and minification" /%} ## CLI API Bun and esbuild both provide a command-line interface. ```bash $ esbuild --outdir=out --bundle $ bun build --outdir=out ``` In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Other flags like `--outdir ` 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`. {% table %} - `esbuild` - `bun build` --- - `--bundle` - n/a - Bun always bundles, use `--no-bundle` to disable this behavior. --- - `--define:K=V` - `--define K=V` - Small syntax difference; no colon. ```bash $ esbuild --define:foo=bar $ bun build --define foo=bar ``` --- - `--external:` - `--external ` - Small syntax difference; no colon. ```bash $ esbuild --external:react $ bun build --external react ``` --- - `--format` - `--format` - Bun only supports `"esm"` currently but other 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](/docs/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. ```bash $ esbuild app.ts --bundle --loader:.svg=text $ bun build app.ts --loader .svg:text ``` --- - `--minify` - `--minify` - No differences --- - `--outdir` - `--outdir` - No differences --- - `--outfile` - `--outfile` --- - `--packages` - n/a - Not supported --- - `--platform` - `--target` - Renamed to `--target` for consistency with tsconfig. Does not support `neutral`. --- - `--serve` - n/a - Not applicable --- - `--sourcemap` - `--sourcemap` - No differences --- - `--splitting` - `--splitting` - No differences --- - `--target` - n/a - No supported. Bun's bundler performs no syntactic down-leveling at this time. --- - `--watch` - `--watch` - No differences --- - `--allow-overwrite` - n/a - Overwriting is never allowed --- - `--analyze` - n/a - Not supported --- - `--asset-names` - `--asset-naming` - Renamed for consistency with `naming` in JS API --- - `--banner` - n/a - Not supported --- - `--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` - n/a - Not supported --- - `--entry-names` - `--entry-naming` - Renamed for consistency with `naming` in JS API --- - `--footer` - n/a - Not supported --- - `--global-name` - n/a - Not applicable, Bun does not support `iife` output at this time --- - `--ignore-annotations` - n/a - Not supported --- - `--inject` - n/a - Not supported --- - `--jsx` - `--jsx-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 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-effects` - n/a - JSX is always assumed to be side-effect-free --- - `--keep-names` - n/a - Not supported --- - `--keyfile` - n/a - Not applicable --- - `--legal-comments` - n/a - Not supported --- - `--log-level` - n/a - Not supported. This can be set 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` - n/a - Not supported --- - `--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 yet. --- - `--sourcemap` - `--sourcemap` - No differences --- - `--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. {% /table %} ## JavaScript API {% table %} - `esbuild.build()` - `Bun.build()` --- - `absWorkingDir` - n/a - Always set to `process.cwd()` --- - `alias` - n/a - Not supported --- - `allowOverwrite` - n/a - Always `false` --- - `assetNames` - `naming.asset` - Uses same templating syntax as esbuild, but `[ext]` must be included explicitly. ```ts Bun.build({ entrypoints: ["./index.tsx"], naming: { asset: "[name].[ext]", }, }); ``` --- - `banner` - n/a - Not supported --- - `bundle` - n/a - Always `true`. Use [`Bun.Transpiler`](/docs/api/transpiler) to transpile without bundling. --- - `charset` - n/a - Not supported --- - `chunkNames` - `naming.chunk` - Uses same templating syntax as esbuild, but `[ext]` must be included explicitly. ```ts Bun.build({ entrypoints: ["./index.tsx"], naming: { chunk: "[name].[ext]", }, }); ``` --- - `color` - n/a - Bun returns logs in the `logs` property of the build result. --- - `conditions` - n/a - Not supported. Export conditions priority is determined by `target`. --- - `define` - `define` --- - `drop` - n/a - Not 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. ```ts 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]", }, }); ``` --- - `entryPoints` - `entrypoints` - Capitalization difference --- - `external` - `external` - No differences --- - `footer` - n/a - Not supported --- - `format` - `format` - Only supports `"esm"` currently. Support for `"cjs"` and `"iife"` is planned. --- - `globalName` - n/a - Not supported --- - `ignoreAnnotations` - n/a - Not supported --- - `inject` - n/a - Not supported --- - `jsx` - `jsx` - Not supported in JS API, configure in `tsconfig.json` --- - `jsxDev` - `jsxDev` - Not supported in JS API, configure in `tsconfig.json` --- - `jsxFactory` - `jsxFactory` - Not supported in JS API, configure in `tsconfig.json` --- - `jsxFragment` - `jsxFragment` - Not supported in JS API, configure in `tsconfig.json` --- - `jsxImportSource` - `jsxImportSource` - Not supported in JS API, configure in `tsconfig.json` --- - `jsxSideEffects` - `jsxSideEffects` - Not supported in JS API, configure in `tsconfig.json` --- - `keepNames` - n/a - Not supported --- - `legalComments` - n/a - Not supported --- - `loader` - `loader` - Bun supports a different set of built-in loaders than esbuild; see [Bundler > Loaders](/docs/bundler/loaders) for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented. --- - `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` - n/a - Not supported --- - `minify` - `minify` - In Bun, `minify` can be a boolean or an object. ```ts Bun.build({ entrypoints: ['./index.tsx'], // enable all minification minify: true // granular options minify: { identifiers: true, syntax: true, whitespace: true } }) ``` --- - `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` - n/a - Not supported, use `external` --- - `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 will work out of the box with Bun. --- - `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 `"inline"`, `"external"`, and `"none"` --- - `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` - n/a - Always `true` --- - `tsconfig` - n/a - Not supported --- - `write` - n/a - Set to `true` if `outdir`/`outfile` is set, otherwise `false` --- {% /table %} ## 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. {% callout %} 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. {% /callout %} Plugins in Bun and esbuild are defined with a `builder` object. ```ts 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`](/docs/bundler/plugins) (same thing but with Bun's `BuildConfig` format) instead. ```ts 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` {% table %} - 🟢 - `filter` --- - 🟢 - `namespace` {% /table %} #### `arguments` {% table %} - 🟢 - `path` --- - 🟢 - `importer` --- - 🔴 - `namespace` --- - 🔴 - `resolveDir` --- - 🔴 - `kind` --- - 🔴 - `pluginData` {% /table %} #### `results` {% table %} - 🟢 - `namespace` --- - 🟢 - `path` --- - 🔴 - `errors` --- - 🔴 - `external` --- - 🔴 - `pluginData` --- - 🔴 - `pluginName` --- - 🔴 - `sideEffects` --- - 🔴 - `suffix` --- - 🔴 - `warnings` --- - 🔴 - `watchDirs` --- - 🔴 - `watchFiles` {% /table %} ### `onLoad` #### `options` {% table %} --- - 🟢 - `filter` --- - 🟢 - `namespace` {% /table %} #### `arguments` {% table %} --- - 🟢 - `path` --- - 🔴 - `namespace` --- - 🔴 - `suffix` --- - 🔴 - `pluginData` {% /table %} #### `results` {% table %} --- - 🟢 - `contents` --- - 🟢 - `loader` --- - 🔴 - `errors` --- - 🔴 - `pluginData` --- - 🔴 - `pluginName` --- - 🔴 - `resolveDir` --- - 🔴 - `warnings` --- - 🔴 - `watchDirs` --- - 🔴 - `watchFiles` {% /table %}