oven-sh/bun
Architecture
Bun is a single statically-linked executable that hosts a JavaScriptCore VM, a custom parser/bundler pipeline, an event loop, and a set of subsystem modules (package manager, test runner, HTTP, shell, ...). All subcommands run in the same process and share most of these subsystems.
High-level shape
graph TD
User[bun <subcommand>]
User --> Main[main.zig → bun.zig]
Main --> CLI[cli.zig dispatcher]
CLI -->|run / test / repl| Run[Runtime: VirtualMachine.zig]
CLI -->|install / add / pm| PM[Package manager: install/]
CLI -->|build| Build[Bundler: bundler/bundle_v2.zig]
CLI -->|test| Test[Test runner: cli/test_command.zig]
CLI -->|x| Bunx[bunx_command.zig]
Run --> JSC[(JavaScriptCore VM)]
Run --> EL[Event loop]
Build --> Parser[js_parser + js_lexer]
Build --> Linker[bundle_v2 LinkerContext]
Test --> Run
EL --> uSockets[uSockets / libuv]
JSC --> Bindings[C++ bindings src/bun.js/bindings/]
Bindings --> ZigAPIs[Zig APIs src/bun.js/api/]bun.zig is the Zig entry point. It pre-loads global state (allocators, output, signal handlers, the crash handler from crash_handler.zig), then hands control to cli.zig which parses argv and dispatches to a per-command Zig file under src/cli/. Each command builds whichever subsystem it needs.
Process lifecycle
sequenceDiagram
participant Shell
participant bun_zig as src/bun.zig
participant cli as src/cli.zig
participant cmd as src/cli/<cmd>_command.zig
participant vm as VirtualMachine.zig
participant jsc as JSC
Shell->>bun_zig: exec bun args...
bun_zig->>bun_zig: install crash handler, allocators, output
bun_zig->>cli: Cli.start()
cli->>cli: parse Arguments.zig
cli->>cmd: Command.exec(ctx)
cmd->>vm: VirtualMachine.create(...) (only for run/test/repl)
vm->>jsc: ZigGlobalObject::create
vm->>vm: load entry script via ModuleLoader.zig
vm->>jsc: drive event loop
jsc-->>vm: tick microtasks
vm-->>cmd: process.exit / drained loop
cmd-->>cli: exit code
cli-->>Shell: exitNot every command spins up a JS VM. bun install, bun build (file output), bun pm, and similar pure-native commands run entirely in Zig without instantiating JavaScriptCore. The VM is created lazily only when JS needs to execute (loading config files, plugins, user scripts).
Three-language layout
A given feature usually has all three languages stacked on top of each other:
graph TB
subgraph TS["TypeScript / JS (src/js/)"]
A1["JS implementation: e.g. node/fs.ts"]
A2["builtin: e.g. ReadableStream.ts"]
end
subgraph CPP["C++ bindings (src/bun.js/bindings/)"]
B1["host functions, prototypes, structures"]
B2["generated classes from *.classes.ts"]
end
subgraph ZIG["Zig (src/bun.js/api/, src/bun.js/webcore/, src/bun.js/node/)"]
C1["e.g. server.zig (Bun.serve)"]
C2["e.g. fetch.zig (fetch())"]
end
subgraph VENDOR["vendor/"]
D1["JavaScriptCore"]
D2["BoringSSL, libuv, mimalloc, lolhtml..."]
end
A1 --> B1
A2 --> B2
B1 --> C1
B2 --> C2
C1 --> D1
C2 --> D2Most JS-visible objects (Request, Response, Bun.serve's server, Bun.spawn's subprocess, bun:sqlite's Database, ...) are defined by a Zig struct, a *.classes.ts declaration, and an autogenerated C++ wrapper. The code generator is src/codegen/generate-classes.ts. See Code generation.
Event loop
Bun runs one event loop per JS realm. The main-thread loop is in src/bun.js/event_loop.zig and wraps either:
- uSockets (
packages/bun-usockets/, vendored from uWebSockets) on POSIX — used for HTTP, TCP, UDP, TLS, WebSockets. - libuv (
vendor/libuv/) on Windows — for file I/O, processes, signals, timers.
The loop owns a queue of tasks (Task union in event_loop.zig), a microtask drain hook into JSC, the timer manager (src/bun.js/api/Timer.zig), and the file-system watcher (Watcher.zig). Worker threads (web_worker.zig) and the bundler thread pool (bundler/ThreadPool.zig) have their own simpler loops.
See Event loop.
Module loading
Loading import "./foo.ts" goes through several layers:
graph LR
Spec["import './foo.ts'"]
Spec --> Resolver["src/resolver/resolver.zig"]
Resolver --> Cache["RuntimeTranspilerCache.zig"]
Cache -->|hit| Loaded[ready]
Cache -->|miss| Parser["js_parser.zig + js_lexer.zig"]
Parser --> Printer["js_printer.zig"]
Printer --> Cache
Cache --> ModLoader["bun.js/ModuleLoader.zig"]
ModLoader --> JSC["JSC LinkAndEvaluateModule"]The resolver implements Node ESM/CJS rules plus tsconfig path mapping, browser-field, exports/imports field, and Bun's auto-install. Parsed modules are cached on disk under ~/.bun/install/cache/ keyed by content hash. The cache lets bun run skip parsing on the second invocation.
See Module resolution and Parser & transpiler.
Bundler pipeline
Bun.build and bun build share the same pipeline as the runtime's transpile step but with extra phases:
graph LR
Entry[entry points] --> Scan[parse + scan imports]
Scan --> Resolve[resolver]
Resolve --> Scan
Scan --> Graph[LinkerGraph]
Graph --> TreeShake[symbol tracker tree shaking]
TreeShake --> Chunks[chunk planning Chunk.zig]
Chunks --> CSS[css/css_parser.zig]
Chunks --> Print[js_printer with binding renames]
Print --> Output[OutputFile.zig]bundle_v2.zig is the orchestrator (a 234,000-byte file). The linker walks the import graph, dedupes symbols, applies renaming, runs CSS through src/css/css_parser.zig, and emits chunked files. Plugins (src/bun.js/api/JSBundler.zig) hook into resolve and load phases.
See Bundler.
HTTP stack
The HTTP/WebSocket implementation lives in src/http/ (client) and src/bun.js/api/server.zig (server, ~170,000 bytes — the largest single file in src/bun.js/api/). Both build on top of:
- uSockets for the socket-event plumbing (epoll/kqueue/IOCP).
- BoringSSL for TLS.
- lshpack + lsqpack for HPACK / QPACK compression.
- lsquic for QUIC / HTTP/3.
- picohttpparser for HTTP/1.1 line parsing.
The same HTTPThread.zig powers fetch(), the bun install registry client, and bunx. See HTTP stack.
Subsystem inventory
| Subsystem | Source root | Wiki page |
|---|---|---|
| CLI dispatcher | src/cli.zig, src/cli/ |
Apps |
| JS runtime / VM | src/bun.js/VirtualMachine.zig |
Runtime |
| Parser, lexer, printer | src/js_parser.zig, src/js_lexer.zig, src/js_printer.zig |
Parser & transpiler |
| Module resolver | src/resolver/ |
Module resolution |
| Bundler | src/bundler/ |
Bundler |
| Package manager | src/install/ |
Package manager |
| Test runner | src/cli/test_command.zig, src/cli/test/ |
Test runner |
| Shell | src/shell/ |
Shell |
| Dev server / SSR | src/bake/ |
Bake (dev server) |
| HTTP/WebSocket client | src/http/ |
HTTP stack |
| HTTP server | src/bun.js/api/server.zig |
Runtime |
| SQL clients | src/sql/ |
SQL clients |
| Event loop | src/bun.js/event_loop.zig |
Event loop |
| C++ JSC bindings | src/bun.js/bindings/ |
C++ bindings |
| Built-in JS modules | src/js/ |
Built-in JS modules |
| Code generation | src/codegen/ |
Code generation |
| CSS parser | src/css/ |
CSS parser |
| Crash handler | src/crash_handler.zig |
Crash handler |
| File-system watcher | src/Watcher.zig |
Watcher & hot reload |
| Allocators / sys | src/allocators/, src/sys.zig |
Allocators & sys |
| Vendored deps | vendor/ |
Vendored dependencies |
Threading model
| Thread | Owner | Role |
|---|---|---|
| Main | VirtualMachine / EventLoop |
Drives JS, runs microtasks and timer fires. |
| HTTP thread | src/http/HTTPThread.zig |
Owns the uSockets loop for outgoing HTTP. Workers post requests; replies marshal back. |
| Bundler workers | src/bundler/ThreadPool.zig |
Parse ParseTasks in parallel during Bun.build and runtime dynamic imports. |
| Install workers | src/install/PackageManagerTask.zig |
Tarball downloads, extraction, postinstall execution. |
| Worker threads | src/bun.js/web_worker.zig |
Each new Worker(...) gets its own VM and event loop on a dedicated OS thread. |
| Watcher thread | src/Watcher.zig |
Polls inotify / FSEvents / ReadDirectoryChangesW and posts to the main loop. |
Bun is not a green-thread runtime; concurrency is event-loop based. The thread pool exists for CPU-bound parse/extract work that would otherwise stall the loop.
Where to start when modifying Bun
| Want to change... | Start here |
|---|---|
| A subcommand's CLI flags | src/cli/Arguments.zig then the corresponding src/cli/<cmd>_command.zig |
A Bun.* API |
src/bun.js/api/*.zig and the matching *.classes.ts |
A node:* module |
src/js/node/<name>.ts (JS-visible side) and possibly src/bun.js/node/*.zig (native) |
| A web API | src/bun.js/webcore/*.zig |
| The bundler | src/bundler/bundle_v2.zig and LinkerContext.zig |
| Module resolution | src/resolver/resolver.zig |
| The parser | src/js_parser.zig |
| Built-in JSC builtins | src/js/builtins/ |
| HTTP server response shape | src/bun.js/api/server.zig |
| Package install layout | src/install/PackageInstaller.zig and hoisted_install.zig / isolated_install.zig |
Always rebuild with bun bd after Zig or C++ changes; pure src/js/**.ts edits are picked up by bun run build without rebuilding Zig.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.