Bun Bundler
Bun's built-in bundler: Bun.build() API, entry points, output formats (esm, cjs, iife), plugins, loaders, tree shaking, code splitting, CSS bundling, HTML entries, and compile-time macros.
You are an expert in Bun's built-in bundler, covering the JavaScript API, CLI interface, plugin system, and production optimization techniques.
## Key Points
- **esm**: Default. Produces `import`/`export` syntax. Best for modern browsers and Node/Bun.
- **cjs**: Produces `require`/`module.exports`. Use for Node.js packages that must support older consumers.
- **iife**: Wraps everything in a self-executing function. Use for `<script>` tags in browsers without module support.
- **Bundling server-side code with `target: "browser"`**: Server code that uses `node:fs` or `bun:sqlite` will fail. Use `target: "bun"` or `target: "node"` for backend bundles.
- **Using `require()` and expecting tree shaking**: Tree shaking only works with static `import`/`export`. Migrate to ESM for optimal bundle sizes.
- **Forgetting `splitting: true` for multi-page apps**: Without code splitting, shared libraries are duplicated in every entry point's output.
- **Inlining large assets with the `text` loader**: Large files imported as text increase bundle size. Use the `file` loader for assets over a few KB.
- **Not checking `result.success`**: Build errors do not throw by default. Always check `result.success` and log `result.logs` on failure.
## Quick Example
```typescript
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
});
// Produces: dist/index.html (with updated paths), dist/app-[hash].js, dist/main-[hash].css
```
```json
{
"sideEffects": false
}
```skilldb get bun-skills/Bun BundlerFull skill: 393 linesBun Bundler — Fast JavaScript and CSS Bundling
You are an expert in Bun's built-in bundler, covering the JavaScript API, CLI interface, plugin system, and production optimization techniques.
Overview
Bun includes a bundler that replaces webpack, esbuild, Rollup, and Parcel for most use cases. It is invoked via the Bun.build() API or the bun build CLI command. The bundler resolves imports, performs tree shaking, supports code splitting, and handles TypeScript, JSX, CSS, and JSON out of the box. It is significantly faster than webpack and comparable in speed to esbuild.
Basic Usage
CLI
# Bundle a single entry point
bun build ./src/index.ts --outdir ./dist
# Multiple entry points
bun build ./src/index.ts ./src/worker.ts --outdir ./dist
# Specify output format
bun build ./src/index.ts --outdir ./dist --format esm
# Minify output
bun build ./src/index.ts --outdir ./dist --minify
# Generate sourcemaps
bun build ./src/index.ts --outdir ./dist --sourcemap=external
# Watch mode
bun build ./src/index.ts --outdir ./dist --watch
JavaScript API
const result = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
format: "esm",
minify: true,
sourcemap: "external",
target: "browser",
});
if (!result.success) {
for (const log of result.logs) {
console.error(log);
}
process.exit(1);
}
// result.outputs is an array of BuildArtifact objects
for (const output of result.outputs) {
console.log(output.path); // absolute path to output file
console.log(output.kind); // "entry-point", "chunk", "asset"
}
Entry Points
Entry points are the files where bundling begins. Each entry point produces at least one output file:
await Bun.build({
entrypoints: [
"./src/index.ts",
"./src/worker.ts",
"./src/service-worker.ts"
],
outdir: "./dist",
});
HTML Entry Points
Bun can use HTML files as entry points, automatically finding and bundling all referenced scripts and stylesheets:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./styles/main.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/app.tsx"></script>
</body>
</html>
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
});
// Produces: dist/index.html (with updated paths), dist/app-[hash].js, dist/main-[hash].css
Output Formats
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
format: "esm", // ES modules (default) -- import/export
// format: "cjs", // CommonJS -- require/module.exports
// format: "iife", // Immediately Invoked Function Expression -- for <script> tags
});
- esm: Default. Produces
import/exportsyntax. Best for modern browsers and Node/Bun. - cjs: Produces
require/module.exports. Use for Node.js packages that must support older consumers. - iife: Wraps everything in a self-executing function. Use for
<script>tags in browsers without module support.
Target Environments
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "browser", // default -- strips Node/Bun-specific code
// target: "bun", // keeps Bun-specific APIs
// target: "node", // keeps Node.js APIs, resolves node: imports
});
The target affects how built-in modules are resolved. With target: "browser", imports like node:fs are marked as errors. With target: "bun", bun:sqlite and other Bun-specific imports are left as-is.
Tree Shaking
Bun performs tree shaking automatically for ESM code. Unused exports are removed from the output:
// utils.ts
export function used() { return 1; }
export function unused() { return 2; } // removed if never imported
// index.ts
import { used } from "./utils";
console.log(used());
Tree shaking only works reliably with ES module syntax (import/export). CommonJS require() calls are dynamic and cannot be statically analyzed, so they prevent tree shaking.
Mark packages as side-effect-free in package.json to enable deeper tree shaking:
{
"sideEffects": false
}
Or mark specific files as having side effects:
{
"sideEffects": ["./src/polyfills.ts", "*.css"]
}
Code Splitting
When multiple entry points share dependencies, Bun can extract shared code into separate chunks:
await Bun.build({
entrypoints: [
"./src/pages/home.ts",
"./src/pages/about.ts",
"./src/pages/contact.ts",
],
outdir: "./dist",
splitting: true, // enable code splitting
format: "esm", // splitting requires ESM format
});
This produces shared chunks like chunk-[hash].js that are imported by multiple entry points. Use dynamic import() to create split points within a single entry:
// Lazy-load a heavy module
const editor = await import("./heavy-editor.ts");
editor.init();
CSS Bundling
Bun bundles CSS files, resolving @import statements and handling CSS modules:
await Bun.build({
entrypoints: ["./src/styles/main.css"],
outdir: "./dist",
minify: true,
});
/* main.css */
@import "./reset.css";
@import "./variables.css";
@import "./components/button.css";
body {
font-family: var(--font-family);
}
CSS modules are supported when the file ends in .module.css:
/* button.module.css */
.primary {
background: blue;
color: white;
}
import styles from "./button.module.css";
// styles.primary is a unique class name like "button_primary_x7f3a"
Minification
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
minify: true, // minify everything
// Or control individually:
minify: {
whitespace: true, // remove whitespace
identifiers: true, // mangle variable names
syntax: true, // simplify syntax
},
});
Sourcemaps
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
sourcemap: "external", // .js.map file alongside output
// sourcemap: "inline", // embedded in the JS file as data URL
// sourcemap: "linked", // external file, referenced in JS comment
// sourcemap: "none", // no sourcemap (default)
});
Plugins
Plugins let you customize how Bun resolves and loads modules:
import type { BunPlugin } from "bun";
const envPlugin: BunPlugin = {
name: "env-loader",
setup(build) {
// Custom resolver: intercept specific import paths
build.onResolve({ filter: /^env:/ }, (args) => {
return {
path: args.path.slice(4), // strip "env:" prefix
namespace: "env",
};
});
// Custom loader: provide content for resolved modules
build.onLoad({ filter: /.*/, namespace: "env" }, (args) => {
const value = Bun.env[args.path];
return {
contents: `export default ${JSON.stringify(value ?? "")};`,
loader: "js",
};
});
},
};
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
plugins: [envPlugin],
});
// Now this works in your source code:
import apiUrl from "env:API_URL";
console.log(apiUrl);
YAML Plugin Example
const yamlPlugin: BunPlugin = {
name: "yaml-loader",
setup(build) {
build.onLoad({ filter: /\.ya?ml$/ }, async (args) => {
const text = await Bun.file(args.path).text();
const { parse } = await import("yaml");
const data = parse(text);
return {
contents: `export default ${JSON.stringify(data)};`,
loader: "json",
};
});
},
};
Loaders
Loaders tell Bun how to interpret files with specific extensions:
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
loader: {
".txt": "text", // import as string
".json": "json", // import as parsed JSON (default)
".toml": "toml", // import as parsed TOML
".svg": "file", // copy file, import as URL path
".png": "file", // copy file, import as URL path
".wasm": "file", // copy file
},
});
Built-in loaders: js, jsx, ts, tsx, json, toml, text, file, css, napi, wasm.
Macros — Compile-Time Code Execution
Macros run JavaScript at bundle time and inline the result:
// get-version.ts (runs at build time)
export function getVersion(): string {
const pkg = require("./package.json");
return pkg.version;
}
// index.ts
import { getVersion } from "./get-version" with { type: "macro" };
// At runtime, this becomes a string literal like "1.2.3"
console.log(getVersion());
Macros are powerful for embedding build metadata, reading configuration files at compile time, or generating code. They execute in Bun's runtime during the build step, so they have full access to the file system and environment.
Define — Compile-Time Constants
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
define: {
"process.env.NODE_ENV": JSON.stringify("production"),
"__APP_VERSION__": JSON.stringify("1.2.3"),
"__DEV__": "false",
},
});
Combined with tree shaking, this eliminates dead code branches:
if (__DEV__) {
// This entire block is removed in production builds
enableDevTools();
}
Anti-Patterns
- Bundling server-side code with
target: "browser": Server code that usesnode:fsorbun:sqlitewill fail. Usetarget: "bun"ortarget: "node"for backend bundles. - Using
require()and expecting tree shaking: Tree shaking only works with staticimport/export. Migrate to ESM for optimal bundle sizes. - Forgetting
splitting: truefor multi-page apps: Without code splitting, shared libraries are duplicated in every entry point's output. - Inlining large assets with the
textloader: Large files imported as text increase bundle size. Use thefileloader for assets over a few KB. - Not checking
result.success: Build errors do not throw by default. Always checkresult.successand logresult.logson failure. - Using macros for runtime-dependent values: Macros execute at build time. Do not use them for values that change at runtime (like environment-specific config that differs between staging and production).
Install this skill directly: skilldb add bun-skills
Related Skills
Bun Fundamentals
Bun runtime overview: all-in-one JavaScript runtime, bundler, test runner, and package manager. Installation, project initialization, Node.js compatibility, performance characteristics, and guidance on when to choose Bun vs Node.
Bun HTTP Server
Building HTTP servers with Bun: Bun.serve() API, routing patterns, WebSocket support, streaming responses, static file serving, TLS configuration, hot reloading, and integration with frameworks like Hono and Elysia.
Bun Node.js Migration
Migrating from Node.js to Bun: compatibility checklist, node:* module imports, native addon handling, environment variable differences, Docker setup, CI/CD pipeline changes, and common migration pitfalls.
Bun Package Manager
Bun as a package manager: bun install, bun add, bun remove, the binary lockfile (bun.lockb), workspace support, overrides, patching, publishing packages, global cache, and comparison to npm, pnpm, and yarn.
Bun Production Patterns
Production patterns for Bun: Docker deployments, TypeScript configuration, shell scripting with Bun.$, monorepo setup, database access patterns, and deployment to Fly.io and Railway.
Bun Runtime APIs
Bun-native runtime APIs including Bun.serve(), Bun.file(), Bun.write(), Bun.spawn(), Bun.sleep(), Bun.env, FFI for calling native libraries, built-in SQLite, S3 client, glob, and semver utilities.