# Bytecode Caching Source: https://bun.com/docs/bundler/bytecode Speed up JavaScript execution with bytecode caching in Bun's bundler Bytecode caching is a build-time optimization that dramatically improves application startup time by pre-compiling your JavaScript to bytecode. For example, when compiling TypeScript's `tsc` with bytecode enabled, startup time improves by **2x**. ## Usage ### Basic usage (CommonJS) Enable bytecode caching with the `--bytecode` flag. Without `--format`, this defaults to CommonJS: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.ts --target=bun --bytecode --outdir=./dist ``` This generates two files: * `dist/index.js` - Your bundled JavaScript (CommonJS) * `dist/index.jsc` - The bytecode cache file At runtime, Bun automatically detects and uses the `.jsc` file: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./dist/index.js # Automatically uses index.jsc ``` ### With standalone executables When creating executables with `--compile`, bytecode is embedded into the binary. Both ESM and CommonJS formats are supported: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # ESM (requires --compile) bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli # CommonJS (works with or without --compile) bun build ./cli.ts --compile --bytecode --outfile=mycli ``` The resulting executable contains both the code and bytecode, giving you maximum performance in a single file. ### ESM bytecode ESM bytecode requires `--compile` because Bun embeds module metadata (import/export information) in the compiled binary. This metadata allows the JavaScript engine to skip parsing entirely at runtime. Without `--compile`, ESM bytecode would still require parsing the source to analyze module dependencies—defeating the purpose of bytecode caching. ### Combining with other optimizations Bytecode works great with minification and source maps: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --bytecode --minify --sourcemap ./cli.ts --outfile=mycli ``` * `--minify` reduces code size before generating bytecode (less code -> less bytecode) * `--sourcemap` preserves error reporting (errors still point to original source) * `--bytecode` eliminates parsing overhead ## Performance impact The performance improvement scales with your codebase size: | Application size | Typical startup improvement | | ------------------------- | --------------------------- | | Small CLI (\< 100 KB) | 1.5-2x faster | | Medium-large app (> 5 MB) | 2.5x-4x faster | Larger applications benefit more because they have more code to parse. ## When to use bytecode ### Great for: #### CLI tools * Invoked frequently (linters, formatters, git hooks) * Startup time is the entire user experience * Users notice the difference between 90ms and 45ms startup * Example: TypeScript compiler, Prettier, ESLint #### Build tools and task runners * Run hundreds or thousands of times during development * Milliseconds saved per run compound quickly * Developer experience improvement * Example: Build scripts, test runners, code generators #### Standalone executables * Distributed to users who care about snappy performance * Single-file distribution is convenient * File size less important than startup time * Example: CLIs distributed via npm or as binaries ### Skip it for: * ❌ **Small scripts** * ❌ **Code that runs once** * ❌ **Development builds** * ❌ **Size-constrained environments** ## Limitations ### Version compatibility Bytecode is **not portable across Bun versions**. The bytecode format is tied to JavaScriptCore's internal representation, which changes between versions. When you update Bun, you must regenerate bytecode: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # After updating Bun bun build --bytecode ./index.ts --outdir=./dist ``` If bytecode doesn't match the current Bun version, it's automatically ignored and your code falls back to parsing the JavaScript source. Your app still runs — the performance optimization is skipped. **Best practice**: Generate bytecode as part of your CI/CD build process. Don't commit `.jsc` files to git. Regenerate them whenever you update Bun. ### Source code still required * The `.js` file (your bundled source code) * The `.jsc` file (the bytecode cache) At runtime: 1. Bun loads the `.js` file, sees a `@bytecode` pragma, and checks the `.jsc` file 2. Bun loads the `.jsc` file 3. Bun validates the bytecode hash matches the source 4. If valid, Bun uses the bytecode 5. If invalid, Bun falls back to parsing the source ### Bytecode is not obfuscation Bytecode **does not obscure your source code**. It's an optimization, not a security measure. ## Production deployment ### Docker Include bytecode generation in your Dockerfile: ```dockerfile Dockerfile icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}} FROM oven/bun:1 AS builder WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile COPY . . RUN bun build --bytecode --minify --sourcemap \ --target=bun \ --outdir=./dist \ --compile \ ./src/server.ts --outfile=./dist/server FROM oven/bun:1 AS runner WORKDIR /app COPY --from=builder /dist/server /app/server CMD ["./server"] ``` The bytecode is architecture-independent. ### CI/CD Generate bytecode during your build pipeline: ```yaml workflow.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} # GitHub Actions - name: Build with bytecode run: | bun install bun build --bytecode --minify \ --outdir=./dist \ --target=bun \ ./src/index.ts ``` ## Debugging ### Verify bytecode is being used Check that the `.jsc` file exists: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} ls -lh dist/ ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} -rw-r--r-- 1 user staff 245K index.js -rw-r--r-- 1 user staff 1.1M index.jsc ``` The `.jsc` file should be 2-8x larger than the `.js` file. To log if bytecode is being used, set `BUN_JSC_verboseDiskCache=1` in your environment. On success, it will log something like: ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} [Disk cache] cache hit for sourceCode ``` If you see a cache miss, it will log something like: ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} [Disk cache] cache miss for sourceCode ``` It's normal for it it to log a cache miss multiple times since Bun doesn't currently bytecode cache JavaScript code used in builtin modules. ### Common issues **Bytecode silently ignored**: Usually caused by a Bun version update. The cache version doesn't match, so bytecode is rejected. Regenerate to fix. **File size too large**: This is expected. Consider: * Using `--minify` to reduce code size before bytecode generation * Compressing `.jsc` files for network transfer (gzip/brotli) * Evaluating if the startup performance gain is worth the size increase ## What is bytecode? When you run JavaScript, the JavaScript engine doesn't execute your source code directly. Instead, it goes through several steps: 1. **Parsing**: The engine reads your JavaScript source code and converts it into an Abstract Syntax Tree (AST) 2. **Bytecode compilation**: The AST is compiled into bytecode - a lower-level representation that's faster to execute 3. **Execution**: The bytecode is executed by the engine's interpreter or JIT compiler Bytecode is an intermediate representation - it's lower-level than JavaScript source code, but higher-level than machine code. Think of it as assembly language for a virtual machine. Each bytecode instruction represents a single operation like "load this variable," "add two numbers," or "call this function." This happens **every single time** you run your code. If you have a CLI tool that runs 100 times a day, your code gets parsed 100 times. If you have a serverless function with frequent cold starts, parsing happens on every cold start. With bytecode caching, Bun moves steps 1 and 2 to the build step. At runtime, the engine loads the pre-compiled bytecode and jumps straight to execution. ### Why lazy parsing makes this even better Modern JavaScript engines use a clever optimization called **lazy parsing**. They don't parse all your code upfront - instead, functions are only parsed when they're first called: ```js theme={"theme":{"light":"github-light","dark":"dracula"}} // Without bytecode caching: function rarely_used() { // This 500-line function is only parsed // when it's actually called } function main() { console.log("Starting app"); // rarely_used() is never called, so it's never parsed } ``` This means parsing overhead isn't just a startup cost - it happens throughout your application's lifetime as different code paths execute. With bytecode caching, **all functions are pre-compiled**, even the ones that are lazily parsed. The parsing work happens once at build time instead of being distributed throughout your application's execution. ## The bytecode format ### Inside a .jsc file A `.jsc` file contains a serialized bytecode structure. Understanding what's inside helps explain both the performance benefits and the file size tradeoff. **Header section** (validated on every load): * **Cache version**: A hash tied to the JavaScriptCore framework version. This ensures bytecode generated with one version of Bun only runs with that exact version. * **Code block type tag**: Identifies whether this is a Program, Module, Eval, or Function code block. **SourceCodeKey** (validates bytecode matches source): * **Source code hash**: A hash of the original JavaScript source code. Bun verifies this matches before using the bytecode. * **Source code length**: The exact length of the source, for additional validation. * **Compilation flags**: Critical compilation context like strict mode, whether it's a script vs module, eval context type, etc. The same source code compiled with different flags produces different bytecode. **Bytecode instructions**: * **Instruction stream**: The actual bytecode opcodes - the compiled representation of your JavaScript. This is a variable-length sequence of bytecode instructions. * **Metadata table**: Each opcode has associated metadata - things like profiling counters, type hints, and execution counts (even if not yet populated). * **Jump targets**: Pre-computed addresses for control flow (if/else, loops, switch statements). * **Switch tables**: Optimized lookup tables for switch statements. **Constants and identifiers**: * **Constant pool**: All literal values in your code - numbers, strings, booleans, null, undefined. These are stored as actual JavaScript values (JSValues) so they don't need to be parsed from source at runtime. * **Identifier table**: All variable and function names used in the code. Stored as deduplicated strings. * **Source code representation markers**: Flags indicating how constants should be represented (as integers, doubles, big ints, etc.). **Function metadata** (for each function in your code): * **Register allocation**: How many registers (local variables) the function needs - `thisRegister`, `scopeRegister`, `numVars`, `numCalleeLocals`, `numParameters`. * **Code features**: A bitmask of function characteristics: is it a constructor? an arrow function? does it use `super`? does it have tail calls? These affect how the function is executed. * **Lexically scoped features**: Strict mode and other lexical context. * **Parse mode**: The mode in which the function was parsed (normal, async, generator, async generator). **Nested structures**: * **Function declarations and expressions**: Each nested function gets its own bytecode block, recursively. A file with 100 functions has 100 separate bytecode blocks, all nested in the structure. * **Exception handlers**: Try/catch/finally blocks with their boundaries and handler addresses pre-computed. * **Expression info**: Maps bytecode positions back to source code locations for error reporting and debugging. ### What bytecode does NOT contain Importantly, **bytecode does not embed your source code**. Instead: * The JavaScript source is stored separately (in the `.js` file) * The bytecode only stores a hash and length of the source * At load time, Bun validates the bytecode matches the current source code This is why you need to deploy both the `.js` and `.jsc` files. The `.jsc` file is useless without its corresponding `.js` file. ## The tradeoff: file size Bytecode files are significantly larger than source code - typically 2-8x larger. ### Why is bytecode so much larger? **Bytecode instructions are verbose**: A single line of minified JavaScript might compile to dozens of bytecode instructions. For example: ```js theme={"theme":{"light":"github-light","dark":"dracula"}} const sum = arr.reduce((a, b) => a + b, 0); ``` Compiles to bytecode that: * Loads the `arr` variable * Gets the `reduce` property * Creates the arrow function (which itself has bytecode) * Loads the initial value `0` * Sets up the call with the right number of arguments * Actually performs the call * Stores the result in `sum` Each of these steps is a separate bytecode instruction with its own metadata. **Constant pools store everything**: Every string literal, number, property name - everything gets stored in the constant pool. Even if your source code has `"hello"` a hundred times, the constant pool stores it once, but the identifier table and constant references add overhead. **Per-function metadata**: Each function - even small one-line functions - gets its own complete metadata: * Register allocation info * Code features bitmask * Parse mode * Exception handlers * Expression info for debugging A file with 1,000 small functions has 1,000 sets of metadata. **Profiling data structures**: Even though profiling data isn't populated yet, the *structures* to hold profiling data are allocated. This includes: * Value profile slots (tracking what types flow through each operation) * Array profile slots (tracking array access patterns) * Binary arithmetic profile slots (tracking number types in math operations) * Unary arithmetic profile slots These take up space even when empty. **Pre-computed control flow**: Jump targets, switch tables, and exception handler boundaries are all pre-computed and stored. This makes execution faster but increases file size. ### Mitigation strategies **Compression**: Bytecode compresses extremely well with gzip/brotli (60-70% compression). The repetitive structure and metadata compress efficiently. **Minification first**: Using `--minify` before bytecode generation helps: * Shorter identifiers → smaller identifier table * Dead code elimination → less bytecode generated * Constant folding → fewer constants in the pool **The tradeoff**: You're trading 2-4x larger files for 2-4x faster startup. For CLIs, this is usually worth it. For long-running servers where a few megabytes of disk space don't matter, it's even less of an issue. ## Versioning and portability ### Cross-architecture portability: ✅ Bytecode is **architecture-independent**. You can: * Build on macOS ARM64, deploy to Linux x64 * Build on Linux x64, deploy to AWS Lambda ARM64 * Build on Windows x64, deploy to macOS ARM64 The bytecode contains abstract instructions that work on any architecture. Architecture-specific optimizations happen during JIT compilation at runtime, not in the cached bytecode. ### Cross-version portability: ❌ Bytecode is **not stable across Bun versions**. Here's why: **Bytecode format changes**: JavaScriptCore's bytecode format evolves. New opcodes get added, old ones get removed or changed, metadata structures change. Each version of JavaScriptCore has a different bytecode format. **Version validation**: The cache version in the `.jsc` file header is a hash of the JavaScriptCore framework. When Bun loads bytecode: 1. It extracts the cache version from the `.jsc` file 2. It computes the current JavaScriptCore version 3. If they don't match, the bytecode is **silently rejected** 4. Bun falls back to parsing the `.js` source code Your application still runs — the performance optimization is skipped. **Graceful degradation**: This design means bytecode caching "fails open" - if anything goes wrong (version mismatch, corrupted file, missing file), your code still runs normally. You might see slower startup, but you won't see errors. ## Unlinked vs. linked bytecode JavaScriptCore makes a crucial distinction between "unlinked" and "linked" bytecode. This separation is what makes bytecode caching possible: ### Unlinked bytecode (what's cached) The bytecode saved in `.jsc` files is **unlinked bytecode**. It contains: * The compiled bytecode instructions * Structural information about the code * Constants and identifiers * Control flow information But it **doesn't** contain: * Pointers to actual runtime objects * JIT-compiled machine code * Profiling data from previous runs * Call link information (which functions call which) Unlinked bytecode is **immutable and shareable**. Multiple executions of the same code can all reference the same unlinked bytecode. ### Linked bytecode (runtime execution) When Bun runs bytecode, it "links" it - creating a runtime wrapper that adds: * **Call link information**: As your code runs, the engine learns which functions call which and optimizes those call sites. * **Profiling data**: The engine tracks how many times each instruction executes, what types of values flow through the code, array access patterns, etc. * **JIT compilation state**: References to baseline JIT or optimizing JIT (DFG/FTL) compiled versions of hot code. * **Runtime objects**: Pointers to actual JavaScript objects, prototypes, scopes, etc. This linked representation is created fresh every time you run your code. This allows: 1. **Caching the expensive work** (parsing and compilation to unlinked bytecode) 2. **Still collecting runtime profiling data** to guide optimizations 3. **Still applying JIT optimizations** based on actual execution patterns Bytecode caching moves expensive work (parsing and compiling to bytecode) from runtime to build time. For applications that start frequently, this can halve your startup time at the cost of larger files on disk. For production CLIs and serverless deployments, the combination of `--bytecode --minify --sourcemap` gives you the best performance while maintaining debuggability. # CSS Source: https://bun.com/docs/bundler/css Bun's bundler has built-in support for CSS with modern features Bun's bundler has built-in support for CSS with the following features: * Transpiling modern/future features to work on all browsers (including vendor prefixing) * Minification * CSS Modules * Tailwind (via a native bundler plugin) ## Transpiling Bun's CSS bundler lets you use modern/future CSS features without having to worry about browser compatibility — all thanks to its transpiling and vendor prefixing features which are enabled by default. Bun's CSS parser and bundler is a direct Rust → Zig port of LightningCSS, with a bundling approach inspired by esbuild. The transpiler converts modern CSS syntax into backwards-compatible equivalents that work across browsers. A huge thanks goes to the amazing work from the authors of LightningCSS and esbuild. ## Browser Compatibility By default, Bun's CSS bundler targets the following browsers: * ES2020 * Edge 88+ * Firefox 78+ * Chrome 87+ * Safari 14+ ## Syntax Lowering ### Nesting The CSS Nesting specification allows you to write more concise and intuitive stylesheets by nesting selectors inside one another. Instead of repeating parent selectors across your CSS file, you can write child styles directly within their parent blocks. ```scss title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* With nesting */ .card { background: white; border-radius: 4px; .title { font-size: 1.2rem; font-weight: bold; } .content { padding: 1rem; } } ``` Bun's CSS bundler automatically converts this nested syntax into traditional flat CSS that works in all browsers: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Compiled output */ .card { background: white; border-radius: 4px; } .card .title { font-size: 1.2rem; font-weight: bold; } .card .content { padding: 1rem; } ``` You can also nest media queries and other at-rules inside selectors, eliminating the need to repeat selector patterns: ```scss title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .responsive-element { display: block; @media (min-width: 768px) { display: flex; } } ``` This compiles to: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .responsive-element { display: block; } @media (min-width: 768px) { .responsive-element { display: flex; } } ``` ### Color mix The `color-mix()` function blends two colors together according to a specified ratio in a chosen color space. Use it to create color variations without manually calculating the resulting values. ```scss title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { /* Mix blue and red in the RGB color space with a 30/70 proportion */ background-color: color-mix(in srgb, blue 30%, red); /* Create a lighter variant for hover state */ &:hover { background-color: color-mix(in srgb, blue 30%, red, white 20%); } } ``` Bun's CSS bundler evaluates these color mixes at build time when all color values are known (not CSS variables), generating static color values that work in all browsers: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { /* Computed to the exact resulting color */ background-color: #b31a1a; } .button:hover { background-color: #c54747; } ``` This feature is particularly useful for creating color systems with programmatically derived shades, tints, and accents without needing preprocessors or custom tooling. ### Relative colors CSS allows you to modify individual components of a color using relative color syntax. Create color variations by adjusting specific attributes like lightness, saturation, or individual channels without having to recalculate the entire color. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .theme-color { /* Start with a base color and increase lightness by 15% */ --accent: lch(from purple calc(l + 15%) c h); /* Take our brand blue and make a desaturated version */ --subtle-blue: oklch(from var(--brand-blue) l calc(c * 0.8) h); } ``` Bun's CSS bundler computes these relative color modifications at build time (when not using CSS variables) and generates static color values for browser compatibility: ```css theme={"theme":{"light":"github-light","dark":"dracula"}} .theme-color { --accent: lch(69.32% 58.34 328.37); --subtle-blue: oklch(60.92% 0.112 240.01); } ``` This approach is extremely useful for theme generation, creating accessible color variants, or building color scales based on mathematical relationships instead of hard-coding each value. ### LAB colors Modern CSS supports perceptually uniform color spaces like LAB, LCH, OKLAB, and OKLCH that offer significant advantages over traditional RGB. These color spaces can represent colors outside the standard RGB gamut, resulting in more vibrant and visually consistent designs. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .vibrant-element { /* A vibrant red that exceeds sRGB gamut boundaries */ color: lab(55% 78 35); /* A smooth gradient using perceptual color space */ background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg)); } ``` Bun's CSS bundler automatically converts these advanced color formats to backwards-compatible alternatives for browsers that don't yet support them: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .vibrant-element { /* Fallback to closest RGB approximation */ color: #ff0f52; /* P3 fallback for browsers with wider gamut support */ color: color(display-p3 1 0.12 0.37); /* Original value preserved for browsers that support it */ color: lab(55% 78 35); background: linear-gradient(to right, #cd4e15, #3887ab); background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg)); } ``` This layered approach ensures optimal color rendering across all browsers while allowing you to use the latest color technologies in your designs. ### Color function The `color()` function provides a standardized way to specify colors in various predefined color spaces, expanding your design options beyond the traditional RGB space. This allows you to access wider color gamuts and create more vibrant designs. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .vivid-element { /* Using the Display P3 color space for wider gamut colors */ color: color(display-p3 1 0.1 0.3); /* Using A98 RGB color space */ background-color: color(a98-rgb 0.44 0.5 0.37); } ``` For browsers that don't support these advanced color functions yet, Bun's CSS bundler provides appropriate RGB fallbacks: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .vivid-element { /* RGB fallback first for maximum compatibility */ color: #fa1a4c; /* Keep original for browsers that support it */ color: color(display-p3 1 0.1 0.3); background-color: #6a805d; background-color: color(a98-rgb 0.44 0.5 0.37); } ``` This functionality lets you use modern color spaces immediately while ensuring your designs remain functional across all browsers, with optimal colors displayed in supporting browsers and reasonable approximations elsewhere. ### HWB colors The HWB (Hue, Whiteness, Blackness) color model expresses colors based on how much white or black is mixed with a pure hue. Many designers find this approach more natural for creating color variations compared to manipulating RGB or HSL values. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .easy-theming { /* Pure cyan with no white or black added */ --primary: hwb(180 0% 0%); /* Same hue, but with 20% white added (tint) */ --primary-light: hwb(180 20% 0%); /* Same hue, but with 30% black added (shade) */ --primary-dark: hwb(180 0% 30%); /* Muted version with both white and black added */ --primary-muted: hwb(180 30% 20%); } ``` Bun's CSS bundler automatically converts HWB colors to RGB for compatibility with all browsers: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .easy-theming { --primary: #00ffff; --primary-light: #33ffff; --primary-dark: #00b3b3; --primary-muted: #339999; } ``` The HWB model is well-suited for creating systematic color variations for design systems, providing a more direct approach to creating consistent tints and shades than working with RGB or HSL values. ### Color notation Modern CSS has introduced more concise ways to express colors. Space-separated color syntax eliminates the need for commas in RGB and HSL values, while hex colors with alpha channels provide a compact way to specify transparency. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .modern-styling { /* Space-separated RGB notation (no commas) */ color: rgb(50 100 200); /* Space-separated RGB with alpha */ border-color: rgba(100 50 200 / 75%); /* Hex with alpha channel (8 digits) */ background-color: #00aaff80; /* HSL with simplified notation */ box-shadow: 0 5px 10px hsl(200 50% 30% / 40%); } ``` Bun's CSS bundler automatically converts these modern color formats to ensure compatibility with older browsers: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .modern-styling { /* Converted to comma format for older browsers */ color: rgb(50, 100, 200); /* Alpha channels handled appropriately */ border-color: rgba(100, 50, 200, 0.75); /* Hex+alpha converted to rgba when needed */ background-color: rgba(0, 170, 255, 0.5); box-shadow: 0 5px 10px rgba(38, 115, 153, 0.4); } ``` This conversion process lets you write cleaner, more modern CSS while ensuring your styles work correctly across all browsers. ### light-dark() color function The `light-dark()` function implements color schemes that respect the user's system preference without requiring media queries. This function accepts two color values and automatically selects the appropriate one based on the current color scheme context. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} :root { /* Define color scheme support */ color-scheme: light dark; } .themed-component { /* Automatically picks the right color based on system preference */ background-color: light-dark(#ffffff, #121212); color: light-dark(#333333, #eeeeee); border-color: light-dark(#dddddd, #555555); } /* Override system preference when needed */ .light-theme { color-scheme: light; } .dark-theme { color-scheme: dark; } ``` For browsers that don't support this feature yet, Bun's CSS bundler converts it to use CSS variables with proper fallbacks: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} :root { --lightningcss-light: initial; --lightningcss-dark: ; color-scheme: light dark; } @media (prefers-color-scheme: dark) { :root { --lightningcss-light: ; --lightningcss-dark: initial; } } .light-theme { --lightningcss-light: initial; --lightningcss-dark: ; color-scheme: light; } .dark-theme { --lightningcss-light: ; --lightningcss-dark: initial; color-scheme: dark; } .themed-component { background-color: var(--lightningcss-light, #ffffff) var(--lightningcss-dark, #121212); color: var(--lightningcss-light, #333333) var(--lightningcss-dark, #eeeeee); border-color: var(--lightningcss-light, #dddddd) var(--lightningcss-dark, #555555); } ``` This approach gives you a clean way to handle light and dark themes without duplicating styles or writing complex media queries, while maintaining compatibility with browsers that don't yet support the feature natively. ### Logical properties CSS logical properties let you define layout, spacing, and sizing relative to the document's writing mode and text direction rather than physical screen directions. This is crucial for creating truly international layouts that automatically adapt to different writing systems. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .multilingual-component { /* Margin that adapts to writing direction */ margin-inline-start: 1rem; /* Padding that makes sense regardless of text direction */ padding-block: 1rem 2rem; /* Border radius for the starting corner at the top */ border-start-start-radius: 4px; /* Size that respects the writing mode */ inline-size: 80%; block-size: auto; } ``` For browsers that don't fully support logical properties, Bun's CSS bundler compiles them to physical properties with appropriate directional adjustments: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* For left-to-right languages */ .multilingual-component:dir(ltr) { margin-left: 1rem; padding-top: 1rem; padding-bottom: 2rem; border-top-left-radius: 4px; width: 80%; height: auto; } /* For right-to-left languages */ .multilingual-component:dir(rtl) { margin-right: 1rem; padding-top: 1rem; padding-bottom: 2rem; border-top-right-radius: 4px; width: 80%; height: auto; } ``` If the `:dir()` selector isn't supported, additional fallbacks are automatically generated to ensure your layouts work properly across all browsers and writing systems. This makes creating internationalized designs much simpler while maintaining compatibility with older browsers. ### :dir() selector The `:dir()` pseudo-class selector allows you to style elements based on their text direction (RTL or LTR), letting you create direction-aware designs without JavaScript. This selector matches elements based on their directionality as determined by the document or explicit direction attributes. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Apply different styles based on text direction */ .nav-arrow:dir(ltr) { transform: rotate(0deg); } .nav-arrow:dir(rtl) { transform: rotate(180deg); } /* Position elements based on text flow */ .sidebar:dir(ltr) { border-right: 1px solid #ddd; } .sidebar:dir(rtl) { border-left: 1px solid #ddd; } ``` For browsers that don't support the `:dir()` selector yet, Bun's CSS bundler converts it to the more widely supported `:lang()` selector with appropriate language mappings: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Converted to use language-based selectors as fallback */ .nav-arrow:lang(en, fr, de, es, it, pt, nl) { transform: rotate(0deg); } .nav-arrow:lang(ar, he, fa, ur) { transform: rotate(180deg); } .sidebar:lang(en, fr, de, es, it, pt, nl) { border-right: 1px solid #ddd; } .sidebar:lang(ar, he, fa, ur) { border-left: 1px solid #ddd; } ``` This conversion lets you write direction-aware CSS that works reliably across browsers, even those that don't yet support the `:dir()` selector natively. If multiple arguments to `:lang()` aren't supported, further fallbacks are automatically provided. ### :lang() selector The `:lang()` pseudo-class selector targets elements based on the language they're in, enabling language-specific styling. Modern CSS supports passing multiple language codes to `:lang()`, letting you group language-specific rules more efficiently. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Typography adjustments for CJK languages */ :lang(zh, ja, ko) { line-height: 1.8; font-size: 1.05em; } /* Different quote styles by language group */ blockquote:lang(fr, it, es, pt) { font-style: italic; } blockquote:lang(de, nl, da, sv) { font-weight: 500; } ``` For browsers that don't support multiple arguments in the `:lang()` selector, Bun's CSS bundler converts this syntax to use the `:is()` selector to maintain the same behavior: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Multiple languages grouped with :is() for better browser support */ :is(:lang(zh), :lang(ja), :lang(ko)) { line-height: 1.8; font-size: 1.05em; } blockquote:is(:lang(fr), :lang(it), :lang(es), :lang(pt)) { font-style: italic; } blockquote:is(:lang(de), :lang(nl), :lang(da), :lang(sv)) { font-weight: 500; } ``` If needed, Bun can provide additional fallbacks for `:is()` as well, ensuring your language-specific styles work across all browsers. This approach simplifies creating internationalized designs with distinct typographic and styling rules for different language groups. ### :is() selector The `:is()` pseudo-class function (formerly `:matches()`) allows you to create more concise and readable selectors by grouping multiple selectors together. It accepts a selector list as its argument and matches if any of the selectors in that list match, significantly reducing repetition in your CSS. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Instead of writing these separately */ /* .article h1, .article h2, .article h3 { margin-top: 1.5em; } */ /* You can write this */ .article :is(h1, h2, h3) { margin-top: 1.5em; } /* Complex example with multiple groups */ :is(header, main, footer) :is(h1, h2, .title) { font-family: "Heading Font", sans-serif; } ``` For browsers that don't support `:is()`, Bun's CSS bundler provides fallbacks using vendor-prefixed alternatives: ```css theme={"theme":{"light":"github-light","dark":"dracula"}} /* Fallback using -webkit-any */ .article :-webkit-any(h1, h2, h3) { margin-top: 1.5em; } /* Fallback using -moz-any */ .article :-moz-any(h1, h2, h3) { margin-top: 1.5em; } /* Original preserved for modern browsers */ .article :is(h1, h2, h3) { margin-top: 1.5em; } /* Complex example with fallbacks */ :-webkit-any(header, main, footer) :-webkit-any(h1, h2, .title) { font-family: "Heading Font", sans-serif; } :-moz-any(header, main, footer) :-moz-any(h1, h2, .title) { font-family: "Heading Font", sans-serif; } :is(header, main, footer) :is(h1, h2, .title) { font-family: "Heading Font", sans-serif; } ``` The vendor-prefixed versions have some limitations compared to the standardized `:is()` selector, particularly with complex selectors. Bun handles these limitations intelligently, only using prefixed versions when they'll work correctly. ### :not() selector The `:not()` pseudo-class allows you to exclude elements that match a specific selector. The modern version of this selector accepts multiple arguments, letting you exclude multiple patterns with a single, concise selector. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Select all buttons except primary and secondary variants */ button:not(.primary, .secondary) { background-color: #f5f5f5; border: 1px solid #ddd; } /* Apply styles to all headings except those inside sidebars or footers */ h2:not(.sidebar *, footer *) { margin-top: 2em; } ``` For browsers that don't support multiple arguments in `:not()`, Bun's CSS bundler converts this syntax to a more compatible form while preserving the same behavior: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Converted to use :not with :is() for compatibility */ button:not(:is(.primary, .secondary)) { background-color: #f5f5f5; border: 1px solid #ddd; } h2:not(:is(.sidebar *, footer *)) { margin-top: 2em; } ``` And if `:is()` isn't supported, Bun can generate further fallbacks: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Even more fallbacks for maximum compatibility */ button:not(:-webkit-any(.primary, .secondary)) { background-color: #f5f5f5; border: 1px solid #ddd; } button:not(:-moz-any(.primary, .secondary)) { background-color: #f5f5f5; border: 1px solid #ddd; } button:not(:is(.primary, .secondary)) { background-color: #f5f5f5; border: 1px solid #ddd; } ``` This conversion ensures your negative selectors work correctly across all browsers while maintaining the correct specificity and behavior of the original selector. ### Math functions CSS now includes a rich set of mathematical functions that let you perform complex calculations directly in your stylesheets. These include standard math functions (`round()`, `mod()`, `rem()`, `abs()`, `sign()`), trigonometric functions (`sin()`, `cos()`, `tan()`, `asin()`, `acos()`, `atan()`, `atan2()`), and exponential functions (`pow()`, `sqrt()`, `exp()`, `log()`, `hypot()`). ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .dynamic-sizing { /* Clamp a value between minimum and maximum */ width: clamp(200px, 50%, 800px); /* Round to the nearest multiple */ padding: round(14.8px, 5px); /* Trigonometry for animations or layouts */ transform: rotate(calc(sin(45deg) * 50deg)); /* Complex math with multiple functions */ --scale-factor: pow(1.25, 3); font-size: calc(16px * var(--scale-factor)); } ``` Bun's CSS bundler evaluates these mathematical expressions at build time when all values are known constants (not variables), resulting in optimized output: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .dynamic-sizing { width: clamp(200px, 50%, 800px); padding: 15px; transform: rotate(35.36deg); --scale-factor: 1.953125; font-size: calc(16px * var(--scale-factor)); } ``` This approach lets you write more expressive and maintainable CSS with meaningful mathematical relationships, which then gets compiled to optimized values for maximum browser compatibility and performance. ### Media query ranges Modern CSS supports range syntax for media queries, allowing you to specify breakpoints using comparison operators like `<`, `>`, `<=`, and `>=` instead of the more verbose `min-` and `max-` prefixes. This syntax is more readable and matches how you typically think about values and ranges. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Modern syntax with comparison operators */ @media (width >= 768px) { .container { max-width: 720px; } } /* Inclusive range using <= and >= */ @media (768px <= width <= 1199px) { .sidebar { display: flex; } } /* Exclusive range using < and > */ @media (width > 320px) and (width < 768px) { .mobile-only { display: block; } } ``` Bun's CSS bundler converts these modern range queries to traditional media query syntax for compatibility with all browsers: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Converted to traditional min/max syntax */ @media (min-width: 768px) { .container { max-width: 720px; } } @media (min-width: 768px) and (max-width: 1199px) { .sidebar { display: flex; } } @media (min-width: 321px) and (max-width: 767px) { .mobile-only { display: block; } } ``` This lets you write more readable media queries while ensuring your stylesheets work correctly across all browsers, including those that don't support the modern range syntax. ### Shorthands CSS has introduced several modern shorthand properties that improve code readability and maintainability. Bun's CSS bundler ensures these convenient shorthands work on all browsers by converting them to their longhand equivalents when needed. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* Alignment shorthands */ .flex-container { /* Shorthand for align-items and justify-items */ place-items: center start; /* Shorthand for align-content and justify-content */ place-content: space-between center; } .grid-item { /* Shorthand for align-self and justify-self */ place-self: end center; } /* Two-value overflow */ .content-box { /* First value for horizontal, second for vertical */ overflow: hidden auto; } /* Enhanced text-decoration */ .fancy-link { /* Combines multiple text decoration properties */ text-decoration: underline dotted blue 2px; } /* Two-value display syntax */ .component { /* Outer display type + inner display type */ display: inline flex; } ``` For browsers that don't support these modern shorthands, Bun converts them to their component longhand properties: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .flex-container { /* Expanded alignment properties */ align-items: center; justify-items: start; align-content: space-between; justify-content: center; } .grid-item { align-self: end; justify-self: center; } .content-box { /* Separate overflow properties */ overflow-x: hidden; overflow-y: auto; } .fancy-link { /* Individual text decoration properties */ text-decoration-line: underline; text-decoration-style: dotted; text-decoration-color: blue; text-decoration-thickness: 2px; } .component { /* Single value display */ display: inline-flex; } ``` This conversion ensures your stylesheets remain clean and maintainable while providing the broadest possible browser compatibility. ### Double position gradients The double position gradient syntax is a modern CSS feature that allows you to create hard color stops in gradients by specifying the same color at two adjacent positions. This creates a sharp transition rather than a smooth fade, which is useful for creating stripes, color bands, and other multi-color designs. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .striped-background { /* Creates a sharp transition from green to red at 30%-40% */ background: linear-gradient( to right, yellow 0%, green 20%, green 30%, red 30%, /* Double position creates hard stop */ red 70%, blue 70%, blue 100% ); } .progress-bar { /* Creates distinct color sections */ background: linear-gradient( to right, #4caf50 0% 25%, /* Green from 0% to 25% */ #ffc107 25% 50%, /* Yellow from 25% to 50% */ #2196f3 50% 75%, /* Blue from 50% to 75% */ #9c27b0 75% 100% /* Purple from 75% to 100% */ ); } ``` For browsers that don't support this syntax, Bun's CSS bundler automatically converts it to the traditional format by duplicating color stops: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .striped-background { background: linear-gradient( to right, yellow 0%, green 20%, green 30%, red 30%, /* Split into two color stops */ red 70%, blue 70%, blue 100% ); } .progress-bar { background: linear-gradient( to right, #4caf50 0%, #4caf50 25%, /* Two stops for green section */ #ffc107 25%, #ffc107 50%, /* Two stops for yellow section */ #2196f3 50%, #2196f3 75%, /* Two stops for blue section */ #9c27b0 75%, #9c27b0 100% /* Two stops for purple section */ ); } ``` This conversion lets you use the cleaner double position syntax in your source code while ensuring gradients display correctly in all browsers. ### system-ui font The `system-ui` generic font family lets you use the device's native UI font, creating interfaces that feel more integrated with the operating system. This provides a more native look and feel without having to specify different font stacks for each platform. ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .native-interface { /* Use the system's default UI font */ font-family: system-ui; } .fallback-aware { /* System UI font with explicit fallbacks */ font-family: system-ui, sans-serif; } ``` For browsers that don't support `system-ui`, Bun's CSS bundler automatically expands it to a comprehensive cross-platform font stack: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .native-interface { /* Expanded to support all major platforms */ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue"; } .fallback-aware { /* Preserves the original fallback after the expanded stack */ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } ``` This approach gives you the simplicity of writing just `system-ui` in your source code while ensuring your interface adapts correctly to all operating systems and browsers. The expanded font stack includes appropriate system fonts for macOS/iOS, Windows, Android, Linux, and fallbacks for older browsers. ## CSS Modules Bun's bundler also supports bundling CSS modules in addition to regular CSS with support for the following features: * Automatically detecting CSS module files (`.module.css`) with zero configuration * Composition (`composes` property) * Importing CSS modules into JSX/TSX * Warnings/errors for invalid usages of CSS modules A CSS module is a CSS file (with the `.module.css` extension) where are all class names and animations are scoped to the file. This helps you avoid class name collisions as CSS declarations are globally scoped by default. Under the hood, Bun's bundler transforms locally scoped class names into unique identifiers. ### Getting started Create a CSS file with the `.module.css` extension: ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { color: red; } ``` ```css title="other-styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { color: blue; } ``` You can then import this file, for example into a TSX file: ```tsx title="app.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import styles from "./styles.module.css"; import otherStyles from "./other-styles.module.css"; export default function App() { return ( <> ); } ``` The styles object from importing the CSS module file will be an object with all class names as keys and their unique identifiers as values: ```ts title="app.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import styles from "./styles.module.css"; import otherStyles from "./other-styles.module.css"; console.log(styles); console.log(otherStyles); ``` This will output: ```ts title="app.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} { button: "button_123"; } { button: "button_456"; } ``` As you can see, the class names are unique to each file, avoiding any collisions! ### Composition CSS modules allow you to compose class selectors together. This lets you reuse style rules across multiple classes. For example: ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { composes: background; color: red; } .background { background-color: blue; } ``` Would be the same as writing: ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { background-color: blue; color: red; } .background { background-color: blue; } ``` There are a couple rules to keep in mind when using `composes`: **Composition Rules:** - A `composes` property must come before any regular CSS properties or declarations - You can only use `composes` on a simple selector with a single class name ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} #button { /* Invalid! `#button` is not a class selector */ composes: background; } .button, .button-secondary { /* Invalid! `.button, .button-secondary` is not a simple selector */ composes: background; } ``` ### Composing from a separate CSS module file You can also compose from a separate CSS module file: ```css title="background.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .background { background-color: blue; } ``` ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { composes: background from "./background.module.css"; color: red; } ``` When composing classes from separate files, be sure that they do not contain the same properties. The CSS module spec says that composing classes from separate files with conflicting properties is undefined behavior, meaning that the output may differ and be unreliable. # esbuild Source: https://bun.com/docs/bundler/esbuild Migration guide from esbuild to Bun's bundler Bun's bundler API is inspired heavily by esbuild. 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`. **Bundler only.** Unlike esbuild, Bun's bundler does not include a built-in development server or file watcher. 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 a 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. Bundling 10 copies of three.js from scratch, with sourcemaps and minification ## CLI API Bun and esbuild both provide a command-line interface. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # esbuild esbuild --outdir=out --bundle # bun 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`. | esbuild | bun build | Notes | | ---------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--bundle` | n/a | Bun always bundles, use `--no-bundle` to disable this behavior. | | `--define:K=V` | `--define K=V` | Small syntax difference; no colon.
`esbuild --define:foo=bar`
`bun build --define foo=bar` | | `--external:` | `--external ` | Small syntax difference; no colon.
`esbuild --external:react`
`bun build --external react` | | `--format` | `--format` | Bun supports `"esm"` and `"cjs"` currently, but more 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 for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented.

