Using Bun's Built-in Bundler: Configuration, Verification, and Known Limits
Bun's native bundler compiles JS/TS/JSX into one minified file with a single config. Here is a worked bunfig.toml setup, verification steps, and the limits that matter.
03 Apr 2026, 17:41 UTC

The problem and the short answer
You want to ship a JavaScript or TypeScript project as a single optimized file without wiring up webpack, esbuild, or Rollup. The short answer: Bun ships with a native bundler that handles JS, TS, JSX, TSX, and JSON modules directly. One config file and one command produce a minified, tree-shaken bundle you can execute with bun run.
This guide assumes Bun 1.x on Linux or macOS. Commands run in your project directory with normal user permissions; no elevated rights are needed.
How the bundler works
When you run bun build (or the bundling API), Bun parses the entry module, follows every static import, and builds a dependency graph. It then applies tree-shaking, which drops exported code nothing references, and dead-code elimination, which removes unreachable branches. Minification shortens identifiers and strips whitespace. The result is one JavaScript file written to your configured output path.
Because TypeScript and JSX are compiled natively, you do not need Babel or a separate tsc step before bundling. Because the whole graph is processed in one pass, the output is deterministic for a given configuration and source tree.
A worked configuration
Create a small project to see the mechanics end to end. Placeholder <project-root> means any directory you own.
mkdir <project-root> && cd <project-root>
mkdir src distAdd an entry file and a module it imports:
// src/utils.ts
export function greet(name: string): void {
console.log('Hello, ' + name);
}
export function unused(): void {
console.log('this should be tree-shaken');
}// src/index.ts
import { greet } from './utils';
const data = await Promise.resolve('World');
greet(data);Configure the bundler in bunfig.toml at the project root:
# bunfig.toml
[build]
entry = 'src/index.ts'
outdir = 'dist'
target = 'bun'
minify = true
sourcemap = 'external'Field meanings: entry is the module where graph traversal starts; outdir is where output lands; target selects the runtime (use bun when the bundle will run under Bun, node for Node.js); minify enables compression; sourcemap emits a separate map file for debugging original sources.
Build and run
- From
<project-root>, runbun build src/index.ts --outdir dist --minify --target bun, or rely on the config with the equivalent build invocation for your Bun version. Checkbun build --helpfirst, since CLI flag names have changed between releases. - Confirm output exists:
ls -l dist/. You should see a.jsfile and, if enabled, a.js.mapfile. - Execute it:
bun run dist/index.js. Expected check: it printsHello, Worldand exits with code 0, proving top-level await survived bundling. - Confirm tree-shaking:
grep -c tree-shaken dist/index.jsshould return 0, meaning the unused export was removed.
Risk note: the build overwrites files in outdir. Point it at a dedicated directory like dist, never at src.
Limits that bite in practice
- No asset loaders. CSS, images, and fonts imported from JS are not processed. Handle them with a separate step or a small wrapper script that copies assets alongside the bundle.
- Dynamic imports need static strings.
import('./utils')bundles fine.import('./plugins/' + name)cannot be analyzed, so that module will be missing at runtime and the import will throw. - No incremental builds. Every run recompiles the full graph. On large codebases this is noticeably slower than watch-mode bundlers; script your rebuilds accordingly.
- Config drift. bunfig.toml keys and CLI flags have shifted across Bun releases. Verify against
bun build --helpon your installed version rather than copying old examples.
Common mistakes
- Wrong
entrypath relative to the project root, producing an empty or partial bundle. Always run from the directory containing bunfig.toml. - Targeting
nodebut running the output with an older Node that lacks features Bun emitted. Matchtargetto the actual runtime. - Assuming a
requireof a JSON or asset file behaves like in Node. Test the bundled file, not just the source.
Verification checklist
ls -l dist/shows exactly one bundle file plus optional source map.bun run dist/index.jsexits 0 with expected output.- Bundle size is smaller than the sum of sources when minify is on: compare with
du -sh src dist. - If you hit a module-not-found at runtime, audit for dynamic imports with computed paths and convert them to static strings or an explicit lookup map.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.