Bun 12 ⚡ Bundling Applications for Production with bun build
Bun’s bundler is not a separate tool. It is a component of the runtime, exposed through the bun build command and the Bun.build() JavaScript API. The bundler reads TypeScript, JSX, and CSS natively, resolves imports using Bun’s own resolver, and produces optimized output for a specified target. The LFCS exam does not test Bun, but the Bun Certified Associate exam does. The conceptual model is the same as any bundler: an entrypoint, a dependency graph, a target environment, and an output format. The difference is that Bun’s bundler is fast enough to run on every save, and it integrates directly with the runtime that will execute the bundle.
Key point: The target determines the execution environment. target: "browser" is the default and prioritizes the "browser" export condition. target: "bun" is for server-side code and marks the bundle with a // @bun pragma so the runtime skips re-transpilation. target: "node" prioritizes the "node" export condition and does not polyfill Bun-specific globals .
Why bun build exists
The startup problem. A Bun server can run TypeScript directly without bundling. The runtime transpiles on the fly, and the startup cost is paid once. For a long-running server, this is acceptable. For a CLI tool or a serverless function that starts and stops frequently, the transpilation cost is paid on every invocation. Bundling moves the transpilation to build time. The --bytecode flag goes further, pre-compiling the JavaScript to JavaScriptCore bytecode so the runtime skips parsing entirely . The documented improvement is 1.5x to 4x faster startup depending on application size.
The deployment problem. A Node.js application deployed to a container includes node_modules, the package.json, the lockfile, and the source files. The image is large, and the startup is slow. bun build --compile produces a single-file executable that contains the runtime and the application code. The executable has no external dependencies. It can be copied to a server without Node.js or Bun installed . The --compile flag is the deployment mechanism for Bun applications.
The minification problem. A development build is readable. A production build should be small. The --minify flag enables whitespace removal, syntax optimization, and identifier shortening. The --production flag enables all three and sets process.env.NODE_ENV to "production" . The --keep-names flag preserves function and class names for readable stack traces while still shortening identifiers .
The target problem. A bundle for the browser must not include Node.js built-ins like fs. A bundle for Bun can use bun:sqlite and Bun.serve. A bundle for Node must not include Bun-specific globals. The target option applies the correct module resolution rules for the intended environment . Without it, the bundle may include code that fails at runtime.
The trade-off. Bundling adds a build step. The source is no longer the deployed artifact; the bundle is. This means the bundle must be regenerated when the source changes, and the build pipeline must be configured correctly. The benefit is faster startup, smaller deployment artifacts, and the ability to use features (like bytecode) that are only available at build time.
a. The basic bundle: CLI and JavaScript API
The bun build command takes an entrypoint and an output directory. The simplest invocation produces a bundle in the default format (ESM) for the default target (browser).
# Basic bundle
bun build ./index.tsx --outdir ./out
# Bundle for Bun runtime
bun build ./server.ts --outdir ./out --target bun
# Bundle for Node.js
bun build ./cli.ts --outdir ./out --target node --format cjs
The JavaScript API is the programmatic equivalent. The Bun.build() function returns a BuildArtifact object that contains the output files, the success status, and any logs.
const result = await Bun.build({
entrypoints: ["./index.tsx"],
outdir: "./out",
target: "browser",
});
if (!result.success) {
console.error(result.logs);
}
The entrypoints array can contain multiple files. When multiple entrypoints import the same module, the splitting option extracts the shared code into a separate chunk. The chunk filename includes a content hash to avoid collisions .
b. Minification, bytecode, and source maps
The --minify flag enables all three minification modes: whitespace, syntax, and identifiers. The modes can be enabled individually for finer control.
# Full minification
bun build ./index.ts --outdir ./out --minify
# Production mode (minify + NODE_ENV)
bun build ./index.ts --outdir ./out --production
# Keep function names for stack traces
bun build ./index.ts --outdir ./out --minify --keep-names
The --bytecode flag pre-compiles the JavaScript to JavaScriptCore bytecode. This is the optimization for startup time. The bytecode is not portable across Bun versions; it must be regenerated after a Bun upgrade .
# Minify, generate source maps, and pre-compile
bun build ./index.ts --outdir ./out --minify --sourcemap=linked --bytecode --target=bun
The --sourcemap option generates source maps. The linked value produces an external .map file and adds a //# sourceMappingURL comment. The inline value embeds the map in the bundle. The external value produces a separate map without the comment .
The --drop flag removes calls to specific functions or statements. The --drop=console removes all console.* calls. The --drop=debugger removes debugger statements. This is useful for production builds where logging is handled by a different mechanism .
c. Compiling to a single-file executable
The --compile flag produces a standalone executable. The output is a binary that contains the Bun runtime and the application code. It runs on the target platform without Bun installed.
# Compile to a single executable
bun build --compile ./cli.ts --outfile ./mycli
# Compile with embedded runtime arguments
bun build --compile --compile-exec-argv="--smol" ./app.ts --outfile ./myapp
The executable reads the BUN_OPTIONS environment variable at runtime, so flags like --smol or --cpu-prof can be passed without recompiling . The --compile-autoload-tsconfig and --compile-autoload-package-json flags enable runtime loading of those files, which is disabled by default in compiled executables .
The experimental --aot flag (or bun compile) performs ahead-of-time compilation to machine code. The executable has no JIT and no bytecode, so it starts faster and uses less memory. This is the optimization for production CLIs where startup time is critical .
Complete Example Session
# ============================================
# PART 1: BASIC BUNDLE
# ============================================
bun build ./index.ts --outdir ./dist
# Produces ./dist/index.js in ESM format for browsers.
# ============================================
# PART 2: TARGET BUN
# ============================================
bun build ./server.ts --outdir ./dist --target bun
# Produces a bundle with the // @bun pragma.
# The runtime skips re-transpilation.
# ============================================
# PART 3: TARGET NODE
# ============================================
bun build ./cli.ts --outdir ./dist --target node --format cjs
# Produces a CommonJS bundle for Node.js.
# ============================================
# PART 4: PRODUCTION BUILD
# ============================================
bun build ./index.ts --outdir ./dist --production
# Minifies all modes, sets NODE_ENV=production.
# ============================================
# PART 5: BYTECODE
# ============================================
bun build ./server.ts --outdir ./dist --target bun --bytecode
# Produces index.js and index.jsc.
# The runtime uses the .jsc file automatically.
# ============================================
# PART 6: DROP CONSOLE
# ============================================
bun build ./index.ts --outdir ./dist --production --drop=console
# Removes all console.* calls from the bundle.
# ============================================
# PART 7: SINGLE-FILE EXECUTABLE
# ============================================
bun build --compile ./cli.ts --outfile ./mycli
# ./mycli is a standalone binary.
./mycli
# Runs without Bun installed.
# ============================================
# PART 8: JAVASCRIPT API
# ============================================
const result = await Bun.build({
entrypoints: ["./index.ts"],
outdir: "./dist",
target: "browser",
minify: true,
});
if (!result.success) console.error(result.logs);
# ============================================
# PART 9: VERIFY OUTPUT
# ============================================
ls -la ./dist/
# index.js (minified)
# index.js.map (linked source map)
# index.jsc (bytecode, if --bytecode)
The nine parts trace the build lifecycle: basic bundle, target selection, format selection, production minification, bytecode, drop statements, single-file compilation, JavaScript API, and output verification.
Quick Reference
Targets
| Target | Default Format | Export Condition | Use Case |
|---|---|---|---|
browser | esm | browser | Web applications |
bun | esm | bun | Bun servers, full-stack |
node | esm | node | Node.js CLIs, libraries |
Formats
| Format | Flag | Notes |
|---|---|---|
| ESM | --format esm | Default, supports top-level await |
| CJS | --format cjs | CommonJS, target changes to node |
| IIFE | --format iife | Browser global, no export exposure |
Build Flags
| Flag | Purpose |
|---|---|
--outdir | Output directory |
--target | Execution environment |
--format | Module format |
--minify | All minification modes |
--production | Minify + NODE_ENV |
--bytecode | Pre-compile to JavaScriptCore bytecode |
--sourcemap=linked | External source map |
--drop=console | Remove console calls |
--keep-names | Preserve function names |
--compile | Single-file executable |
--splitting | Code splitting for multiple entrypoints |
Best Practices
✅ Do This:
bun build ./server.ts --outdir ./dist --target bun --production
<!-- Target the runtime, minify for production --> # ✅
bun build ./cli.ts --outfile ./cli --compile
<!-- Compile for single-file deployment --> # ✅
bun build ./index.ts --outdir ./dist --target bun --bytecode
<!-- Use bytecode for faster startup --> # ✅
❌ Don’t Do This:
# Bundle for browser with Bun-specific globals
<!-- Bun.serve is not available in the browser --> # ❌
# Skip source maps in production
<!-- Error traces become unreadable --> # ❌
# Assume bytecode survives Bun upgrades
<!-- Regenerate after each Bun version --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Bundle fails at runtime | Wrong target | Match target to execution environment |
| Bun globals undefined | target: "node" used | Use target: "bun" for Bun APIs |
| Startup not faster | No --bytecode | Add --bytecode for Bun target |
| Stack traces unreadable | No --keep-names with minify | Add --keep-names |
| Bytecode mismatch | Bun version changed | Rebuild with current Bun version |
Real-World Examples
1. Browser Bundle
bun build ./app.tsx --outdir ./dist
2. Bun Server Bundle
bun build ./server.ts --outdir ./dist --target bun
3. Node CLI Bundle
bun build ./cli.ts --outdir ./dist --target node --format cjs
4. Production Minify
bun build ./index.ts --outdir ./dist --production
5. Bytecode Startup
bun build ./server.ts --outdir ./dist --target bun --bytecode
6. Single Executable
bun build --compile ./cli.ts --outfile ./mycli
7. Drop Console
bun build ./index.ts --outdir ./dist --production --drop=console
8. Code Splitting
bun build ./entry-a.ts ./entry-b.ts --outdir ./dist --splitting
9. JavaScript API
await Bun.build({ entrypoints: ["./app.ts"], outdir: "./dist", target: "bun" });
10. External Package
bun build ./index.ts --outdir ./dist --external zod
Visual
Build Target Selection
┌──────────────────────────────────────────────┐
│ Where will the bundle run? │
└──────────┬───────────────────────┬───────────┘
│ BROWSER │ SERVER
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ target: "browser" │ │ Bun or Node? │
│ (default) │ └──────────┬───────────┘
│ export: "browser" │ │
└──────────────────────┘ ┌────────┴────────┐
│ BUN │ NODE
▼ ▼
┌───────────────┐ ┌───────────────┐
│ target: "bun" │ │ target: "node"│
│ // @bun pragma│ │ export: "node"│
└───────────────┘ └───────────────┘
Compilation Outputs
┌──────────────────────────────────────────────┐
│ bun build ./app.ts --outdir ./dist │
│ → dist/app.js (transpiled bundle) │
└──────────────────┬───────────────────────────┘
│
│ --bytecode
▼
┌──────────────────────────────────────────────┐
│ dist/app.js (transpiled) │
│ dist/app.jsc (bytecode, faster startup) │
└──────────────────┬───────────────────────────┘
│
│ --compile
▼
┌──────────────────────────────────────────────┐
│ ./myapp (standalone executable) │
│ Contains Bun runtime + application code │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Bundler command | bun build <entrypoint> |
| JavaScript API | Bun.build({ ... }) |
| Default target | browser |
| Bun target | --target bun (adds // @bun) |
| Node target | --target node |
| Default format | esm |
| CommonJS format | --format cjs |
| Minification | --minify or --production |
| Bytecode | --bytecode (Bun target) |
| Single executable | --compile |
| Source maps | --sourcemap=linked |
| Drop statements | --drop=console |
Key takeaways:
- The target determines the runtime.
browser,bun, andnodeapply different module resolution rules and expose different globals. A bundle built for the wrong target fails at runtime . --productionis the production build flag. It enables all minification modes and setsNODE_ENV=production.--minifyenables the same modes without the environment variable .--bytecodeis the startup optimization. It pre-compiles to JavaScriptCore bytecode, moving parsing out of the critical path. The bytecode is version-specific and must be regenerated after Bun upgrades .--compileproduces a single-file executable. The binary contains the Bun runtime and the application. It runs without Bun installed .- Source maps and
--keep-namespreserve debuggability. A minified bundle without source maps produces unreadable error traces. The--sourcemap=linkedflag generates the map, and--keep-namespreserves function names .
Remember: bun build is the bundler built into the runtime. It reads TypeScript and JSX natively, resolves imports using Bun’s resolver, and produces output for a specified target. The target determines what the bundle can access. The format determines how it is loaded. The minification determines how small it is. The bytecode determines how fast it starts. The compilation determines whether it needs Bun installed. Each flag is a trade-off between size, speed, and debuggability.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!