The syntax for `--loader` is slightly different.
`esbuild app.ts --bundle --loader:.svg=text`
`bun build app.ts --loader .svg:text` | | `--minify` | `--minify` | No differences | | `--outdir` | `--outdir` | No differences | | `--outfile` | `--outfile` | No differences | | `--packages` | `--packages` | No differences | | `--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 | Not 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` | `--banner` | Only applies to js bundles | | `--footer` | `--footer` | Only applies to js bundles | | `--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` | `--drop` | | | n/a | `--feature` | Bun-specific. Enable feature flags for compile-time dead-code elimination via `import { feature } from "bun:bundle"` | | `--entry-names` | `--entry-naming` | Renamed for consistency with naming in JS API | | `--global-name` | n/a | Not applicable, Bun does not support `iife` output at this time | | `--ignore-annotations` | `--ignore-dce-annotations` | | | `--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`. 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. | ## JavaScript API | esbuild.build() | Bun.build() | Notes | | ------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `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` 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 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
await 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 | ## 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. 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. Plugins in Bun and esbuild are defined with a builder object. ```ts title="myPlugin.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} 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 `onStart`, `onEnd`, `onResolve`, and `onLoad`. It does not yet implement the esbuild hooks `onDispose` and `resolve`. `initialOptions` is partially implemented, being read-only and only having a subset of esbuild's options; use `config` (same thing but with Bun's `BuildConfig` format) instead. ```ts title="myPlugin.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "my-plugin", setup(builder) { builder.onStart(() => { /* called when the bundle starts */ }); builder.onResolve( { /* onResolve.options */ }, args => { return { /* onResolve.results */ }; }, ); builder.onLoad( { /* onLoad.options */ }, args => { return { /* onLoad.results */ }; }, ); builder.onEnd(result => { /* called when the bundle is complete */ }); }, }; ``` ### onResolve * 🟢 `filter` * 🟢 `namespace` * 🟢 `path` * 🟢 `importer` * 🔴 `namespace` * 🔴 `resolveDir` * 🔴 `kind` * 🔴 `pluginData` * 🟢 `namespace` * 🟢 `path` * 🔴 `errors` * 🔴 `external` * 🔴 `pluginData` * 🔴 `pluginName` * 🔴 `sideEffects` * 🔴 `suffix` * 🔴 `warnings` * 🔴 `watchDirs` * 🔴 `watchFiles` ### onLoad * 🟢 `filter` * 🟢 `namespace` * 🟢 `path` * 🔴 `namespace` * 🔴 `suffix` * 🔴 `pluginData` * 🟢 `contents` * 🟢 `loader` * 🔴 `errors` * 🔴 `pluginData` * 🔴 `pluginName` * 🔴 `resolveDir` * 🔴 `warnings` * 🔴 `watchDirs` * 🔴 `watchFiles` # Single-file executable Source: https://bun.com/docs/bundler/executables Generate standalone executables from TypeScript or JavaScript files with Bun Bun's bundler implements a `--compile` flag for generating a standalone binary from a TypeScript or JavaScript file. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./cli.ts --compile --outfile mycli ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./cli.ts"], compile: { outfile: "./mycli", }, }); ``` ```ts cli.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("Hello world!"); ``` This bundles `cli.ts` into an executable that can be executed directly: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} ./mycli ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} Hello world! ``` All imported files and packages are bundled into the executable, along with a copy of the Bun runtime. All built-in Bun and Node.js APIs are supported. *** ## Cross-compile to other platforms The `--target` flag lets you compile your standalone executable for a different operating system, architecture, or version of Bun than the machine you're running `bun build` on. To build for Linux x64 (most servers): ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp # To support CPUs from before 2013, use the baseline version (nehalem) bun build --compile --target=bun-linux-x64-baseline ./index.ts --outfile myapp # To explicitly only support CPUs from 2013 and later, use the modern version (haswell) # modern is faster, but baseline is more compatible. bun build --compile --target=bun-linux-x64-modern ./index.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // Standard Linux x64 await Bun.build({ entrypoints: ["./index.ts"], compile: { target: "bun-linux-x64", outfile: "./myapp", }, }); // Baseline (pre-2013 CPUs) await Bun.build({ entrypoints: ["./index.ts"], compile: { target: "bun-linux-x64-baseline", outfile: "./myapp", }, }); // Modern (2013+ CPUs, faster) await Bun.build({ entrypoints: ["./index.ts"], compile: { target: "bun-linux-x64-modern", outfile: "./myapp", }, }); ``` To build for Linux ARM64 (e.g. Graviton or Raspberry Pi): ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # Note: the default architecture is x64 if no architecture is specified. bun build --compile --target=bun-linux-arm64 ./index.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], compile: { target: "bun-linux-arm64", outfile: "./myapp", }, }); ``` To build for Windows x64: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-windows-x64 ./path/to/my/app.ts --outfile myapp # To support CPUs from before 2013, use the baseline version (nehalem) bun build --compile --target=bun-windows-x64-baseline ./path/to/my/app.ts --outfile myapp # To explicitly only support CPUs from 2013 and later, use the modern version (haswell) bun build --compile --target=bun-windows-x64-modern ./path/to/my/app.ts --outfile myapp # note: if no .exe extension is provided, Bun will automatically add it for Windows executables ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // Standard Windows x64 await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-windows-x64", outfile: "./myapp", // .exe added automatically }, }); // Baseline or modern variants await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-windows-x64-baseline", outfile: "./myapp", }, }); ``` To build for Windows arm64: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-windows-arm64 ./path/to/my/app.ts --outfile myapp # note: if no .exe extension is provided, Bun will automatically add it for Windows executables ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-windows-arm64", outfile: "./myapp", // .exe added automatically }, }); ``` To build for macOS arm64: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-darwin-arm64 ./path/to/my/app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-darwin-arm64", outfile: "./myapp", }, }); ``` To build for macOS x64: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-darwin-x64 ./path/to/my/app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-darwin-x64", outfile: "./myapp", }, }); ``` ### Supported targets The order of the `--target` flag does not matter, as long as they're delimited by a `-`. | --target | Operating System | Architecture | Modern | Baseline | Libc | | -------------------- | ---------------- | ------------ | ------ | -------- | ----- | | bun-linux-x64 | Linux | x64 | ✅ | ✅ | glibc | | bun-linux-arm64 | Linux | arm64 | ✅ | N/A | glibc | | bun-windows-x64 | Windows | x64 | ✅ | ✅ | - | | bun-windows-arm64 | Windows | arm64 | ✅ | N/A | - | | bun-darwin-x64 | macOS | x64 | ✅ | ✅ | - | | bun-darwin-arm64 | macOS | arm64 | ✅ | N/A | - | | bun-linux-x64-musl | Linux | x64 | ✅ | ✅ | musl | | bun-linux-arm64-musl | Linux | arm64 | ✅ | N/A | musl | On x64 platforms, Bun uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `-baseline` build of Bun is for older CPUs that don't support these optimizations. Normally, when you install Bun we automatically detect which version to use but this can be harder to do when cross-compiling since you might not know the target CPU. You usually don't need to worry about it on Darwin x64, but it is relevant for Windows x64 and Linux x64. If you or your users see `"Illegal instruction"` errors, you might need to use the baseline version. *** ## Build-time constants Use the `--define` flag to inject build-time constants into your executable, such as version numbers, build timestamps, or configuration values: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./src/cli.ts"], compile: { outfile: "./mycli", }, define: { BUILD_VERSION: JSON.stringify("1.2.3"), BUILD_TIME: JSON.stringify("2024-01-15T10:30:00Z"), }, }); ``` These constants are embedded directly into your compiled binary at build time, providing zero runtime overhead and enabling dead code elimination optimizations. For comprehensive examples and advanced patterns, see the [Build-time constants guide](/guides/runtime/build-time-constants). *** ## Deploying to production Compiled executables reduce memory usage and improve Bun's start time. Normally, Bun reads and transpiles JavaScript and TypeScript files on `import` and `require`. This is part of what makes so much of Bun "just work", but it's not free. It costs time and memory to read files from disk, resolve file paths, parse, transpile, and print source code. With compiled executables, you can move that cost from runtime to build-time. When deploying to production, we recommend the following: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --minify --sourcemap ./path/to/my/app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { outfile: "./myapp", }, minify: true, sourcemap: "linked", }); ``` ### Bytecode compilation To improve startup time, enable bytecode compilation: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --minify --sourcemap --bytecode ./path/to/my/app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { outfile: "./myapp", }, minify: true, sourcemap: "linked", bytecode: true, }); ``` Using bytecode compilation, `tsc` starts 2x faster: ![Bytecode performance comparison](https://github.com/user-attachments/assets/dc8913db-01d2-48f8-a8ef-ac4e984f9763) Bytecode compilation moves parsing overhead for large input files from runtime to bundle time. Your app starts faster, in exchange for making the `bun build` command a little slower. It doesn't obscure source code. Bytecode compilation supports both `cjs` and `esm` formats when used with `--compile`. ### What do these flags do? The `--minify` argument optimizes the size of the transpiled output code. If you have a large application, this can save megabytes of space. For smaller applications, it might still improve start time a little. The `--sourcemap` argument embeds a sourcemap compressed with zstd, so that errors & stacktraces point to their original locations instead of the transpiled location. Bun will automatically decompress & resolve the sourcemap when an error occurs. The `--bytecode` argument enables bytecode compilation. Every time you run JavaScript code in Bun, JavaScriptCore (the engine) will compile your source code into bytecode. We can move this parsing work from runtime to bundle time, saving you startup time. *** ## Embedding runtime arguments **`--compile-exec-argv="args"`** - Embed runtime arguments that are available via `process.execArgv`: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --compile-exec-argv="--smol --user-agent=MyBot" ./app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./app.ts"], compile: { execArgv: ["--smol", "--user-agent=MyBot"], outfile: "./myapp", }, }); ``` ```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // In the compiled app console.log(process.execArgv); // ["--smol", "--user-agent=MyBot"] ``` ### Runtime arguments via `BUN_OPTIONS` The `BUN_OPTIONS` environment variable is applied to standalone executables, allowing you to pass runtime flags without recompiling: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # Enable CPU profiling on a compiled executable BUN_OPTIONS="--cpu-prof" ./myapp # Enable heap profiling with markdown output BUN_OPTIONS="--heap-prof-md" ./myapp # Combine multiple flags BUN_OPTIONS="--smol --cpu-prof-md" ./myapp ``` This is useful for debugging or profiling production executables without rebuilding them. *** ## Automatic config loading Standalone executables can automatically load configuration files from the directory where they are run. By default: * **`tsconfig.json`** and **`package.json`** loading is **disabled** — these are typically only needed at development time, and the bundler already uses them when compiling * **`.env`** and **`bunfig.toml`** loading is **enabled** — these often contain runtime configuration that may vary per deployment In a future version of Bun, `.env` and `bunfig.toml` may also be disabled by default for more deterministic behavior. ### Enabling config loading at runtime If your executable needs to read `tsconfig.json` or `package.json` at runtime, you can opt in with the new CLI flags: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # Enable runtime loading of tsconfig.json bun build --compile --compile-autoload-tsconfig ./app.ts --outfile myapp # Enable runtime loading of package.json bun build --compile --compile-autoload-package-json ./app.ts --outfile myapp # Enable both bun build --compile --compile-autoload-tsconfig --compile-autoload-package-json ./app.ts --outfile myapp ``` ### Disabling config loading at runtime To disable `.env` or `bunfig.toml` loading for deterministic execution: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # Disable .env loading bun build --compile --no-compile-autoload-dotenv ./app.ts --outfile myapp # Disable bunfig.toml loading bun build --compile --no-compile-autoload-bunfig ./app.ts --outfile myapp # Disable all config loading bun build --compile --no-compile-autoload-dotenv --no-compile-autoload-bunfig ./app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./app.ts"], compile: { // tsconfig.json and package.json are disabled by default autoloadTsconfig: true, // Enable tsconfig.json loading autoloadPackageJson: true, // Enable package.json loading // .env and bunfig.toml are enabled by default autoloadDotenv: false, // Disable .env loading autoloadBunfig: false, // Disable bunfig.toml loading outfile: "./myapp", }, }); ``` *** ## Act as the Bun CLI New in Bun v1.2.16 You can run a standalone executable as if it were the `bun` CLI itself by setting the `BUN_BE_BUN=1` environment variable. When this variable is set, the executable will ignore its bundled entrypoint and instead expose all the features of Bun's CLI. For example, consider an executable compiled from this script: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} echo "console.log(\"you shouldn't see this\");" > such-bun.js bun build --compile ./such-bun.js ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} [3ms] bundle 1 modules [89ms] compile such-bun ``` Normally, running `./such-bun` with arguments would execute the script. ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # Executable runs its own entrypoint by default ./such-bun install ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} you shouldn't see this ``` However, with the `BUN_BE_BUN=1` environment variable, it acts just like the `bun` binary: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # With the env var, the executable acts like the `bun` CLI BUN_BE_BUN=1 ./such-bun install ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} bun install v1.2.16-canary.1 (1d1db811) Checked 63 installs across 64 packages (no changes) [5.00ms] ``` This is useful for building CLI tools on top of Bun that may need to install packages, bundle dependencies, run different or local files and more without needing to download a separate binary or install bun. *** ## Full-stack executables New in Bun v1.2.17 Bun's `--compile` flag can create standalone executables that contain both server and client code, making it ideal for full-stack applications. When you import an HTML file in your server code, Bun automatically bundles all frontend assets (JavaScript, CSS, etc.) and embeds them into the executable. When Bun sees the HTML import on the server, it kicks off a frontend build process to bundle JavaScript, CSS, and other assets. ```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; import index from "./index.html"; const server = serve({ routes: { "/": index, "/api/hello": { GET: () => Response.json({ message: "Hello from API" }) }, }, }); console.log(`Server running at http://localhost:${server.port}`); ``` ```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} My App

