Open-Source Wikis

/

Bun

/

Bun

/

Architecture

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: exit

Not 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 --> D2

Most 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.

Architecture – Bun wiki | Factory