# 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 improves 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`, the output format defaults to CommonJS:
```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./index.ts --target=bun --bytecode --outdir=./dist
```
The build writes 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 you create an executable with `--compile`, Bun embeds the bytecode in the binary. Both ESM and CommonJS work:
```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 the bytecode.
### ESM bytecode
ESM bytecode requires `--compile` because Bun embeds module metadata (import/export information) in the compiled binary. With this metadata, the JavaScript engine skips parsing entirely at runtime.
Without `--compile`, ESM bytecode would still require parsing the source to analyze module dependencies, which defeats the purpose of bytecode caching.
### Combining with other optimizations
Combine bytecode 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, Bun ignores it and falls back to parsing the JavaScript source.
**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
Bytecode doesn't replace your JavaScript. You must deploy both files:
* 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 whether the bytecode is used, set `BUN_JSC_verboseDiskCache=1` in your environment.
On a cache hit, Bun logs:
```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[Disk cache] cache hit for sourceCode
```
On a cache miss:
```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[Disk cache] cache miss for sourceCode
```
Several cache-miss lines are normal: Bun doesn't bytecode-cache the JavaScript in its 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 whether the startup 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."
All of this happens **every time** you run your code. A CLI tool that runs 100 times a day gets parsed 100 times; a serverless function gets parsed 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 an 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 the engine would otherwise parse lazily.
## The bytecode format
### Inside a .jsc file
A `.jsc` file contains a serialized bytecode structure.
**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**: Compilation context such as strict mode, script vs. module, and eval context type. The same source compiled with different flags produces different bytecode.
**Bytecode instructions**:
* **Instruction stream**: The bytecode opcodes - the compiled representation of your JavaScript, stored as a variable-length sequence of instructions.
* **Metadata table**: Each opcode has associated metadata such as 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 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
**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 typically 2-8x larger than the source code.
### 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 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 changes from version to version. New opcodes get added, old ones get removed or changed, metadata structures change.
**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
**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 distinguishes 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 separation 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
For production CLIs and serverless deployments, the combination of `--bytecode --minify --sourcemap` gives you the best startup time while keeping errors mapped to your original source.
# 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 (through a native bundler plugin)
## Transpiling
Transpiling and vendor prefixing are enabled by default, so you can use modern and future CSS features without worrying about browser compatibility.
Bun's CSS parser and bundler is a direct port of LightningCSS, with a bundling approach inspired by esbuild. The transpiler converts modern CSS syntax into backwards-compatible equivalents that work across browsers.
Thanks to the authors of LightningCSS and esbuild for their work.
## Browser Compatibility
By default, Bun's CSS bundler targets the following browsers:
* ES2020
* Edge 88+
* Firefox 78+
* Chrome 87+
* Safari 14+
## Syntax Lowering
### Nesting
With CSS Nesting, you write child styles directly inside their parent blocks instead of repeating parent selectors across your CSS file.
```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:
```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 at a given ratio in a chosen color space. Use it to create color variations without calculating the resulting values yourself.
```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;
}
```
### Relative colors
Relative color syntax modifies individual components of an existing color. Adjust attributes like lightness, saturation, or individual channels without recalculating 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);
}
```
Use it for theme generation, accessible color variants, or color scales derived from a base color instead of hard-coding each value.
### LAB colors
Modern CSS supports the perceptually uniform color spaces LAB, LCH, OKLAB, and OKLCH, which can represent colors outside the standard RGB gamut.
```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 converts these color formats to backwards-compatible alternatives for browsers that don't 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));
}
```
### Color function
The `color()` function specifies colors in predefined color spaces beyond traditional RGB, giving you access to wider color gamuts.
```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 color spaces, Bun's CSS bundler adds 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);
}
```
### HWB colors
The HWB (Hue, Whiteness, Blackness) color model expresses colors based on how much white or black is mixed with a pure hue. This makes tints and shades more direct to create than with 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 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;
}
```
### Color notation
Modern CSS supports space-separated RGB and HSL values (no commas) and hex colors with an alpha channel.
```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 converts these formats for 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);
}
```
### light-dark() color function
The `light-dark()` function takes two colors and applies one based on the current color scheme, so styles respect the user's system preference without media queries.
```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 `light-dark()`, Bun's CSS bundler converts it to CSS variables with 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);
}
```
### Logical properties
CSS logical properties define layout, spacing, and sizing relative to the document's writing mode and text direction rather than physical screen directions, so layouts 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 for each text direction:
```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, Bun generates additional fallbacks.
### :dir() selector
The `:dir()` pseudo-class styles elements based on their text direction (RTL or LTR), as determined by the document or explicit direction attributes. Use it to write direction-aware styles without JavaScript.
```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, 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;
}
```
If multiple arguments to `:lang()` aren't supported, Bun generates further fallbacks.
### :lang() selector
The `:lang()` pseudo-class targets elements based on their language. To group rules for related languages, pass multiple language codes to a single `:lang()`.
```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 the `:is()` selector with 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 generate additional fallbacks for `:is()` as well.
### :is() selector
The `:is()` pseudo-class function (formerly `:matches()`) takes a selector list and matches if any selector in the list matches.
```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 limitations compared to the standardized `:is()` selector, particularly with complex
selectors. Bun only uses the prefixed versions when they work correctly.
### :not() selector
The `:not()` pseudo-class excludes elements that match a selector. The modern version accepts multiple arguments to exclude several patterns with one `:not()`.
```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 with 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;
}
```
The converted selectors keep the specificity and behavior of the original.
### Math functions
CSS includes 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 expressions at build time when all values are known constants (not variables):
```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));
}
```
### Media query ranges
Media query range syntax expresses breakpoints with comparison operators (`<`, `>`, `<=`, `>=`) instead of the more verbose `min-` and `max-` prefixes.
```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 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;
}
}
```
### Shorthands
CSS has introduced several shorthand properties that combine multiple longhand properties.
```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 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;
}
```
### Double position gradients
Double position gradient syntax specifies the same color at two adjacent positions to create a hard color stop: a sharp transition instead of a smooth fade. Use it for 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 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 */
);
}
```
### system-ui font
The `system-ui` generic font family uses the device's native UI font.
```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 expands it to a 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;
}
```
The expanded stack includes system fonts for macOS/iOS, Windows, Android, and Linux, plus fallbacks for older browsers.
## CSS Modules
Bun's bundler also supports CSS modules, with the following features:
* Detecting CSS module files (`.module.css`) with no 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 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.
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 (
<>
>
);
}
```
Importing a CSS module gives you an object that maps each class name to its unique identifier:
```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 outputs:
```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";
}
```
The class names are unique to each file, so they don't collide.
### Composition
CSS modules can compose class selectors together to 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;
}
```
This is 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;
}
```
Two rules apply 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, make sure they do not contain the same properties.
The CSS module spec says that composing classes from separate files with conflicting properties is undefined behavior: 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 heavily inspired by esbuild. This page is a side-by-side comparison of the two APIs.
A few behaviors differ:
**Bundling by default.** Unlike esbuild, Bun bundles by default; no `--bundle` flag is needed. To transpile each file
individually, use `Bun.Transpiler`.
**Bundler only.** Unlike esbuild, Bun's bundler has no built-in development server or file watcher. Use it with
`Bun.serve` and other runtime APIs to get the same effect. esbuild's HTTP and file-watching options don't apply.
## Performance
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
```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, boolean flags like `--minify` take no argument. Flags that take one, like `--outdir `, can be written as `--outdir out` or `--outdir=out`. Some flags, like `--define`, can be repeated: `--define foo=bar --define bar=baz`.
| esbuild | bun build | Notes |
| ---------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--bundle` | n/a | Bun always bundles; use `--no-bundle` to disable it. |
| `--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"`; 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 [loaders](/docs/bundler/loaders). The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not implemented.
The syntax for `--loader` differs. `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. |
| `--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. Enables feature flags for compile-time dead-code elimination through `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 |
| `--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 uses 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. |
| `--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 the 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 the 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 the 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"`. 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 the JS API; configure in `tsconfig.json` |
| `jsxDev` | `jsxDev` | Not supported in the JS API; configure in `tsconfig.json` |
| `jsxFactory` | `jsxFactory` | Not supported in the JS API; configure in `tsconfig.json` |
| `jsxFragment` | `jsxFragment` | Not supported in the JS API; configure in `tsconfig.json` |
| `jsxImportSource` | `jsxImportSource` | Not supported in the JS API; configure in `tsconfig.json` |
| `jsxSideEffects` | `jsxSideEffects` | Not supported in the 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 [loaders](/docs/bundler/loaders). The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not 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.
// 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 work with Bun without modification. |
| `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, and many third-party esbuild plugins work with Bun without modification.
Long term, we aim for feature parity with esbuild's API. If something doesn't work, 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's methods hook into parts of the bundling process. Bun implements `onStart`, `onEnd`, `onResolve`, and `onLoad`; it does not implement the esbuild hooks `onDispose` and `resolve`. `initialOptions` is partially implemented: it's read-only and exposes only a subset of esbuild's options. Use `config` (the same thing in 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 you can run 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
Use the `--target` flag to 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 (for example, 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 adds it automatically 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 adds it automatically 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 segments of the `--target` value can appear in any order, as long as they're delimited by `-`.
| --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 that require a CPU with AVX2 instructions. The `-baseline` build of Bun
is for older CPUs without them. The Bun installer detects which version to use, but when cross-compiling you might not
know the target CPU. This mostly matters on Windows x64 and Linux x64, rarely on Darwin 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"),
},
});
```
Bun inlines these constants into the binary at build time, so they cost nothing at runtime and enable dead code elimination.
For more examples and patterns, see the [Build-time constants guide](/docs/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: reading files from disk, resolving paths, parsing, transpiling, and printing source code costs time and memory.
Compiled executables 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 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 reduces the size of the transpiled output code. For 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 decompresses & resolves the sourcemap automatically when an error occurs.
The `--bytecode` argument enables bytecode compilation. Every time you run JavaScript code in Bun, JavaScriptCore (the engine) compiles your source code into bytecode. `--bytecode` moves that parsing work from runtime to bundle time, which shortens startup.
***
## Embedding runtime arguments
**`--compile-exec-argv="args"`** - Embed runtime arguments, available at runtime in `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`
Standalone executables read the `BUN_OPTIONS` environment variable, so you can 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
```
***
## 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, opt in with these 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
Set the `BUN_BE_BUN=1` environment variable to run a standalone executable as if it were the `bun` CLI itself. The executable ignores its bundled entrypoint and exposes the full `bun` CLI instead.
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 executes 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 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]
```
CLI tools built on top of Bun can use this to install packages, bundle dependencies, or run other files without downloading a separate binary or installing Bun.
***
## Full-stack executables
New in Bun v1.2.17
The `--compile` flag can create a standalone executable that contains both server and client code, which suits full-stack applications. When you import an HTML file in your server code, Bun bundles the frontend assets (JavaScript, CSS, and so on) and embeds them into the executable.
```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 you can deploy anywhere without installing Node.js, Bun, or any dependencies:
```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
./myapp
```
Bun serves the frontend assets with the correct MIME types and cache headers. The HTML import is replaced with a manifest object that `Bun.serve` uses to serve the pre-bundled assets.
For more on building full-stack applications, see the [full-stack guide](/docs/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, each is bundled separately into the executable.
We may eventually detect statically-known paths in `new Worker(path)` and bundle them automatically, but for now you need to list the worker file as an entrypoint, as in the earlier example.
If you use a relative path to a file not included in the standalone executable, Bun loads that path from disk relative to the process's current working directory, and errors 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 at `/usr/bin/hello` and the user's terminal is in `/home/me/Desktop`, Bun looks 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 can embed files directly into the binary, so a single executable can ship 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 with the 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
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());
```
If you're using `@mapbox/node-pre-gyp` or similar tools, the `.node` file must be required directly 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 a workaround, and we expect to replace it with a more direct API.
### Detecting standalone mode at runtime
Use `Bun.isStandaloneExecutable` to check whether the current process is running from a compiled binary:
```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"}}
if (Bun.isStandaloneExecutable) {
// Running from `bun build --compile` output
} else {
// Running via `bun ` or as a library
}
```
Unlike `Bun.embeddedFiles.length > 0`, this check does not allocate `Blob` objects for each embedded file, so it is safe to call at startup in binaries that embed large assets.
### Listing embedded files
`Bun.embeddedFiles` exposes 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;
```
Use it to serve every embedded asset through `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, which helps with cache invalidation when you serve them from a URL or CDN. To keep the original name instead, 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, platform-specific options 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 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-jitcom.apple.security.cs.allow-unsigned-executable-memorycom.apple.security.cs.disable-executable-page-protectioncom.apple.security.cs.allow-dyld-environment-variablescom.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; use them to transform files during the 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"}}
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. See the [plugin documentation](/docs/bundler/plugins).
***
## Unsupported CLI arguments
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](/docs/bundler/standalone-html) for `--compile --target=browser` with `.html` files)
* `--no-bundle` - Bun always bundles 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 `