Bundler
Bun's fast native bundler for JavaScript, TypeScript, JSX, and more
export const name_0 = undefined
Bun's fast native bundler can be used via the bun build CLI command or the Bun.build() JavaScript API.
At a Glance
- JS API:
await Bun.build({ entrypoints, outdir }) - CLI:
bun build <entry> --outdir ./out - Watch:
--watchfor incremental rebuilds - Targets:
--target browser|bun|node - Formats:
--format esm|cjs|iife(experimental for cjs/iife)
It's fast. The numbers below represent performance on esbuild's three.js benchmark.
Why bundle?
The bundler is a key piece of infrastructure in the JavaScript ecosystem. As a brief overview of why bundling is so important:
- Reducing HTTP requests. A single package in
node_modulesmay consist of hundreds of files, and large applications may have dozens of such dependencies. Loading each of these files with a separate HTTP request becomes untenable very quickly, so bundlers are used to convert our application source code into a smaller number of self-contained "bundles" that can be loaded with a single request. - Code transforms. Modern apps are commonly built with languages or tools like TypeScript, JSX, and CSS modules, all of which must be converted into plain JavaScript and CSS before they can be consumed by a browser. The bundler is the natural place to configure these transformations.
- Framework features. Frameworks rely on bundler plugins & code transformations to implement common patterns like file-system routing, client-server code co-location (think
getServerSidePropsor Remix loaders), and server components. - Full-stack Applications. Bun's bundler can handle both server and client code in a single command, enabling optimized production builds and single-file executables. With build-time HTML imports, you can bundle your entire application — frontend assets and backend server — into a single deployable unit.
Let's jump into the bundler API.
The Bun bundler is not intended to replace tsc for typechecking or generating type declarations.
Basic example
Let's build our first bundle. You have the following two files, which implement a simple client-side rendered React app.
const root = ReactDOM.createRoot(document.getElementById("root")!); root.render();
```tsx Component.tsx icon="https://mintcdn.com/bun-1dd33a4e/nIz6GtMH5K-dfXeV/icons/typescript.svg?fit=max&auto=format&n=nIz6GtMH5K-dfXeV&q=85&s=5d73d76daf7eb7b158469d8c30d349b0" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function Component(props: { message: string }) {
return <h1>{props.message}</h1>;
}
Here, index.tsx is the "entrypoint" to our application. Commonly, this will be a script that performs some side effect, like starting a server or—in this case—initializing a React root. Because we're using TypeScript & JSX, we need to bundle our code before it can be sent to the browser.
To create our bundle:
bun build ./index.tsx --outdir ./out
For each file specified in entrypoints, Bun will generate a new bundle. This bundle will be written to disk in the ./out directory (as resolved from the current working directory). After running the build, the file system looks like this:
.
├── index.tsx
├── Component.tsx
└── out
└── index.js
The contents of out/index.js will look something like this:
// out/index.js
// ...
// ~20k lines of code
// including the contents of `react-dom/client` and all its dependencies
// this is where the $jsxDEV and $createRoot functions are defined
// Component.tsx
function Component(props) {
return $jsxDEV(
"p",
{
children: props.message,
},
undefined,
false,
undefined,
this,
);
}
// index.tsx
var rootNode = document.getElementById("root");
var root = $createRoot(rootNode);
root.render(
$jsxDEV(
Component,
{
message: "Sup!",
},
undefined,
false,
undefined,
this,
),
);
Watch mode
Like the runtime and test runner, the bundler supports watch mode natively.
bun build ./index.tsx --outdir ./out --watch
Content types
Like the Bun runtime, the bundler supports an array of file types out of the box. The following table breaks down the bundler's set of standard "loaders". Refer to Bundler > File types for full documentation.
| Extensions | Details |
|---|---|
.js .jsx .cjs .mjs .mts .cts .ts .tsx |
Uses Bun's built-in transpiler to parse the file and transpile TypeScript/JSX syntax to vanilla JavaScript. The bundler executes a set of default transforms including dead code elimination and tree shaking. At the moment Bun does not attempt to down-convert syntax; if you use recently ECMAScript syntax, that will be reflected in the bundled code. |
.json |
JSON files are parsed and inlined into the bundle as a JavaScript object.js<br/>import pkg from "./package.json";<br/>pkg.name; // => "my-package"<br/> |
.jsonc |
JSON with comments. Files are parsed and inlined into the bundle as a JavaScript object.js<br/>import config from "./config.jsonc";<br/>config.name; // => "my-config"<br/> |
.toml |
TOML files are parsed and inlined into the bundle as a JavaScript object.js<br/>import config from "./bunfig.toml";<br/>config.logLevel; // => "debug"<br/> |
.yaml .yml |
YAML files are parsed and inlined into the bundle as a JavaScript object.js<br/>import config from "./config.yaml";<br/>config.name; // => "my-app"<br/> |
.txt |
The contents of the text file are read and inlined into the bundle as a string.js<br/>import contents from "./file.txt";<br/>console.log(contents); // => "Hello, world!"<br/> |
.html |
HTML files are processed and any referenced assets (scripts, stylesheets, images) are bundled. |
.css |
CSS files are bundled together into a single .css file in the output directory. |
.node .wasm |
These files are supported by the Bun runtime, but during bundling they are treated as assets. |
Assets
If the bundler encounters an import with an unrecognized extension, it treats the imported file as an external file. The referenced file is copied as-is into outdir, and the import is resolved as a path to the file.
// bundled output
var logo = "./logo-a7305bdef.svg";
console.log(logo);
The exact behavior of the file loader is also impacted by naming and publicPath.
Refer to the Bundler > Loaders page for more complete documentation on the file loader.
Plugins
The behavior described in this table can be overridden or extended with plugins. Refer to the Bundler > Loaders page for complete documentation.
API
entrypoints
Required
An array of paths corresponding to the entrypoints of our application. One bundle will be generated for each entrypoint.
files
A map of file paths to their contents for in-memory bundling. This allows you to bundle virtual files that don't exist on disk, or override the contents of files that do exist. This option is only available in the JavaScript API.
File contents can be provided as a string, Blob, TypedArray, or ArrayBuffer.
Bundle entirely from memory
You can bundle code without any files on disk by providing all sources via files:
const result = await Bun.build({
entrypoints: ["/app/index.ts"],
files: {
"/app/index.ts": `
import { greet } from "./greet.ts";
console.log(greet("World"));
`,
"/app/greet.ts": `
export function greet(name: string) {
return "Hello, " + name + "!";
}
`,
},
});
const output = await result.outputs[0].text();
console.log(output);
When all entrypoints are in the files map, the current working directory is used as the root.
Override files on disk
In-memory files take priority over files on disk. This lets you override specific files while keeping the rest of your codebase unchanged:
// Assume ./src/config.ts exists on disk with development settings
await Bun.build({
entrypoints: ["./src/index.ts"],
files: {
// Override config.ts with production values
"./src/config.ts": `
export const API_URL = "https://api.production.com";
export const DEBUG = false;
`,
},
outdir: "./dist",
});
Mix disk and virtual files
Real files on disk can import virtual files, and virtual files can import real files:
// ./src/index.ts exists on disk and imports "./generated.ts"
await Bun.build({
entrypoints: ["./src/index.ts"],
files: {
// Provide a virtual file that index.ts imports
"./src/generated.ts": `
export const BUILD_ID = "${crypto.randomUUID()}";
export const BUILD_TIME = ${Date.now()};
`,
},
outdir: "./dist",
});
This is useful for code generation, injecting build-time constants, or testing with mock modules.
outdir
The directory where output files will be written.
If outdir is not passed to the JavaScript API, bundled code will not be written to disk. Bundled files are returned in an array of BuildArtifact objects. These objects are Blobs with extra properties; see Outputs for complete documentation.
const result = await Bun.build({
entrypoints: ["./index.ts"],
});
for (const res of result.outputs) {
// Can be consumed as blobs
await res.text();
// Bun will set Content-Type and Etag headers
new Response(res);
// Can be written manually, but you should use `outdir` in this case.
Bun.write(path.join("out", res.path), res);
}
When outdir is set, the path property on a BuildArtifact will be the absolute path to where it was written to.
target
The intended execution environment for the bundle.
Depending on the target, Bun will apply different module resolution rules and optimizations.
All bundles generated with target: "bun" are marked with a special // @bun pragma, which indicates to the Bun runtime that there's no need to re-transpile the file before execution.
If any entrypoints contains a Bun shebang (#!/usr/bin/env bun) the bundler will default to target: "bun" instead of "browser".
When using target: "bun" and format: "cjs" together, the // @bun @bun-cjs pragma is added and the CommonJS wrapper function is not compatible with Node.js.
format
Specifies the module format to be used in the generated bundles.
Bun defaults to "esm", and provides experimental support for "cjs" and "iife".
format: "esm" - ES Module
This is the default format, which supports ES Module syntax including top-level await, import.meta, and more.
To use ES Module syntax in browsers, set format to "esm" and make sure your <script type="module"> tag has type="module" set.
format: "cjs" - CommonJS
To build a CommonJS module, set format to "cjs". When choosing "cjs", the default target changes from "browser" (esm) to "node" (cjs). CommonJS modules transpiled with format: "cjs", target: "node" can be executed in both Bun and Node.js (assuming the APIs in use are supported by both).
format: "iife" - IIFE
TODO: document IIFE once we support globalNames.
jsx
Configure JSX transform behavior. Allows fine-grained control over how JSX is compiled.
Classic runtime example (uses factory and fragment):
# JSX configuration is handled via bunfig.toml or tsconfig.json
bun build ./app.tsx --outdir ./out
Automatic runtime example (uses importSource):
# JSX configuration is handled via bunfig.toml or tsconfig.json
bun build ./app.tsx --outdir ./out
splitting
Whether to enable code splitting.
When true, the bundler will enable code splitting. When multiple entrypoints both import the same file, module, or set of files/modules, it's often useful to split the shared code into a separate bundle. This shared bundle is known as a chunk. Consider the following files:
import { shared } from "./shared.ts";
export const shared = "shared";
To bundle entry-a.ts and entry-b.ts with code-splitting enabled:
Running this build will result in the following files:
.
├── entry-a.tsx
├── entry-b.tsx
├── shared.tsx
└── out
├── entry-a.js
├── entry-b.js
└── chunk-2fce6291bf86559d.js
The generated chunk-2fce6291bf86559d.js file contains the shared code. To avoid collisions, the file name automatically includes a content hash by default. This can be customized with naming.
plugins
A list of plugins to use during bundling.
await Bun.build({
entrypoints: ["./index.tsx"],
outdir: "./out",
plugins: [
/* ... */
],
});
Bun implements a universal plugin system for both Bun's runtime and bundler. Refer to the plugin documentation for complete documentation.
env
Controls how environment variables are handled during bundling. Internally, this uses define to inject environment variables into the bundle, but makes it easier to specify the environment variables to inject.
env: "inline"
Injects environment variables into the bundled output by converting process.env.FOO references to string literals containing the actual environment variable values.
For the input below:
// input.js
console.log(process.env.FOO);
console.log(process.env.BAZ);
The generated bundle will contain the following code:
// output.js
console.log("bar");
console.log("123");
env: "PUBLIC_*" (prefix)
Inlines environment variables matching the given prefix (the part before the * character), replacing process.env.FOO with the actual environment variable value. This is useful for selectively inlining environment variables for things like public-facing URLs or client-side tokens, without worrying about injecting private credentials into output bundles.
// Inline all env vars that start with "ACME_PUBLIC_"
env: "ACME_PUBLIC_*",
})
```
For example, given the following environment variables:
FOO=bar BAZ=123 ACME_PUBLIC_URL=https://acme.com
And source code:
console.log(process.env.FOO);
console.log(process.env.ACME_PUBLIC_URL);
console.log(process.env.BAZ);
The generated bundle will contain the following code:
console.log(process.env.FOO);
console.log("https://acme.com");
console.log(process.env.BAZ);
env: "disable"
Disables environment variable injection entirely.
sourcemap
Specifies the type of sourcemap to generate.
| Value | Description |
|---|---|
"none" |
Default. No sourcemap is generated. |
"linked" |
A separate *.js.map file is created alongside each *.js bundle using a //# sourceMappingURL comment to link the two. Requires --outdir to be set. The base URL of this can be customized with --public-path.js<br/>// <bundled code here><br/><br/>//# sourceMappingURL=bundle.js.map<br/> |
"external" |
A separate *.js.map file is created alongside each *.js bundle without inserting a //# sourceMappingURL comment.Generated bundles contain a debug id that can be used to associate a bundle with its corresponding sourcemap. This debugId is added as a comment at the bottom of the file.js<br/>// <generated bundle code><br/><br/>//# debugId=<DEBUG ID><br/> |
"inline" |
A sourcemap is generated and appended to the end of the generated bundle as a base64 payload.js<br/>// <bundled code here><br/><br/>//# sourceMappingURL=data:application/json;base64,<encoded sourcemap here><br/> |
The associated *.js.map sourcemap will be a JSON file containing an equivalent debugId property.
minify
Whether to enable minification. Default false.
When targeting bun, identifiers will be minified by default.
To enable all minification options:
To granularly enable certain minifications:
external
A list of import paths to consider external. Defaults to [].
An external import is one that will not be included in the final bundle. Instead, the import statement will be left as-is, to be resolved at runtime.
For instance, consider the following entrypoint file:
import _ from "lodash";
import { z } from "zod";
const value = z.string().parse("Hello world!");
console.log(_.upperCase(value));
Normally, bundling index.tsx would generate a bundle containing the entire source code of the "zod" package. If instead, we want to leave the import statement as-is, we can mark it as external:
The generated bundle will look something like this:
import { z } from "zod";
// ...
// the contents of the "lodash" package
// including the `_.upperCase` function
var value = z.string().parse("Hello world!");
console.log(_.upperCase(value));
To mark all imports as external, use the wildcard *:
…(truncated)