Hello World

``` ```ts app.ts icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("Hello from the client!"); ``` ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-color: #f0f0f0; } ```
To build this into a single executable: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile ./server.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./server.ts"], compile: { outfile: "./myapp", }, }); ``` This creates a self-contained binary that includes: * Your server code * The Bun runtime * All frontend assets (HTML, CSS, JavaScript) * Any npm packages used by your server The result is a single file that can be deployed anywhere without needing Node.js, Bun, or any dependencies installed. Just run: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} ./myapp ``` Bun automatically handles serving the frontend assets with proper MIME types and cache headers. The HTML import is replaced with a manifest object that `Bun.serve` uses to efficiently serve pre-bundled assets. For more details on building full-stack applications with Bun, see the [full-stack guide](/bundler/fullstack). *** ## Worker To use workers in a standalone executable, add the worker's entrypoint to the build: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile ./index.ts ./my-worker.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts", "./my-worker.ts"], compile: { outfile: "./myapp", }, }); ``` Then, reference the worker in your code: ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("Hello from Bun!"); // Any of these will work: new Worker("./my-worker.ts"); new Worker(new URL("./my-worker.ts", import.meta.url)); new Worker(new URL("./my-worker.ts", import.meta.url).href); ``` When you add multiple entrypoints to a standalone executable, they will be bundled separately into the executable. In the future, we may automatically detect usages of statically-known paths in `new Worker(path)` and then bundle those into the executable, but for now, you'll need to add it to the shell command manually like the above example. If you use a relative path to a file not included in the standalone executable, it will attempt to load that path from disk relative to the current working directory of the process (and then error if it doesn't exist). *** ## SQLite You can use `bun:sqlite` imports with `bun build --compile`. By default, the database is resolved relative to the current working directory of the process. ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import db from "./my.db" with { type: "sqlite" }; console.log(db.query("select * from users LIMIT 1").get()); ``` That means if the executable is located at `/usr/bin/hello`, the user's terminal is located at `/home/me/Desktop`, it will look for `/home/me/Desktop/my.db`. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} cd /home/me/Desktop ./hello ``` *** ## Embed assets & files Standalone executables support embedding files directly into the binary. This lets you ship a single executable that contains images, JSON configs, templates, or any other assets your application needs. ### How it works Use the `with { type: "file" }` [import attribute](https://github.com/tc39/proposal-import-attributes) to embed a file: ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import icon from "./icon.png" with { type: "file" }; console.log(icon); // During development: "./icon.png" // After compilation: "$bunfs/icon-a1b2c3d4.png" (internal path) ``` The import returns a **path string** that points to the embedded file. At build time, Bun: 1. Reads the file contents 2. Embeds the data into the executable 3. Replaces the import with an internal path (prefixed with `$bunfs/`) You can then read this embedded file using `Bun.file()` or Node.js `fs` APIs. ### Reading embedded files with Bun.file() `Bun.file()` is the recommended way to read embedded files: ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import icon from "./icon.png" with { type: "file" }; import { file } from "bun"; // Get file contents as different types const bytes = await file(icon).arrayBuffer(); // ArrayBuffer const text = await file(icon).text(); // string (for text files) const blob = file(icon); // Blob // Stream the file in a response export default { fetch(req) { return new Response(file(icon), { headers: { "Content-Type": "image/png" }, }); }, }; ``` ### Reading embedded files with Node.js fs Embedded files work seamlessly with Node.js file system APIs: ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import icon from "./icon.png" with { type: "file" }; import config from "./config.json" with { type: "file" }; import { readFileSync, promises as fs } from "node:fs"; // Synchronous read const iconBuffer = readFileSync(icon); // Async read const configData = await fs.readFile(config, "utf-8"); const parsed = JSON.parse(configData); // Check file stats const stats = await fs.stat(icon); console.log(`Icon size: ${stats.size} bytes`); ``` ### Practical examples #### Embedding a JSON config file ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import configPath from "./default-config.json" with { type: "file" }; import { file } from "bun"; // Load the embedded default configuration const defaultConfig = await file(configPath).json(); // Merge with user config if it exists const userConfig = await file("./user-config.json") .json() .catch(() => ({})); const config = { ...defaultConfig, ...userConfig }; ``` #### Serving static assets in an HTTP server Use `static` routes in `Bun.serve()` for efficient static file serving: ```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import favicon from "./favicon.ico" with { type: "file" }; import logo from "./logo.png" with { type: "file" }; import styles from "./styles.css" with { type: "file" }; import { file, serve } from "bun"; serve({ static: { "/favicon.ico": file(favicon), "/logo.png": file(logo), "/styles.css": file(styles), }, fetch(req) { return new Response("Not found", { status: 404 }); }, }); ``` Bun automatically handles Content-Type headers and caching for static routes. #### Embedding templates ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import templatePath from "./email-template.html" with { type: "file" }; import { file } from "bun"; async function sendWelcomeEmail(user: { name: string; email: string }) { const template = await file(templatePath).text(); const html = template.replace("{{name}}", user.name).replace("{{email}}", user.email); // Send email with the rendered template... } ``` #### Embedding binary files ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import wasmPath from "./processor.wasm" with { type: "file" }; import fontPath from "./font.ttf" with { type: "file" }; import { file } from "bun"; // Load a WebAssembly module const wasmBytes = await file(wasmPath).arrayBuffer(); const wasmModule = await WebAssembly.instantiate(wasmBytes); // Read binary font data const fontData = await file(fontPath).bytes(); ``` ### Embed SQLite databases If your application wants to embed a SQLite database into the compiled executable, set `type: "sqlite"` in the import attribute and the `embed` attribute to `"true"`. The database file must already exist on disk. Then, import it in your code: ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import myEmbeddedDb from "./my.db" with { type: "sqlite", embed: "true" }; console.log(myEmbeddedDb.query("select * from users LIMIT 1").get()); ``` Finally, compile it into a standalone executable: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile ./index.ts --outfile mycli ``` The database file must exist on disk when you run `bun build --compile`. The `embed: "true"` attribute tells the bundler to include the database contents inside the compiled executable. When running normally with `bun run`, the database file is loaded from disk just like a regular SQLite import. In the compiled executable, the embedded database is read-write, but all changes are lost when the executable exits (since it's stored in memory). ### Embed N-API Addons You can embed `.node` files into executables. ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} const addon = require("./addon.node"); console.log(addon.hello()); ``` Unfortunately, if you're using `@mapbox/node-pre-gyp` or other similar tools, you'll need to make sure the `.node` file is directly required or it won't bundle correctly. ### Embed directories To embed a directory with `bun build --compile`, include file patterns in your build: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile ./index.ts ./public/**/*.png ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { Glob } from "bun"; // Expand glob pattern to file list const glob = new Glob("./public/**/*.png"); const pngFiles = Array.from(glob.scanSync(".")); await Bun.build({ entrypoints: ["./index.ts", ...pngFiles], compile: { outfile: "./myapp", }, }); ``` Then, you can reference the files in your code: ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import icon from "./public/assets/icon.png" with { type: "file" }; import { file } from "bun"; export default { fetch(req) { // Embedded files can be streamed from Response objects return new Response(file(icon)); }, }; ``` This is honestly a workaround, and we expect to improve this in the future with a more direct API. ### Listing embedded files `Bun.embeddedFiles` gives you access to all embedded files as `Blob` objects: ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import "./icon.png" with { type: "file" }; import "./data.json" with { type: "file" }; import "./template.html" with { type: "file" }; import { embeddedFiles } from "bun"; // List all embedded files for (const blob of embeddedFiles) { console.log(`${blob.name} - ${blob.size} bytes`); } // Output: // icon-a1b2c3d4.png - 4096 bytes // data-e5f6g7h8.json - 256 bytes // template-i9j0k1l2.html - 1024 bytes ``` Each item in `Bun.embeddedFiles` is a `Blob` with a `name` property: ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} embeddedFiles: ReadonlyArray; ``` This is useful for dynamically serving all embedded assets using `static` routes: ```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import "./public/favicon.ico" with { type: "file" }; import "./public/logo.png" with { type: "file" }; import "./public/styles.css" with { type: "file" }; import { embeddedFiles, serve } from "bun"; // Build static routes from all embedded files const staticRoutes: Record = {}; for (const blob of embeddedFiles) { // Remove hash from filename: "icon-a1b2c3d4.png" -> "icon.png" const name = blob.name.replace(/-[a-f0-9]+\./, "."); staticRoutes[`/${name}`] = blob; } serve({ static: staticRoutes, fetch(req) { return new Response("Not found", { status: 404 }); }, }); ``` `Bun.embeddedFiles` excludes bundled source code (`.ts`, `.js`, etc.) to help protect your application's source. #### Content hash By default, embedded files have a content hash appended to their name. This is useful for situations where you want to serve the file from a URL or CDN and have fewer cache invalidation issues. But sometimes, this is unexpected and you might want the original name instead: To disable the content hash, configure asset naming: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --asset-naming="[name].[ext]" ./index.ts ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], compile: { outfile: "./myapp", }, naming: { asset: "[name].[ext]", }, }); ``` *** ## Minification To trim down the size of the executable, enable minification: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --minify ./index.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], compile: { outfile: "./myapp", }, minify: true, // Enable all minification }); // Or granular control: await Bun.build({ entrypoints: ["./index.ts"], compile: { outfile: "./myapp", }, minify: { whitespace: true, syntax: true, identifiers: true, }, }); ``` This uses Bun's minifier to reduce the code size. Overall though, Bun's binary is still way too big and we need to make it smaller. *** ## Windows-specific flags When compiling a standalone executable on Windows, there are platform-specific options to customize metadata on the generated `.exe` file: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # Custom icon bun build --compile --windows-icon=path/to/icon.ico ./app.ts --outfile myapp # Hide console window (for GUI apps) bun build --compile --windows-hide-console ./app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./app.ts"], compile: { outfile: "./myapp", windows: { icon: "./path/to/icon.ico", hideConsole: true, // Additional Windows metadata: title: "My Application", publisher: "My Company", version: "1.0.0", description: "A standalone Windows application", copyright: "Copyright 2024", }, }, }); ``` Available Windows options: * `icon` - Path to `.ico` file for the executable icon * `hideConsole` - Disable the background terminal (for GUI apps) * `title` - Application title in file properties * `publisher` - Publisher name in file properties * `version` - Version string in file properties * `description` - Description in file properties * `copyright` - Copyright notice in file properties These flags currently cannot be used when cross-compiling because they depend on Windows APIs. *** ## Code signing on macOS To codesign a standalone executable on macOS (which fixes Gatekeeper warnings), use the `codesign` command. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} codesign --deep --force -vvvv --sign "XXXXXXXXXX" ./myapp ``` We recommend including an `entitlements.plist` file with JIT permissions. ```xml icon="xml" title="info.plist" theme={"theme":{"light":"github-light","dark":"dracula"}} com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.cs.disable-executable-page-protection com.apple.security.cs.allow-dyld-environment-variables com.apple.security.cs.disable-library-validation ``` To codesign with JIT support, pass the `--entitlements` flag to `codesign`. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} codesign --deep --force -vvvv --sign "XXXXXXXXXX" --entitlements entitlements.plist ./myapp ``` After codesigning, verify the executable: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} codesign -vvv --verify ./myapp ./myapp: valid on disk ./myapp: satisfies its Designated Requirement ``` Codesign support requires Bun v1.2.4 or newer. *** ## Code splitting Standalone executables support code splitting. Use `--compile` with `--splitting` to create an executable that loads code-split chunks at runtime. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --splitting ./src/entry.ts --outdir ./build ``` ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./src/entry.ts"], compile: true, splitting: true, outdir: "./build", }); ``` ```ts src/entry.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("Entrypoint loaded"); const lazy = await import("./lazy.ts"); lazy.hello(); ``` ```ts src/lazy.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export function hello() { console.log("Lazy module loaded"); } ``` ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} ./build/entry ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} Entrypoint loaded Lazy module loaded ``` *** ## Using plugins Plugins work with standalone executables, allowing you to transform files during the build process: ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const envPlugin: BunPlugin = { name: "env-loader", setup(build) { build.onLoad({ filter: /\.env\.json$/ }, async args => { // Transform .env.json files into validated config objects const env = await Bun.file(args.path).json(); return { contents: `export default ${JSON.stringify(env)};`, loader: "js", }; }); }, }; await Bun.build({ entrypoints: ["./cli.ts"], compile: { outfile: "./mycli", }, plugins: [envPlugin], }); ``` Example use case - embedding environment config at build time: ```ts cli.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import config from "./config.env.json"; console.log(`Running in ${config.environment} mode`); console.log(`API endpoint: ${config.apiUrl}`); ``` Plugins can perform any transformation: compile YAML/TOML configs, inline SQL queries, generate type-safe API clients, or preprocess templates. Refer to the [plugin documentation](/bundler/plugins) for more details. *** ## Unsupported CLI arguments Currently, the `--compile` flag can only accept a single entrypoint at a time and does not support the following flags: * `--outdir` — use `outfile` instead (except when using with `--splitting`). * `--public-path` * `--target=node` * `--target=browser` (without HTML entrypoints — see [Standalone HTML](/bundler/standalone-html) for `--compile --target=browser` with `.html` files) * `--no-bundle` - we always bundle everything into the executable. *** ## API reference The `compile` option in `Bun.build()` accepts three forms: ```ts title="types" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} interface BuildConfig { entrypoints: string[]; compile: boolean | Bun.Build.Target | CompileBuildOptions; // ... other BuildConfig options (minify, sourcemap, define, plugins, etc.) } interface CompileBuildOptions { target?: Bun.Build.Target; // Cross-compilation target outfile?: string; // Output executable path execArgv?: string[]; // Runtime arguments (process.execArgv) autoloadTsconfig?: boolean; // Load tsconfig.json (default: false) autoloadPackageJson?: boolean; // Load package.json (default: false) autoloadDotenv?: boolean; // Load .env files (default: true) autoloadBunfig?: boolean; // Load bunfig.toml (default: true) windows?: { icon?: string; // Path to .ico file hideConsole?: boolean; // Hide console window title?: string; // Application title publisher?: string; // Publisher name version?: string; // Version string description?: string; // Description copyright?: string; // Copyright notice }; } ``` Usage forms: ```ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // Simple boolean - compile for current platform (uses entrypoint name as output) compile: true // Target string - cross-compile (uses entrypoint name as output) compile: "bun-linux-x64" // Full options object - specify outfile and other options compile: { target: "bun-linux-x64", outfile: "./myapp", } ``` ### Supported targets ```ts title="Bun.Build.Target" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} type Target = | "bun-darwin-x64" | "bun-darwin-x64-baseline" | "bun-darwin-arm64" | "bun-linux-x64" | "bun-linux-x64-baseline" | "bun-linux-x64-modern" | "bun-linux-arm64" | "bun-linux-x64-musl" | "bun-linux-arm64-musl" | "bun-windows-x64" | "bun-windows-x64-baseline" | "bun-windows-x64-modern" | "bun-windows-arm64"; ``` ### Complete example ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "my-plugin", setup(build) { // Plugin implementation }, }; const result = await Bun.build({ entrypoints: ["./src/cli.ts"], compile: { target: "bun-linux-x64", outfile: "./dist/mycli", execArgv: ["--smol"], autoloadDotenv: false, autoloadBunfig: false, }, minify: true, sourcemap: "linked", bytecode: true, define: { "process.env.NODE_ENV": JSON.stringify("production"), VERSION: JSON.stringify("1.0.0"), }, plugins: [myPlugin], }); if (result.success) { console.log("Build successful:", result.outputs[0].path); } ``` # Fullstack dev server Source: https://bun.com/docs/bundler/fullstack Build fullstack applications with Bun's integrated dev server that bundles frontend assets and handles API routes To get started, import HTML files and pass them to the `routes` option in `Bun.serve()`. ```ts title="app.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; import dashboard from "./dashboard.html"; import homepage from "./index.html"; const server = serve({ routes: { // ** HTML imports ** // Bundle & route index.html to "/". This uses HTMLRewriter to scan // the HTML for ` ``` Becomes something like this: ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} Home
``` ## React Integration To use React in your client-side code, import `react-dom/client` and render your app. ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import dashboard from "../public/dashboard.html"; import { serve } from "bun"; serve({ routes: { "/": dashboard, }, async fetch(req) { // ...api requests return new Response("hello world"); }, }); ``` ```tsx title="src/frontend.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { createRoot } from "react-dom/client"; import App from "./app"; const container = document.getElementById("root"); const root = createRoot(container!); root.render(); ``` ```html title="public/dashboard.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} Dashboard
``` ```tsx title="src/app.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { useState } from "react"; export default function App() { const [count, setCount] = useState(0); return (

Dashboard

); } ```
## Development Mode When building locally, enable development mode by setting `development: true` in `Bun.serve()`. ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import homepage from "./index.html"; import dashboard from "./dashboard.html"; Bun.serve({ routes: { "/": homepage, "/dashboard": dashboard, }, development: true, fetch(req) { // ... api requests }, }); ``` ### Development Mode Features When `development` is `true`, Bun will: * Include the SourceMap header in the response so that devtools can show the original source code * Disable minification * Re-bundle assets on each request to a `.html` file * Enable hot module reloading (unless `hmr: false` is set) * Echo console logs from browser to terminal ### Advanced Development Configuration `Bun.serve()` supports echoing console logs from the browser to the terminal. To enable this, pass `console: true` in the development object in `Bun.serve()`. ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import homepage from "./index.html"; Bun.serve({ // development can also be an object. development: { // Enable Hot Module Reloading hmr: true, // Echo console logs from the browser to the terminal console: true, }, routes: { "/": homepage, }, }); ``` When `console: true` is set, Bun will stream console logs from the browser to the terminal. This reuses the existing WebSocket connection from HMR to send the logs. ### Development vs Production | Feature | Development | Production | | ------------------- | ---------------------- | ---------- | | **Source maps** | ✅ Enabled | ❌ Disabled | | **Minification** | ❌ Disabled | ✅ Enabled | | **Hot reloading** | ✅ Enabled | ❌ Disabled | | **Asset bundling** | 🔄 On each request | 💾 Cached | | **Console logging** | 🖥️ Browser → Terminal | ❌ Disabled | | **Error details** | 📝 Detailed | 🔒 Minimal | ## Production Mode Hot reloading and `development: true` helps you iterate quickly, but in production, your server should be as fast as possible and have as few external dependencies as possible. ### Ahead of Time Bundling (Recommended) As of Bun v1.2.17, you can use `Bun.build` or `bun build` to bundle your full-stack application ahead of time. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --target=bun --production --outdir=dist ./src/index.ts ``` When Bun's bundler sees an HTML import from server-side code, it will bundle the referenced JavaScript/TypeScript/TSX/JSX and CSS files into a manifest object that `Bun.serve()` can use to serve the assets. ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; import index from "./index.html"; serve({ routes: { "/": index }, }); ``` ### Runtime Bundling When adding a build step is too complicated, you can set `development: false` in `Bun.serve()`. This will: * Enable in-memory caching of bundled assets. Bun will bundle assets lazily on the first request to an `.html` file, and cache the result in memory until the server restarts. * Enable `Cache-Control` headers and `ETag` headers * Minify JavaScript/TypeScript/TSX/JSX files ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; import homepage from "./index.html"; serve({ routes: { "/": homepage, }, // Production mode development: false, }); ``` ## API Routes ### HTTP Method Handlers Define API endpoints with HTTP method handlers: ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; serve({ routes: { "/api/users": { async GET(req) { // Handle GET requests const users = await getUsers(); return Response.json(users); }, async POST(req) { // Handle POST requests const userData = await req.json(); const user = await createUser(userData); return Response.json(user, { status: 201 }); }, async PUT(req) { // Handle PUT requests const userData = await req.json(); const user = await updateUser(userData); return Response.json(user); }, async DELETE(req) { // Handle DELETE requests await deleteUser(req.params.id); return new Response(null, { status: 204 }); }, }, }, }); ``` ### Dynamic Routes Use URL parameters in your routes: ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} serve({ routes: { // Single parameter "/api/users/:id": async req => { const { id } = req.params; const user = await getUserById(id); return Response.json(user); }, // Multiple parameters "/api/users/:userId/posts/:postId": async req => { const { userId, postId } = req.params; const post = await getPostByUser(userId, postId); return Response.json(post); }, // Wildcard routes "/api/files/*": async req => { const filePath = req.params["*"]; const file = await getFile(filePath); return new Response(file); }, }, }); ``` ### Request Handling ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} serve({ routes: { "/api/data": { async POST(req) { // Parse JSON body const body = await req.json(); // Access headers const auth = req.headers.get("Authorization"); // Access URL parameters const { id } = req.params; // Access query parameters const url = new URL(req.url); const page = url.searchParams.get("page") || "1"; // Return response return Response.json({ message: "Data processed", page: parseInt(page), authenticated: !!auth, }); }, }, }, }); ``` ## Plugins Bun's bundler plugins are also supported when bundling static routes. To configure plugins for `Bun.serve`, add a `plugins` array in the `[serve.static]` section of your `bunfig.toml`. ### TailwindCSS Plugin You can use TailwindCSS by installing and adding the `tailwindcss` package and `bun-plugin-tailwind` plugin. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun add tailwindcss bun-plugin-tailwind ``` ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] plugins = ["bun-plugin-tailwind"] ``` This will allow you to use TailwindCSS utility classes in your HTML and CSS files. Import `tailwindcss` somewhere in your project: ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` Alternatively, you can import TailwindCSS in your CSS file: ```css title="style.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} @import "tailwindcss"; .custom-class { @apply bg-red-500 text-white; } ``` ```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` ### Custom Plugins Any JS file or module which exports a valid bundler plugin object (essentially an object with a `name` and `setup` field) can be placed inside the plugins array: ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] plugins = ["./my-plugin-implementation.ts"] ``` ```ts title="my-plugin-implementation.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "my-custom-plugin", setup(build) { // Plugin implementation build.onLoad({ filter: /\.custom$/ }, async args => { const text = await Bun.file(args.path).text(); return { contents: `export default ${JSON.stringify(text)};`, loader: "js", }; }); }, }; export default myPlugin; ``` Bun will lazily resolve and load each plugin and use them to bundle your routes. This is currently in `bunfig.toml` to make it possible to know statically which plugins are in use when we eventually integrate this with the `bun build` CLI. These plugins work in `Bun.build()`'s JS API, but are not yet supported in the CLI. ## Inline Environment Variables Bun can replace `process.env.*` references in your frontend JavaScript and TypeScript with their actual values at build time. Configure the `env` option in your `bunfig.toml`: ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended) # env = "inline" # inline all environment variables # env = "disable" # disable env var replacement (default) ``` This only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env = process.env; env.FOO`. If an environment variable is not set, you may see runtime errors like `ReferenceError: process is not defined` in the browser. See the [HTML & static sites documentation](/bundler/html-static#inline-environment-variables) for more details on build-time configuration and examples. ## How It Works Bun uses `HTMLRewriter` to scan for ` ``` * Processes CSS imports and `` tags * Concatenates CSS files * Rewrites url and asset paths to include content-addressable hashes in URLs ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` * Links to assets are rewritten to include content-addressable hashes in URLs * Small assets in CSS files are inlined into `data:` URLs, reducing the total number of HTTP requests sent over the wire * Combines all ` ``` ```tsx title="src/main.tsx" theme={"theme":{"light":"github-light","dark":"dracula"}} import { createRoot } from "react-dom/client"; import { App } from "./App"; const container = document.getElementById("root")!; const root = createRoot(container); root.render(); ``` ```tsx title="src/App.tsx" theme={"theme":{"light":"github-light","dark":"dracula"}} import { useState, useEffect } from "react"; interface User { id: number; name: string; email: string; created_at: string; } export function App() { const [users, setUsers] = useState([]); const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [loading, setLoading] = useState(false); const fetchUsers = async () => { const response = await fetch("/api/users"); const data = await response.json(); setUsers(data); }; const createUser = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); try { const response = await fetch("/api/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, email }), }); if (response.ok) { setName(""); setEmail(""); await fetchUsers(); } else { const error = await response.json(); alert(error.error); } } catch (error) { alert("Failed to create user"); } finally { setLoading(false); } }; const deleteUser = async (id: number) => { if (!confirm("Are you sure?")) return; try { const response = await fetch(`/api/users/${id}`, { method: "DELETE", }); if (response.ok) { await fetchUsers(); } } catch (error) { alert("Failed to delete user"); } }; useEffect(() => { fetchUsers(); }, []); return (

User Management

setName(e.target.value)} required /> setEmail(e.target.value)} required />

Users ({users.length})

{users.map(user => (
{user.name}
{user.email}
))}
); } ``` ```css title="src/styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; background: #f5f5f5; color: #333; } .container { max-width: 800px; margin: 0 auto; padding: 2rem; } h1 { color: #2563eb; margin-bottom: 2rem; } .form { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); margin-bottom: 2rem; display: flex; gap: 1rem; flex-wrap: wrap; } .form input { flex: 1; min-width: 200px; padding: 0.75rem; border: 1px solid #ddd; border-radius: 4px; } .form button { padding: 0.75rem 1.5rem; background: #2563eb; color: white; border: none; border-radius: 4px; cursor: pointer; } .form button:hover { background: #1d4ed8; } .form button:disabled { opacity: 0.5; cursor: not-allowed; } .users { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .user-card { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid #eee; } .user-card:last-child { border-bottom: none; } .delete-btn { padding: 0.5rem 1rem; background: #dc2626; color: white; border: none; border-radius: 4px; cursor: pointer; } .delete-btn:hover { background: #b91c1c; } ``` ## Best Practices ### Project Structure ``` my-app/ ├── src/ │ ├── components/ │ │ ├── Header.tsx │ │ └── UserList.tsx │ ├── styles/ │ │ ├── globals.css │ │ └── components.css │ ├── utils/ │ │ └── api.ts │ ├── App.tsx │ └── main.tsx ├── public/ │ ├── index.html │ ├── dashboard.html │ └── favicon.ico ├── server/ │ ├── routes/ │ │ ├── users.ts │ │ └── auth.ts │ ├── db/ │ │ └── schema.sql │ └── index.ts ├── bunfig.toml └── package.json ``` ### Environment-Based Configuration ```ts title="server/config.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export const config = { development: process.env.NODE_ENV !== "production", port: process.env.PORT || 3000, database: { url: process.env.DATABASE_URL || "./dev.db", }, cors: { origin: process.env.CORS_ORIGIN || "*", }, }; ``` ### Error Handling ```ts title="server/middleware.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export function errorHandler(error: Error, req: Request) { console.error("Server error:", error); if (process.env.NODE_ENV === "production") { return Response.json({ error: "Internal server error" }, { status: 500 }); } return Response.json( { error: error.message, stack: error.stack, }, { status: 500 }, ); } ``` ### API Response Helpers ```ts title="server/utils.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export function json(data: any, status = 200) { return Response.json(data, { status }); } export function error(message: string, status = 400) { return Response.json({ error: message }, { status }); } export function notFound(message = "Not found") { return error(message, 404); } export function unauthorized(message = "Unauthorized") { return error(message, 401); } ``` ### Type Safety ```ts title="types/api.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export interface User { id: number; name: string; email: string; created_at: string; } export interface CreateUserRequest { name: string; email: string; } export interface ApiResponse { data?: T; error?: string; } ``` ## Deployment ### Production Build ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # Build for production bun build --target=bun --production --outdir=dist ./server/index.ts # Run production server NODE_ENV=production bun dist/index.js ``` ### Docker Deployment ```dockerfile title="Dockerfile" icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}} FROM oven/bun:1 as base WORKDIR /usr/src/app # Install dependencies COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile # Copy source code COPY . . # Build application RUN bun build --target=bun --production --outdir=dist ./server/index.ts # Production stage FROM oven/bun:1-slim WORKDIR /usr/src/app COPY --from=base /usr/src/app/dist ./ COPY --from=base /usr/src/app/public ./public EXPOSE 3000 CMD ["bun", "index.js"] ``` ### Environment Variables ```ini title=".env.production" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} NODE_ENV=production PORT=3000 DATABASE_URL=postgresql://user:pass@localhost:5432/myapp CORS_ORIGIN=https://myapp.com ``` ## Migration from Other Frameworks ### From Express + Webpack ```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // Before (Express + Webpack) app.use(express.static("dist")); app.get("/api/users", (req, res) => { res.json(users); }); // After (Bun fullstack) serve({ routes: { "/": homepage, // Replaces express.static "/api/users": { GET() { return Response.json(users); }, }, }, }); ``` ### From Next.js API Routes ```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // Before (Next.js) export default function handler(req, res) { if (req.method === 'GET') { res.json(users); } } // After (Bun) "/api/users": { GET() { return Response.json(users); } } ``` ## Limitations and Future Plans ### Current Limitations * `bun build` CLI integration is not yet available for fullstack apps * Auto-discovery of API routes is not implemented * Server-side rendering (SSR) is not built-in ### Planned Features * Integration with `bun build` CLI * File-based routing for API endpoints * Built-in SSR support * Enhanced plugin ecosystem This is a work in progress. Features and APIs may change as Bun continues to evolve. # Hot reloading Source: https://bun.com/docs/bundler/hot-reloading Hot Module Replacement (HMR) for Bun's development server Hot Module Replacement (HMR) allows you to update modules in a running application without needing a full page reload. This preserves the application state and improves the development experience. HMR is enabled by default when using Bun's full-stack development server. ## `import.meta.hot` API Reference Bun implements a client-side HMR API modeled after [Vite's `import.meta.hot` API](https://vite.dev/guide/api-hmr). It can be checked for with `if (import.meta.hot)`, tree-shaking it in production. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} if (import.meta.hot) { // HMR APIs are available. } ``` However, this check is often not needed as Bun will dead-code-eliminate calls to all of the HMR APIs in production builds. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // This entire function call will be removed in production! import.meta.hot.dispose(() => { console.log("dispose"); }); ``` For this to work, Bun forces these APIs to be called without indirection. That means the following do not work: ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // INVALID: Assigning `hot` to a variable const hot = import.meta.hot; hot.accept(); // INVALID: Assigning `import.meta` to a variable const meta = import.meta; meta.hot.accept(); console.log(meta.hot.data); // INVALID: Passing to a function doSomething(import.meta.hot.dispose); // OK: The full phrase "import.meta.hot." must be called directly: import.meta.hot.accept(); // OK: `data` can be passed to functions: doSomething(import.meta.hot.data); ``` The HMR API is still a work in progress. Some features are missing. HMR can be disabled in `Bun.serve` by setting the development option to `{ hmr: false }`. ## API Methods | Method | Status | Notes | | ------------------ | ------ | --------------------------------------------------------------------- | | `hot.accept()` | ✅ | Indicate that a hot update can be replaced gracefully. | | `hot.data` | ✅ | Persist data between module evaluations. | | `hot.dispose()` | ✅ | Add a callback function to run when a module is about to be replaced. | | `hot.invalidate()` | ❌ | | | `hot.on()` | ✅ | Attach an event listener | | `hot.off()` | ✅ | Remove an event listener from `on`. | | `hot.send()` | ❌ | | | `hot.prune()` | 🚧 | NOTE: Callback is currently never called. | | `hot.decline()` | ✅ | No-op to match Vite's `import.meta.hot` | ## import.meta.hot.accept() The `accept()` method indicates that a module can be hot-replaced. When called without arguments, it indicates that this module can be replaced by re-evaluating the file. After a hot update, importers of this module will be automatically patched. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // index.ts import { getCount } from "./foo.ts"; console.log("count is ", getCount()); import.meta.hot.accept(); export function getNegativeCount() { return -getCount(); } ``` This creates a hot-reloading boundary for all of the files that `index.ts` imports. That means whenever `foo.ts` or any of its dependencies are saved, the update will bubble up to `index.ts` will re-evaluate. Files that import `index.ts` will then be patched to import the new version of `getNegativeCount()`. If only `index.ts` is updated, only the one file will be re-evaluated, and the counter in `foo.ts` is reused. This may be used in combination with `import.meta.hot.data` to transfer state from the previous module to the new one. When no modules call `import.meta.hot.accept()` (and there isn't React Fast Refresh or a plugin calling it for you), the page will reload when the file updates, and a console warning shows which files were invalidated. This warning is safe to ignore if it makes more sense to rely on full page reloads. ### With callback When provided one callback, `import.meta.hot.accept` will function how it does in Vite. Instead of patching the importers of this module, it will call the callback with the new module. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export const count = 0; import.meta.hot.accept(newModule => { if (newModule) { // newModule is undefined when SyntaxError happened console.log("updated: count is now ", newModule.count); } }); ``` Prefer using `import.meta.hot.accept()` without an argument as it usually makes your code easier to understand. ### Accepting other modules ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { count } from "./foo"; import.meta.hot.accept("./foo", () => { if (!newModule) return; console.log("updated: count is now ", count); }); ``` Indicates that a dependency's module can be accepted. When the dependency is updated, the callback will be called with the new module. ### With multiple dependencies ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import.meta.hot.accept(["./foo", "./bar"], newModules => { // newModules is an array where each item corresponds to the updated module // or undefined if that module had a syntax error }); ``` Indicates that multiple dependencies' modules can be accepted. This variant accepts an array of dependencies, where the callback will receive the updated modules, and `undefined` for any that had errors. ## import.meta.hot.data `import.meta.hot.data` maintains state between module instances during hot replacement, enabling data transfer from previous to new versions. When `import.meta.hot.data` is written into, Bun will also mark this module as capable of self-accepting (equivalent of calling `import.meta.hot.accept()`). ```tsx title="index.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { createRoot } from "react-dom/client"; import { App } from "./app"; const root = (import.meta.hot.data.root ??= createRoot(elem)); root.render(); // re-use an existing root ``` In production, `data` is inlined to be `{}`, meaning it cannot be used as a state holder. The above pattern is recommended for stateful modules because Bun knows it can minify `{}.prop ??= value` into `value` in production. ## import.meta.hot.dispose() Attaches an on-dispose callback. This is called: * Just before the module is replaced with another copy (before the next is loaded) * After the module is detached (removing all imports to this module, see `import.meta.hot.prune()`) ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} const sideEffect = setupSideEffect(); import.meta.hot.dispose(() => { sideEffect.cleanup(); }); ``` This callback is not called on route navigation or when the browser tab closes. Returning a promise will delay module replacement until the module is disposed. All dispose callbacks are called in parallel. ## import.meta.hot.prune() Attaches an on-prune callback. This is called when all imports to this module are removed, but the module was previously loaded. This can be used to clean up resources that were created when the module was loaded. Unlike `import.meta.hot.dispose()`, this pairs much better with `accept` and `data` to manage stateful resources. A full example managing a WebSocket: ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { something } from "./something"; // Initialize or re-use a WebSocket connection export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin)); // If the module's import is removed, clean up the WebSocket connection. import.meta.hot.prune(() => { ws.close(); }); ``` If `dispose` was used instead, the WebSocket would close and re-open on every hot update. Both versions of the code will prevent page reloads when imported files are updated. ## import.meta.hot.on() and off() `on()` and `off()` are used to listen for events from the HMR runtime. Event names are prefixed with a prefix so that plugins do not conflict with each other. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import.meta.hot.on("bun:beforeUpdate", () => { console.log("before a hot update"); }); ``` When a file is replaced, all of its event listeners are automatically removed. ### Built-in events | Event | Emitted when | | ---------------------- | ----------------------------------------------------------------------------------------------- | | `bun:beforeUpdate` | before a hot update is applied. | | `bun:afterUpdate` | after a hot update is applied. | | `bun:beforeFullReload` | before a full page reload happens. | | `bun:beforePrune` | before prune callbacks are called. | | `bun:invalidate` | when a module is invalidated with `import.meta.hot.invalidate()` | | `bun:error` | when a build or runtime error occurs | | `bun:ws:disconnect` | when the HMR WebSocket connection is lost. This can indicate the development server is offline. | | `bun:ws:connect` | when the HMR WebSocket connects or re-connects. | For compatibility with Vite, the above events are also available via `vite:*` prefix instead of `bun:*`. # HTML & static sites Source: https://bun.com/docs/bundler/html-static Build static sites, landing pages, and web applications with Bun's bundler Bun's bundler has first-class support for HTML. Build static sites, landing pages, and web applications with zero configuration. Just point Bun at your HTML file and it handles everything else. ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` To get started, pass HTML files to `bun`. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./index.html ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Press h + Enter to show shortcuts ``` Bun's development server provides powerful features with zero configuration: * **Automatic Bundling** - Bundles and serves your HTML, JavaScript, and CSS * **Multi-Entry Support** - Handles multiple HTML entry points and glob entry points * **Modern JavaScript** - TypeScript & JSX support out of the box * **Smart Configuration** - Reads `tsconfig.json` for paths, JSX options, experimental decorators, and more * **Plugins** - Plugins for TailwindCSS and more * **ESM & CommonJS** - Use ESM and CommonJS in your JavaScript, TypeScript, and JSX files * **CSS Bundling & Minification** - Bundles CSS from `` tags and `@import` statements * **Asset Management** - Automatic copying & hashing of images and assets; Rewrites asset paths in JavaScript, CSS, and HTML ## Single Page Apps (SPA) When you pass a single `.html` file to Bun, Bun will use it as a fallback route for all paths. This makes it perfect for single page apps that use client-side routing: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun index.html ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Press h + Enter to show shortcuts ``` Your React or other SPA will work out of the box — no configuration needed. All routes like `/about`, `/users/123`, etc. will serve the same HTML file, letting your client-side router handle the navigation. ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} My SPA
``` ## Multi-page apps (MPA) Some projects have several separate routes or HTML files as entry points. To support multiple entry points, pass them all to `bun`: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./index.html ./about.html ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Routes: / ./index.html /about ./about.html Press h + Enter to show shortcuts ``` This will serve: * `index.html` at `/` * `about.html` at `/about` ### Glob patterns To specify multiple files, you can use glob patterns that end in `.html`: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./**/*.html ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Routes: / ./index.html /about ./about.html Press h + Enter to show shortcuts ``` ### Path normalization The base path is chosen from the longest common prefix among all the files. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./index.html ./about/index.html ./about/foo/index.html ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Routes: / ./index.html /about ./about/index.html /about/foo ./about/foo/index.html Press h + Enter to show shortcuts ``` ## JavaScript, TypeScript, and JSX Bun's transpiler natively implements JavaScript, TypeScript, and JSX support. Learn more about loaders in Bun. Bun's transpiler is also used at runtime. ### ES Modules & CommonJS You can use ESM and CJS in your JavaScript, TypeScript, and JSX files. Bun will handle the transpilation and bundling automatically. There is no pre-build or separate optimization step. It's all done at the same time. Learn more about module resolution in Bun. ## CSS Bun's CSS parser is also natively implemented (clocking in around 58,000 lines of Zig). It's also a CSS bundler. You can use `@import` in your CSS files to import other CSS files. For example: ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} @import "./abc.css"; .container { background-color: blue; } ``` ```css abc.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-color: red; } ``` This outputs: ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-color: red; } .container { background-color: blue; } ``` ### Referencing local assets in CSS You can reference local assets in your CSS files. ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-image: url("./logo.png"); } ``` This will copy `./logo.png` to the output directory and rewrite the path in the CSS file to include a content hash. ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-image: url("./logo-[ABC123].png"); } ``` ### Importing CSS in JavaScript To associate a CSS file with a JavaScript file, you can import it in your JavaScript file. ```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import "./styles.css"; import "./more-styles.css"; ``` This generates `./app.css` and `./app.js` in the output directory. All CSS files imported from JavaScript will be bundled into a single CSS file per entry point. If you import the same CSS file from multiple JavaScript files, it will only be included once in the output CSS file. ## Plugins The dev server supports plugins. ### Tailwind CSS To use TailwindCSS, install the `bun-plugin-tailwind` plugin: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # Or any npm client bun install --dev bun-plugin-tailwind ``` Then, add the plugin to your `bunfig.toml`: ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] plugins = ["bun-plugin-tailwind"] ``` Then, reference TailwindCSS in your HTML via `` tag, `@import` in CSS, or import in JavaScript. ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} @import "tailwindcss"; ``` ```ts title="app.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import "tailwindcss"; ``` Only one of those are necessary, not all three. ## Inline environment variables Bun can replace `process.env.*` references in your JavaScript and TypeScript with their actual values at build time. This is useful for injecting configuration like API URLs or feature flags into your frontend code. ### Dev server (runtime) To inline environment variables when using `bun ./index.html`, configure the `env` option in your `bunfig.toml`: ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended) # env = "inline" # inline all environment variables # env = "disable" # disable env var replacement (default) ``` This only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env = process.env; env.FOO`. If an environment variable is not set, you may see runtime errors like `ReferenceError: process is not defined` in the browser. Then run the dev server: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} PUBLIC_API_URL=https://api.example.com bun ./index.html ``` ### Build for production When building static HTML for production, use the `env` option to inline environment variables: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # Inline all environment variables bun build ./index.html --outdir=dist --env=inline # Only inline env vars with a specific prefix (recommended) bun build ./index.html --outdir=dist --env=PUBLIC_* ``` ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} // Inline all environment variables await Bun.build({ entrypoints: ["./index.html"], outdir: "./dist", env: "inline", // [!code highlight] }); // Only inline env vars with a specific prefix (recommended) await Bun.build({ entrypoints: ["./index.html"], outdir: "./dist", env: "PUBLIC_*", // [!code highlight] }); ``` ### Example Given this source file: ```ts title="app.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} const apiUrl = process.env.PUBLIC_API_URL; console.log(`API URL: ${apiUrl}`); ``` And running with `PUBLIC_API_URL=https://api.example.com`: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} PUBLIC_API_URL=https://api.example.com bun build ./index.html --outdir=dist --env=PUBLIC_* ``` The bundled output will contain: ```js title="dist/app.js" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}} const apiUrl = "https://api.example.com"; console.log(`API URL: ${apiUrl}`); ``` ## Echo console logs from browser to terminal Bun's dev server supports streaming console logs from the browser to the terminal. To enable, pass the `--console` CLI flag. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./index.html --console ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Press h + Enter to show shortcuts ``` Each call to `console.log` or `console.error` will be broadcast to the terminal that started the server. This is useful to see errors from the browser in the same place you run your server. This is also useful for AI agents that watch terminal output. Internally, this reuses the existing WebSocket connection from hot module reloading to send the logs. ## Edit files in the browser Bun's frontend dev server has support for Automatic Workspace Folders in Chrome DevTools, which lets you save edits to files in the browser. ## Keyboard Shortcuts While the server is running: * `o + Enter` - Open in browser * `c + Enter` - Clear console * `q + Enter` (or `Ctrl+C`) - Quit server ## Build for Production When you're ready to deploy, use `bun build` to create optimized production bundles: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.html --minify --outdir=dist ``` ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.html"], outdir: "./dist", minify: true, }); ``` Currently, plugins are only supported through `Bun.build`'s API or through `bunfig.toml` with the frontend dev server * not yet supported in `bun build`'s CLI. ### Watch Mode You can run `bun build --watch` to watch for changes and rebuild automatically. This works nicely for library development. You've never seen a watch mode this fast. ## Plugin API Need more control? Configure the bundler through the JavaScript API and use Bun's builtin `HTMLRewriter` to preprocess HTML. ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.html"], outdir: "./dist", minify: true, plugins: [ { // A plugin that makes every HTML tag lowercase name: "lowercase-html-plugin", setup({ onLoad }) { const rewriter = new HTMLRewriter().on("*", { element(element) { element.tagName = element.tagName.toLowerCase(); }, text(element) { element.replace(element.text.toLowerCase()); }, }); onLoad({ filter: /\.html$/ }, async args => { const html = await Bun.file(args.path).text(); return { // Bun's bundler will scan the HTML for ``` It will output a new HTML file with the bundled assets: ```html title="dist/index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} Local image External image ``` Under the hood, it uses [`lol-html`](https://github.com/cloudflare/lol-html) to extract script and link tags as entrypoints, and other assets as external. Currently, the list of selectors is: * `audio[src]` * `iframe[src]` * `img[src]` * `img[srcset]` * `link:not([rel~='stylesheet']):not([rel~='modulepreload']):not([rel~='manifest']):not([rel~='icon']):not([rel~='apple-touch-icon'])[href]` * `link[as='font'][href], link[type^='font/'][href]` * `link[as='image'][href]` * `link[as='style'][href]` * `link[as='video'][href], link[as='audio'][href]` * `link[as='worker'][href]` * `link[rel='icon'][href], link[rel='apple-touch-icon'][href]` * `link[rel='manifest'][href]` * `link[rel='stylesheet'][href]` * `script[src]` * `source[src]` * `source[srcset]` * `video[poster]` * `video[src]` **HTML Loader Behavior in Different Contexts** The `html` loader behaves differently depending on how it's used: * Static Build: When you run `bun build ./index.html`, Bun produces a static site with all assets bundled and hashed. * Runtime: When you run `bun run server.ts` (where `server.ts` imports an HTML file), Bun bundles assets on-the-fly during development, enabling features like hot module replacement. * Full-stack Build: When you run `bun build --target=bun server.ts` (where `server.ts` imports an HTML file), the import resolves to a manifest object that `Bun.serve` uses to efficiently serve pre-bundled assets in production. *** ### `css` **CSS loader.** Default for `.css`. CSS files can be directly imported. The bundler will parse and bundle CSS files, handling `@import` statements and `url()` references. ```js theme={"theme":{"light":"github-light","dark":"dracula"}} import "./styles.css"; ``` During bundling, all imported CSS files are bundled together into a single `.css` file in the output directory. ```css theme={"theme":{"light":"github-light","dark":"dracula"}} .my-class { background: url("./image.png"); } ``` *** ### `sh` **Bun Shell loader.** Default for `.sh` files. This loader is used to parse Bun Shell scripts. It's only supported when starting Bun itself, so it's not available in the bundler or in the runtime. ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun run ./script.sh ``` *** ### `file` **File loader.** Default for all unrecognized file types. The file loader resolves the import as a path/URL to the imported file. It's commonly used for referencing media or font assets. ```js theme={"theme":{"light":"github-light","dark":"dracula"}} // logo.ts import logo from "./logo.svg"; console.log(logo); ``` In the runtime, Bun checks that the `logo.svg` file exists and converts it to an absolute path to the location of `logo.svg` on disk. ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun run logo.ts # Output: /path/to/project/logo.svg ``` In the bundler, things are slightly different. The file is copied into `outdir` as-is, and the import is resolved as a relative path pointing to the copied file. ```js theme={"theme":{"light":"github-light","dark":"dracula"}} // Output var logo = "./logo.svg"; console.log(logo); ``` If a value is specified for `publicPath`, the import will use value as a prefix to construct an absolute path/URL. | Public path | Resolved import | | ---------------------------- | ---------------------------------- | | `""` (default) | `/logo.svg` | | `"/assets"` | `/assets/logo.svg` | | `"https://cdn.example.com/"` | `https://cdn.example.com/logo.svg` | The location and file name of the copied file is determined by the value of `naming.asset`. This loader is copied into the `outdir` as-is. The name of the copied file is determined using the value of `naming.asset`. # Macros Source: https://bun.com/docs/bundler/macros Run JavaScript functions at bundle-time with Bun macros Macros are a mechanism for running JavaScript functions at bundle-time. The value returned from these functions are directly inlined into your bundle. As a toy example, consider this simple function that returns a random number. ```ts title="random.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export function random() { return Math.random(); } ``` This is a regular function in a regular file, but you can use it as a macro like so: ```tsx title="cli.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { random } from "./random.ts" with { type: "macro" }; console.log(`Your random number is ${random()}`); ``` Macros are indicated using import attribute syntax. If you haven't seen this syntax before, it's a Stage 3 TC39 proposal that lets you attach additional metadata to import statements. Now we'll bundle this file with `bun build`. The bundled file will be printed to stdout. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./cli.tsx ``` ```js theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(`Your random number is ${0.6805550949689833}`); ``` As you can see, the source code of the `random` function occurs nowhere in the bundle. Instead, it is executed during bundling and function call (`random()`) is replaced with the result of the function. Since the source code will never be included in the bundle, macros can safely perform privileged operations like reading from a database. ## When to use macros If you have several build scripts for small things where you would otherwise have a one-off build script, bundle-time code execution can be easier to maintain. It lives with the rest of your code, it runs with the rest of the build, it is automatically parallelized, and if it fails, the build fails too. If you find yourself running a lot of code at bundle-time though, consider running a server instead. ## Import attributes Bun Macros are import statements annotated using either: * `with { type: 'macro' }` — an import attribute, a Stage 3 ECMA Script proposal * `assert { type: 'macro' }` — an import assertion, an earlier incarnation of import attributes that has now been abandoned (but is already supported by a number of browsers and runtimes) ## Security considerations Macros must explicitly be imported with `{ type: "macro" }` in order to be executed at bundle-time. These imports have no effect if they are not called, unlike regular JavaScript imports which may have side effects. You can disable macros entirely by passing the `--no-macros` flag to Bun. It produces a build error like this: ``` error: Macros are disabled foo(); ^ ./hello.js:3:1 53 ``` To reduce the potential attack surface for malicious packages, macros cannot be invoked from inside `node_modules/**/*`. If a package attempts to invoke a macro, you'll see an error like this: ``` error: For security reasons, macros cannot be run from node_modules. beEvil(); ^ node_modules/evil/index.js:3:1 50 ``` Your application code can still import macros from `node_modules` and invoke them. ```ts title="cli.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { macro } from "some-package" with { type: "macro" }; macro(); ``` ## Export condition "macro" When shipping a library containing a macro to npm or another package registry, use the `"macro"` export condition to provide a special version of your package exclusively for the macro environment. ```json title="package.json" icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}} { "name": "my-package", "exports": { "import": "./index.js", "require": "./index.js", "default": "./index.js", "macro": "./index.macro.js" } } ``` With this configuration, users can consume your package at runtime or at bundle-time using the same import specifier: ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import pkg from "my-package"; // runtime import import { macro } from "my-package" with { type: "macro" }; // macro import ``` The first import will resolve to `./node_modules/my-package/index.js`, while the second will be resolved by Bun's bundler to `./node_modules/my-package/index.macro.js`. ## Execution When Bun's transpiler sees a macro import, it calls the function inside the transpiler using Bun's JavaScript runtime and converts the return value from JavaScript into an AST node. These JavaScript functions are called at bundle-time, not runtime. Macros are executed synchronously in the transpiler during the visiting phase—before plugins and before the transpiler generates the AST. They are executed in the order they are imported. The transpiler will wait for the macro to finish executing before continuing. The transpiler will also await any Promise returned by a macro. Bun's bundler is multi-threaded. As such, macros execute in parallel inside of multiple spawned JavaScript "workers". ## Dead code elimination The bundler performs dead code elimination after running and inlining macros. So given the following macro: ```ts title="returnFalse.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export function returnFalse() { return false; } ``` ...then bundling the following file will produce an empty bundle, provided that the minify syntax option is enabled. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { returnFalse } from "./returnFalse.ts" with { type: "macro" }; if (returnFalse()) { console.log("This code is eliminated"); } ``` ## Serializability Bun's transpiler needs to be able to serialize the result of the macro so it can be inlined into the AST. All JSON-compatible data structures are supported: ```ts title="macro.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export function getObject() { return { foo: "bar", baz: 123, array: [1, 2, { nested: "value" }], }; } ``` Macros can be async, or return Promise instances. Bun's transpiler will automatically await the Promise and inline the result. ```ts title="macro.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export async function getText() { return "async value"; } ``` The transpiler implements special logic for serializing common data formats like `Response`, `Blob`, `TypedArray`. * **TypedArray**: Resolves to a base64-encoded string. * **Response**: Bun will read the `Content-Type` and serialize accordingly; for instance, a Response with type `application/json` will be automatically parsed into an object and `text/plain` will be inlined as a string. Responses with an unrecognized or undefined type will be base-64 encoded. * **Blob**: As with Response, the serialization depends on the `type` property. The result of `fetch` is `Promise`, so it can be directly returned. ```ts title="macro.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export function getObject() { return fetch("https://bun.com"); } ``` Functions and instances of most classes (except those mentioned above) are not serializable. ```ts title="macro.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export function getText(url: string) { // this doesn't work! return () => {}; } ``` ## Arguments Macros can accept inputs, but only in limited cases. The value must be statically known. For example, the following is not allowed: ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { getText } from "./getText.ts" with { type: "macro" }; export function howLong() { // the value of `foo` cannot be statically known const foo = Math.random() ? "foo" : "bar"; const text = getText(`https://example.com/${foo}`); console.log("The page is ", text.length, " characters long"); } ``` However, if the value of `foo` is known at bundle-time (say, if it's a constant or the result of another macro) then it's allowed: ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { getText } from "./getText.ts" with { type: "macro" }; import { getFoo } from "./getFoo.ts" with { type: "macro" }; export function howLong() { // this works because getFoo() is statically known const foo = getFoo(); const text = getText(`https://example.com/${foo}`); console.log("The page is", text.length, "characters long"); } ``` This outputs: ```js theme={"theme":{"light":"github-light","dark":"dracula"}} function howLong() { console.log("The page is", 1322, "characters long"); } export { howLong }; ``` ## Examples ### Embed latest git commit hash ```ts title="getGitCommitHash.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export function getGitCommitHash() { const { stdout } = Bun.spawnSync({ cmd: ["git", "rev-parse", "HEAD"], stdout: "pipe", }); return stdout.toString(); } ``` When we build it, the `getGitCommitHash` is replaced with the result of calling the function: ```ts input theme={"theme":{"light":"github-light","dark":"dracula"}} import { getGitCommitHash } from "./getGitCommitHash.ts" with { type: "macro" }; console.log(`The current Git commit hash is ${getGitCommitHash()}`); ``` ```ts output theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(`The current Git commit hash is 3ee3259104e4507cf62c160f0ff5357ec4c7a7f8`); ``` You're probably thinking "Why not just use `process.env.GIT_COMMIT_HASH`?" Well, you can do that too. But can you do this with an environment variable? ### Make fetch() requests at bundle-time In this example, we make an outgoing HTTP request using `fetch()`, parse the HTML response using `HTMLRewriter`, and return an object containing the title and meta tags–all at bundle-time. ```ts title="meta.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} export async function extractMetaTags(url: string) { const response = await fetch(url); const meta = { title: "", }; new HTMLRewriter() .on("title", { text(element) { meta.title += element.text; }, }) .on("meta", { element(element) { const name = element.getAttribute("name") || element.getAttribute("property") || element.getAttribute("itemprop"); if (name) meta[name] = element.getAttribute("content"); }, }) .transform(response); return meta; } ``` The `extractMetaTags` function is erased at bundle-time and replaced with the result of the function call. This means that the fetch request happens at bundle-time, and the result is embedded in the bundle. Also, the branch throwing the error is eliminated since it's unreachable. ```jsx input theme={"theme":{"light":"github-light","dark":"dracula"}} import { extractMetaTags } from "./meta.ts" with { type: "macro" }; export const Head = () => { const headTags = extractMetaTags("https://example.com"); if (headTags.title !== "Example Domain") { throw new Error("Expected title to be 'Example Domain'"); } return ( {headTags.title} ); }; ``` ```jsx output theme={"theme":{"light":"github-light","dark":"dracula"}} export const Head = () => { const headTags = { title: "Example Domain", viewport: "width=device-width, initial-scale=1", }; return ( {headTags.title} ); }; ``` # Minifier Source: https://bun.com/docs/bundler/minifier Reduce bundle sizes with Bun's JavaScript and TypeScript minifier Bun includes a fast JavaScript and TypeScript minifier that can reduce bundle sizes by 80% or more (depending on the codebase) and make output code run faster. The minifier performs dozens of optimizations including constant folding, dead code elimination, and syntax transformations. Unlike other minifiers, Bun's minifier makes `bun build` run faster since there's less code to print. ## CLI Usage ### Enable all minification Use the `--minify` flag to enable all minification modes: ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.ts --minify --outfile=out.js ``` The `--minify` flag automatically enables: * Whitespace minification * Syntax minification * Identifier minification ### Production mode The `--production` flag automatically enables minification: ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.ts --production --outfile=out.js ``` The `--production` flag also: * Sets `process.env.NODE_ENV` to `production` * Enables the production-mode JSX import & transform ### Granular control You can enable specific minification modes individually: ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} # Only remove whitespace bun build ./index.ts --minify-whitespace --outfile=out.js # Only minify syntax bun build ./index.ts --minify-syntax --outfile=out.js # Only minify identifiers bun build ./index.ts --minify-identifiers --outfile=out.js # Combine specific modes bun build ./index.ts --minify-whitespace --minify-syntax --outfile=out.js ``` ## JavaScript API When using Bun's bundler programmatically, configure minification through the `minify` option: ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], outdir: "./out", minify: true, // Enable all minification modes }); ``` For granular control, pass an object: ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], outdir: "./out", minify: { whitespace: true, syntax: true, identifiers: true, }, }); ``` ## Minification Modes Bun's minifier has three independent modes that can be enabled separately or combined. ### Whitespace minification (`--minify-whitespace`) Removes all unnecessary whitespace, newlines, and formatting from the output. ### Syntax minification (`--minify-syntax`) Rewrites JavaScript syntax to shorter equivalent forms and performs constant folding, dead code elimination, and other optimizations. ### Identifier minification (`--minify-identifiers`) Renames local variables and function names to shorter identifiers using frequency-based optimization. ## All Transformations ### Boolean literal shortening **Mode:** `--minify-syntax` Converts boolean literals to shorter expressions. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} true false ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} !0 !1 ``` ### Boolean algebra optimizations **Mode:** `--minify-syntax` Simplifies boolean expressions using logical rules. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} !!x x === true x && true x || false !true !false ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x x x x !1 !0 ``` ### Undefined shortening **Mode:** `--minify-syntax` Replaces `undefined` with shorter equivalent. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} undefined let x = undefined; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} void 0 let x=void 0; ``` ### Undefined equality optimization **Mode:** `--minify-syntax` Optimizes loose equality checks with undefined. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} x == undefined x != undefined ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x == null x != null ``` ### Infinity shortening **Mode:** `--minify-syntax` Converts Infinity to mathematical expressions. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} Infinity -Infinity ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 1/0 -1/0 ``` ### Typeof optimizations **Mode:** `--minify-syntax` Optimizes typeof comparisons and evaluates constant typeof expressions. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} typeof x === 'undefined' typeof x !== 'undefined' typeof require typeof null typeof true typeof 123 typeof "str" typeof 123n ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} typeof x>'u' typeof x<'u' "function" "object" "boolean" "number" "string" "bigint" ``` ### Number formatting **Mode:** `--minify-syntax` Formats numbers in the most compact representation. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} 10000 100000 1000000 1.0 -42.0 ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 1e4 1e5 1e6 1 -42 ``` ### Arithmetic constant folding **Mode:** `--minify-syntax` Evaluates arithmetic operations at compile time. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} 1 + 2 10 - 5 3 * 4 10 / 2 10 % 3 2 ** 3 ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 3 5 12 5 1 8 ``` ### Bitwise constant folding **Mode:** `--minify-syntax` Evaluates bitwise operations at compile time. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} 5 & 3 5 | 3 5 ^ 3 8 << 2 32 >> 2 ~5 ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 1 7 6 32 8 -6 ``` ### String concatenation **Mode:** `--minify-syntax` Combines string literals at compile time. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} "a" + "b" "x" + 123 "foo" + "bar" + "baz" ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "ab" "x123" "foobarbaz" ``` ### String indexing **Mode:** `--minify-syntax` Evaluates string character access at compile time. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} "foo"[2] "hello"[0] ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "o" "h" ``` ### Template literal folding **Mode:** `--minify-syntax` Evaluates template literals with constant expressions. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} `a${123}b` `result: ${5 + 10}` ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "a123b" "result: 15" ``` ### Template literal to string conversion **Mode:** `--minify-syntax` Converts simple template literals to regular strings. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} `Hello World` `Line 1 Line 2` ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "Hello World" "Line 1\nLine 2" ``` ### String quote optimization **Mode:** `--minify-syntax` Chooses the optimal quote character to minimize escapes. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} "It's a string" 'He said "hello"' `Simple string` ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "It's a string" 'He said "hello"' "Simple string" ``` ### Array spread inlining **Mode:** `--minify-syntax` Inlines array spread operations with constant arrays. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} [1, ...[2, 3], 4] [...[a, b]] ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} [1,2,3,4] [a,b] ``` ### Array indexing **Mode:** `--minify-syntax` Evaluates constant array access at compile time. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} [x][0] ['a', 'b', 'c'][1] ['a', , 'c'][1] ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x 'b' void 0 ``` ### Property access optimization **Mode:** `--minify-syntax` Converts bracket notation to dot notation when possible. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} obj["property"] obj["validName"] obj["123"] obj["invalid-name"] ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} obj.property obj.validName obj["123"] obj["invalid-name"] ``` ### Comparison folding **Mode:** `--minify-syntax` Evaluates constant comparisons at compile time. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} 3 < 5 5 > 3 3 <= 3 5 >= 6 "a" < "b" ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} !0 !0 !0 !1 !0 ``` ### Logical operation folding **Mode:** `--minify-syntax` Simplifies logical operations with constant values. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} true && x false && x true || x false || x ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x !1 !0 x ``` ### Nullish coalescing folding **Mode:** `--minify-syntax` Evaluates nullish coalescing with known values. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} null ?? x undefined ?? x 42 ?? x ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x x 42 ``` ### Comma expression simplification **Mode:** `--minify-syntax` Removes side-effect-free expressions from comma sequences. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} (0, x) (123, "str", x) ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x x ``` ### Ternary conditional folding **Mode:** `--minify-syntax` Evaluates conditional expressions with constant conditions. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} true ? a : b false ? a : b x ? true : false x ? false : true ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} a b x ? !0 : !1 x ? !1 : !0 ``` ### Unary expression folding **Mode:** `--minify-syntax` Simplifies unary operations. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} +123 +"123" -(-x) ~~x !!x ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 123 123 123 123 x ~~x !!x x ``` ### Double negation removal **Mode:** `--minify-syntax` Removes unnecessary double negations. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} !!x !!!x ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x !x ``` ### If statement optimization **Mode:** `--minify-syntax` Optimizes if statements with constant conditions. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} if (true) x; if (false) x; if (x) { a; } if (x) {} else y; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x; // removed if(x)a; if(!x)y; ``` ### Dead code elimination **Mode:** `--minify-syntax` Removes unreachable code and code without side effects. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} if (false) { unreachable(); } function foo() { return x; deadCode(); } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function foo(){return x} ``` ### Unreachable branch removal **Mode:** `--minify-syntax` Removes branches that can never execute. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} while (false) { neverRuns(); } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} // removed entirely ``` ### Empty block removal **Mode:** `--minify-syntax` Removes empty blocks and unnecessary braces. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} { } if (x) { } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} ; // removed ``` ### Single statement block unwrapping **Mode:** `--minify-syntax` Removes unnecessary braces around single statements. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} if (condition) { doSomething(); } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} if(condition)doSomething(); ``` ### TypeScript enum inlining **Mode:** `--minify-syntax` Inlines TypeScript enum values at compile time. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} enum Color { Red, Green, Blue } const x = Color.Red; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} const x=0; ``` ### Pure annotation support **Mode:** Always active Respects `/*@__PURE__*/` annotations for tree shaking. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} const x = /*@__PURE__*/ expensive(); // If x is unused... ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} // removed entirely ``` ### Identifier renaming **Mode:** `--minify-identifiers` Renames local variables to shorter names based on usage frequency. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} function calculateSum(firstNumber, secondNumber) { const result = firstNumber + secondNumber; return result; } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function a(b,c){const d=b+c;return d} ``` **Naming strategy:** * Most frequently used identifiers get the shortest names (a, b, c...) * Single letters: a-z (26 names) * Double letters: aa-zz (676 names) * Triple letters and beyond as needed **Preserved identifiers:** * JavaScript keywords and reserved words * Global identifiers * Named exports (to maintain API) * CommonJS names: `exports`, `module` ### Whitespace removal **Mode:** `--minify-whitespace` Removes all unnecessary whitespace. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} function add(a, b) { return a + b; } let x = 10; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function add(a,b){return a+b;}let x=10; ``` ### Semicolon optimization **Mode:** `--minify-whitespace` Inserts semicolons only when necessary. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} let a = 1; let b = 2; return a + b; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} let a=1;let b=2;return a+b ``` ### Operator spacing removal **Mode:** `--minify-whitespace` Removes spaces around operators. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} a + b x = y * z foo && bar || baz ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} a+b x=y*z foo&&bar||baz ``` ### Comment removal **Mode:** `--minify-whitespace` Removes comments except important license comments. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} // This comment is removed /* So is this */ /*! But this license comment is kept */ function test() { /* inline comment */ } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} /*! But this license comment is kept */ function test(){} ``` ### Object and array formatting **Mode:** `--minify-whitespace` Removes whitespace in object and array literals. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} const obj = { name: "John", age: 30 }; const arr = [1, 2, 3]; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} const obj={name:"John",age:30};const arr=[1,2,3]; ``` ### Control flow formatting **Mode:** `--minify-whitespace` Removes whitespace in control structures. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} if (condition) { doSomething(); } for (let i = 0; i < 10; i++) { console.log(i); } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} if(condition)doSomething();for(let i=0;i<10;i++)console.log(i); ``` ### Function formatting **Mode:** `--minify-whitespace` Removes whitespace in function declarations. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} function myFunction(param1, param2) { return param1 + param2; } const arrow = (a, b) => a + b; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function myFunction(a,b){return a+b}const arrow=(a,b)=>a+b; ``` ### Parentheses minimization **Mode:** Always active Only adds parentheses when necessary for operator precedence. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} (a + b) * c a + (b * c) ((x)) ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} (a+b)*c a+b*c x ``` ### Property mangling **Mode:** `--minify-identifiers` (with configuration) Renames object properties to shorter names when configured. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} obj.longPropertyName ``` ```js Output (with property mangling enabled) theme={"theme":{"light":"github-light","dark":"dracula"}} obj.a ``` ### Template literal value folding **Mode:** `--minify-syntax` Converts non-string interpolated values to strings and folds them into the template. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} `hello ${123}` `value: ${true}` `result: ${null}` `status: ${undefined}` `big: ${10n}` ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "hello 123" "value: true" "result: null" "status: undefined" "big: 10" ``` ### String length constant folding **Mode:** `--minify-syntax` Evaluates `.length` property on string literals at compile time. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} "hello world".length "test".length ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 11 4 ``` ### Constructor call simplification **Mode:** `--minify-syntax` Simplifies constructor calls for built-in types. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} new Object() new Object(null) new Object({a: 1}) new Array() new Array(x, y) ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} {} {} {a:1} [] [x,y] ``` ### Single property object inlining **Mode:** `--minify-syntax` Inlines property access for objects with a single property. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} ({fn: () => console.log('hi')}).fn() ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} (() => console.log('hi'))() ``` ### String charCodeAt constant folding **Mode:** Always active Evaluates `charCodeAt()` on string literals for ASCII characters. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} "hello".charCodeAt(1) "A".charCodeAt(0) ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 101 65 ``` ### Void 0 equality to null equality **Mode:** `--minify-syntax` Converts loose equality checks with `void 0` to `null` since they're equivalent. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} x == void 0 x != void 0 ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x == null x != null ``` ### Negation operator optimization **Mode:** `--minify-syntax` Moves negation operator through comma expressions. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} -(a, b) -(x, y, z) ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} a,-b x,y,-z ``` ### Import.meta property inlining **Mode:** Bundle mode Inlines `import.meta` properties at build time when values are known. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} import.meta.dir import.meta.file import.meta.path import.meta.url ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "/path/to/directory" "filename.js" "/full/path/to/file.js" "file:///full/path/to/file.js" ``` ### Variable declaration merging **Mode:** `--minify-syntax` Merges adjacent variable declarations of the same type. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} let a = 1; let b = 2; const c = 3; const d = 4; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} let a=1,b=2; const c=3,d=4; ``` ### Expression statement merging **Mode:** `--minify-syntax` Merges adjacent expression statements using comma operator. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(1); console.log(2); console.log(3); ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(1),console.log(2),console.log(3); ``` ### Return statement merging **Mode:** `--minify-syntax` Merges expressions before return with comma operator. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(x); return y; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} return console.log(x),y; ``` ### Throw statement merging **Mode:** `--minify-syntax` Merges expressions before throw with comma operator. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(x); throw new Error(); ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} throw(console.log(x),new Error()); ``` ### TypeScript enum cross-module inlining **Mode:** `--minify-syntax` (bundle mode) Inlines enum values across module boundaries. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} // lib.ts export enum Color { Red, Green, Blue } // Input (main.ts) import { Color } from './lib'; const x = Color.Red; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} const x=0; ``` ### Computed property enum inlining **Mode:** `--minify-syntax` Inlines enum values used as computed object properties. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} enum Keys { FOO = 'foo' } const obj = { [Keys.FOO]: value } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} const obj={foo:value} ``` ### String number to numeric index **Mode:** `--minify-syntax` Converts string numeric property access to numeric index. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} obj["0"] arr["5"] ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} obj[0] arr[5] ``` ### Arrow function body shortening **Mode:** Always active Uses expression body syntax when an arrow function only returns a value. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} () => { return x; } (a) => { return a + 1; } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} () => x a => a + 1 ``` ### Object property shorthand **Mode:** Always active Uses shorthand syntax when property name and value identifier match. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} { x: x, y: y } { name: name, age: age } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} { x, y } { name, age } ``` ### Method shorthand **Mode:** Always active Uses method shorthand syntax in object literals. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} { foo: function() {}, bar: async function() {} } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} { foo() {}, async bar() {} } ``` ### Drop debugger statements **Mode:** `--drop=debugger` Removes `debugger` statements from code. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} function test() { debugger; return x; } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function test(){return x} ``` ### Drop console calls **Mode:** `--drop=console` Removes all `console.*` method calls from code. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("debug"); console.warn("warning"); x = console.error("error"); ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} void 0; void 0; x=void 0; ``` ### Drop custom function calls **Mode:** `--drop=` Removes calls to specified global functions or methods. ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} assert(condition); obj.assert(test); ``` ```js Output with --drop=assert theme={"theme":{"light":"github-light","dark":"dracula"}} void 0; void 0; ``` ## Keep Names When minifying identifiers, you may want to preserve original function and class names for debugging purposes. Use the `--keep-names` flag: ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.ts --minify --keep-names --outfile=out.js ``` Or in the JavaScript API: ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], outdir: "./out", minify: { identifiers: true, keepNames: true, }, }); ``` This preserves the `.name` property on functions and classes while still minifying the actual identifier names in the code. ## Combined Example Using all three minification modes together: ```ts input.ts (158 bytes) theme={"theme":{"light":"github-light","dark":"dracula"}} const myVariable = 42; const myFunction = () => { const isValid = true; const result = undefined; return isValid ? myVariable : result; }; const output = myFunction(); ``` ```js output.js theme={"theme":{"light":"github-light","dark":"dracula"}} // Output with --minify (49 bytes, 69% reduction) const a=42,b=()=>{const c=!0,d=void 0;return c?a:d},e=b(); ``` ## When to Use Minification **Use `--minify` for:** * Production bundles * Reducing CDN bandwidth costs * Improving page load times **Use individual modes for:** * **`--minify-whitespace`:** Quick size reduction without semantic changes * **`--minify-syntax`:** Smaller output while keeping readable identifiers for debugging * **`--minify-identifiers`:** Maximum size reduction (combine with `--keep-names` for better stack traces) **Avoid minification for:** * Development builds (harder to debug) * When you need readable error messages * Libraries where consumers may read the source # Plugins Source: https://bun.com/docs/bundler/plugins Universal plugin API for extending Bun's runtime and bundler Bun provides a universal plugin API that can be used to extend both the runtime and bundler. Plugins intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to add support for additional file types, like `.scss` or `.yaml`. In the context of Bun's bundler, plugins can be used to implement framework-level features like CSS extraction, macros, and client-server code co-location. ## Lifecycle hooks Plugins can register callbacks to be run at various points in the lifecycle of a bundle: * `onStart()`: Run once the bundler has started a bundle * `onResolve()`: Run before a module is resolved * `onLoad()`: Run before a module is loaded * `onBeforeParse()`: Run zero-copy native addons in the parser thread before a file is parsed * `onEnd()`: Run after the bundle is complete ## Reference A rough overview of the types (please refer to Bun's `bun.d.ts` for the full type definitions): ```ts title="bun.d.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} type PluginBuilder = { onStart(callback: () => void): void; onResolve: ( args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; importer: string }) => { path: string; namespace?: string; } | void, ) => void; onLoad: ( args: { filter: RegExp; namespace?: string }, defer: () => Promise, callback: (args: { path: string }) => { loader?: Loader; contents?: string; exports?: Record; }, ) => void; onEnd(callback: (result: BuildOutput) => void | Promise): void; config: BuildConfig; }; type Loader = | "js" | "jsx" | "ts" | "tsx" | "json" | "jsonc" | "toml" | "yaml" | "file" | "napi" | "wasm" | "text" | "css" | "html"; ``` ## Usage A plugin is defined as a JavaScript object containing a `name` property and a `setup` function. ```ts title="myPlugin.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "Custom loader", setup(build) { // implementation }, }; ``` This plugin can be passed into the `plugins` array when calling `Bun.build`. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./app.ts"], outdir: "./out", plugins: [myPlugin], }); ``` ## Plugin lifecycle ### Namespaces `onLoad` and `onResolve` accept an optional `namespace` string. What is a namespace? Every module has a namespace. Namespaces are used to prefix the import in transpiled code; for instance, a loader with a `filter: /\.yaml$/` and `namespace: "yaml:"` will transform an import from `./myfile.yaml` into `yaml:./myfile.yaml`. The default namespace is `"file"` and it is not necessary to specify it, for instance: `import myModule from "./my-module.ts"` is the same as `import myModule from "file:./my-module.ts"`. Other common namespaces are: * `"bun"`: for Bun-specific modules (e.g. `"bun:test"`, `"bun:sqlite"`) * `"node"`: for Node.js modules (e.g. `"node:fs"`, `"node:path"`) ### onStart ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onStart(callback: () => void): Promise | void; ``` Registers a callback to be run when the bundler starts a new bundle. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { plugin } from "bun"; plugin({ name: "onStart example", setup(build) { build.onStart(() => { console.log("Bundle started!"); }); }, }); ``` The callback can return a Promise. After the bundle process has initialized, the bundler waits until all `onStart()` callbacks have completed before continuing. For example: ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} const result = await Bun.build({ entrypoints: ["./app.ts"], outdir: "./dist", sourcemap: "external", plugins: [ { name: "Sleep for 10 seconds", setup(build) { build.onStart(async () => { await Bun.sleep(10_000); }); }, }, { name: "Log bundle time to a file", setup(build) { build.onStart(async () => { const now = Date.now(); await Bun.$`echo ${now} > bundle-time.txt`; }); }, }, ], }); ``` In the above example, Bun will wait until the first `onStart()` (sleeping for 10 seconds) has completed, as well as the second `onStart()` (writing the bundle time to a file). `onStart()` callbacks (like every other lifecycle callback) do not have the ability to modify the `build.config` object. If you want to mutate `build.config`, you must do so directly in the `setup()` function. ### onResolve ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onResolve( args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; importer: string }) => { path: string; namespace?: string; } | void, ): void; ``` To bundle your project, Bun walks down the dependency tree of all modules in your project. For each imported module, Bun actually has to find and read that module. The "finding" part is known as "resolving" a module. The `onResolve()` plugin lifecycle callback allows you to configure how a module is resolved. The first argument to `onResolve()` is an object with a `filter` and `namespace` property. The `filter` is a regular expression which is run on the import string. Effectively, these allow you to filter which modules your custom resolution logic will apply to. The second argument to `onResolve()` is a callback which is run for each module import Bun finds that matches the filter and namespace defined in the first argument. The callback receives as input the path to the matching module. The callback can return a new path for the module. Bun will read the contents of the new path and parse it as a module. For example, redirecting all imports to `images/` to `./public/images/`: ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { plugin } from "bun"; plugin({ name: "onResolve example", setup(build) { build.onResolve({ filter: /.*/, namespace: "file" }, args => { if (args.path.startsWith("images/")) { return { path: args.path.replace("images/", "./public/images/"), }; } }); }, }); ``` ### onLoad ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onLoad( args: { filter: RegExp; namespace?: string }, defer: () => Promise, callback: (args: { path: string, importer: string, namespace: string, kind: ImportKind }) => { loader?: Loader; contents?: string; exports?: Record; }, ): void; ``` After Bun's bundler has resolved a module, it needs to read the contents of the module and parse it. The `onLoad()` plugin lifecycle callback allows you to modify the contents of a module before it is read and parsed by Bun. Like `onResolve()`, the first argument to `onLoad()` allows you to filter which modules this invocation of `onLoad()` will apply to. The second argument to `onLoad()` is a callback which is run for each matching module before Bun loads the contents of the module into memory. This callback receives as input the path to the matching module, the importer of the module (the module that imported the module), the namespace of the module, and the kind of the module. The callback can return a new `contents` string for the module as well as a new `loader`. For example: ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { plugin } from "bun"; const envPlugin: BunPlugin = { name: "env plugin", setup(build) { build.onLoad({ filter: /env/, namespace: "file" }, args => { return { contents: `export default ${JSON.stringify(process.env)}`, loader: "js", }; }); }, }); Bun.build({ entrypoints: ["./app.ts"], outdir: "./dist", plugins: [envPlugin], }); // import env from "env" // env.FOO === "bar" ``` This plugin will transform all imports of the form `import env from "env"` into a JavaScript module that exports the current environment variables. #### .defer() One of the arguments passed to the `onLoad` callback is a `defer` function. This function returns a Promise that is resolved when all other modules have been loaded. This allows you to delay execution of the `onLoad` callback until all other modules have been loaded. This is useful for returning contents of a module that depends on other modules. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import { plugin } from "bun"; plugin({ name: "track imports", setup(build) { const transpiler = new Bun.Transpiler(); let trackedImports: Record = {}; // Each module that goes through this onLoad callback // will record its imports in `trackedImports` build.onLoad({ filter: /\.ts/ }, async ({ path }) => { const contents = await Bun.file(path).arrayBuffer(); const imports = transpiler.scanImports(contents); for (const i of imports) { trackedImports[i.path] = (trackedImports[i.path] || 0) + 1; } return undefined; }); build.onLoad({ filter: /stats\.json/ }, async ({ defer }) => { // Wait for all files to be loaded, ensuring // that every file goes through the above `onLoad()` function // and their imports tracked await defer(); // Emit JSON containing the stats of each import return { contents: `export default ${JSON.stringify(trackedImports)}`, loader: "json", }; }); }, }); ``` The `.defer()` function currently has the limitation that it can only be called once per `onLoad` callback. ## Native plugins One of the reasons why Bun's bundler is so fast is that it is written in native code and leverages multi-threading to load and parse modules in parallel. However, one limitation of plugins written in JavaScript is that JavaScript itself is single-threaded. Native plugins are written as NAPI modules and can be run on multiple threads. This allows native plugins to run much faster than JavaScript plugins. In addition, native plugins can skip unnecessary work such as the UTF-8 -> UTF-16 conversion needed to pass strings to JavaScript. These are the following lifecycle hooks which are available to native plugins: * `onBeforeParse()`: Called on any thread before a file is parsed by Bun's bundler. Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions. To create a native plugin, you must export a C ABI function which matches the signature of the native lifecycle hook you want to implement. ### Creating a native plugin in Rust Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions. To create a native plugin, you must export a C ABI function which matches the signature of the native lifecycle hook you want to implement. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun add -g @napi-rs/cli napi new ``` Then install this crate: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} cargo add bun-native-plugin ``` Now, inside the `lib.rs` file, we'll use the `bun_native_plugin::bun` proc macro to define a function which will implement our native plugin. Here's an example implementing the `onBeforeParse` hook: ```rust title="lib.rs" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/rust.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=c48e2f9ffc38d0c1d77ef723c617aca8" theme={"theme":{"light":"github-light","dark":"dracula"}} use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader}; use napi_derive::napi; /// Define the plugin and its name define_bun_plugin!("replace-foo-with-bar"); /// Here we'll implement `onBeforeParse` with code that replaces all occurrences of /// `foo` with `bar`. /// /// We use the #[bun] macro to generate some of the boilerplate code. /// /// The argument of the function (`handle: &mut OnBeforeParse`) tells /// the macro that this function implements the `onBeforeParse` hook. #[bun] pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> { // Fetch the input source code. let input_source_code = handle.input_source_code()?; // Get the Loader for the file let loader = handle.output_loader(); let output_source_code = input_source_code.replace("foo", "bar"); handle.set_output_source_code(output_source_code, BunLoader::BUN_LOADER_JSX); Ok(()) } ``` And to use it in `Bun.build()`: ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} import myNativeAddon from "./my-native-addon"; Bun.build({ entrypoints: ["./app.tsx"], plugins: [ { name: "my-plugin", setup(build) { build.onBeforeParse( { namespace: "file", filter: "**/*.tsx", }, { napiModule: myNativeAddon, symbol: "replace_foo_with_bar", // external: myNativeAddon.getSharedState() }, ); }, }, ], }); ``` ### onBeforeParse ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onBeforeParse( args: { filter: RegExp; namespace?: string }, callback: { napiModule: NapiModule; symbol: string; external?: unknown }, ): void; ``` This lifecycle callback is run immediately before a file is parsed by Bun's bundler. As input, it receives the file's contents and can optionally return new source code. This callback can be called from any thread and so the napi module implementation must be thread-safe. ### onEnd ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onEnd(callback: (result: BuildOutput) => void | Promise): void; ``` Registers a callback to be run after the bundle is complete. The callback receives the [`BuildOutput`](/docs/bundler#outputs) object containing the build results, including output files and any build messages. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} const result = await Bun.build({ entrypoints: ["./app.ts"], outdir: "./dist", plugins: [ { name: "onEnd example", setup(build) { build.onEnd(result => { console.log(`Build completed with ${result.outputs.length} files`); for (const log of result.logs) { console.log(log); } }); }, }, ], }); ``` The callback can return a `Promise`. The build output promise from `Bun.build()` will not resolve until all `onEnd()` callbacks have completed. ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}} const result = await Bun.build({ entrypoints: ["./app.ts"], outdir: "./dist", plugins: [ { name: "Upload to S3", setup(build) { build.onEnd(async result => { if (!result.success) return; for (const output of result.outputs) { await uploadToS3(output); } }); }, }, ], }); ``` # Standalone HTML Source: https://bun.com/docs/bundler/standalone-html Bundle a single-page app into a single self-contained .html file with no external dependencies Bun can bundle your entire frontend into a **single `.html` file** with zero external dependencies. JavaScript, TypeScript, JSX, CSS, images, fonts, videos, WASM — everything gets inlined into one file. ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=browser ./index.html --outdir=dist ``` The output is a completely self-contained HTML document. No relative paths. No external files. No server required. Just one `.html` file that works anywhere a browser can open it. ## One file. Upload anywhere. The output is a single `.html` file you can put anywhere: * **Upload it to S3** or any static file host — no directory structure to maintain, one file * **Double-click it from your desktop** — it opens in the browser and works offline, no localhost server needed * **Embed it in your webview** — No need to deal with relative files * **Insert it in an `