Open-Source Wikis

/

Bun

/

Apps

/

Runtime

oven-sh/bun

Runtime

Active contributors: Jarred Sumner, Dylan Conway, Ciro Spaciari

The runtime is what makes bun ./script.ts, bun -e '...', and bun run start execute JavaScript and TypeScript. It owns the JavaScriptCore VM, the event loop, the module loader, the Web API surface, and the Node.js compatibility layer.

Purpose

Provide a Node.js-compatible JavaScript runtime that:

  • Boots faster and uses less memory than Node by avoiding napi indirection where possible.
  • Transpiles TypeScript, JSX, and TSX inline before evaluation.
  • Implements Web APIs (fetch, Request, Response, URL, streams, Blob, WebSocket) natively in Zig/C++ instead of in user-land JavaScript.
  • Exposes Bun-specific APIs (Bun.serve, Bun.spawn, Bun.file, Bun.$, Bun.s3, Bun.sql, Bun.SQLite, ...) backed by native code.

Directory layout

src/
├── bun.js/
│   ├── VirtualMachine.zig          # the runtime root struct (one per realm)
│   ├── ModuleLoader.zig            # imports + require + dynamic import
│   ├── RuntimeTranspilerCache.zig  # disk cache for parsed modules
│   ├── RuntimeTranspilerStore.zig  # parallel transpile workers
│   ├── HardcodedModule.zig         # registry of built-in module IDs
│   ├── ConsoleObject.zig           # console.log + util.inspect formatter
│   ├── Debugger.zig                # WebSocket inspector protocol
│   ├── ipc.zig                     # process.send / parent-child IPC
│   ├── hot_reloader.zig            # --watch / --hot
│   ├── web_worker.zig              # Worker threads
│   ├── event_loop.zig              # task queue + microtask drain
│   ├── api/                        # Bun.* namespace (server, ffi, ...)
│   ├── webcore/                    # fetch, Blob, Request, Response, streams
│   ├── node/                       # Node compat in Zig (fs, net, dns, ...)
│   ├── bindings/                   # C++ JSC glue
│   ├── modules/                    # _NativeModule entries
│   └── webview/                    # Embedded webview (experimental)
├── cli/run_command.zig             # bun run / bun ./script
├── cli/repl_command.zig            # bun repl
└── js/                             # JS/TS code that ends up bundled into the binary

Key abstractions

Type File Purpose
VirtualMachine src/bun.js/VirtualMachine.zig Root struct. Owns the JSC JSGlobalObject, the transpiler, the event loop, the module loader, the timer, hot-reload state, IPC, the source-map table. One per realm.
ZigGlobalObject src/bun.js/bindings/ZigGlobalObject.cpp The JSC::JSGlobalObject subclass that JSC sees. Caches Bun-specific structures and ties Zig and JSC together.
EventLoop src/bun.js/event_loop.zig Macro-task queue, microtask drain, deferred tasks across threads. Wraps uSockets / libuv.
ModuleLoader src/bun.js/ModuleLoader.zig Implements import, require, import(). Dispatches to native loaders for hardcoded modules; resolves user code through src/resolver/.
Transpiler src/transpiler.zig High-level wrapper around the parser + printer + sourcemap pipeline. The VM holds one.
RuntimeTranspilerCache src/bun.js/RuntimeTranspilerCache.zig On-disk content-addressed cache of transpiled bytes (~/.bun/install/cache/).
Strong / Weak src/bun.js/Strong.zig, Weak.zig Roots for JS values that outlive a single function call.
WebWorker src/bun.js/web_worker.zig One worker thread = one full VirtualMachine instance.
HotReloader src/bun.js/hot_reloader.zig Coordinates --watch and --hot; owns the file watcher subscription and re-evaluation logic.

How a script runs

sequenceDiagram
    participant CLI as src/cli/run_command.zig
    participant VM as VirtualMachine.zig
    participant Loader as ModuleLoader.zig
    participant Cache as RuntimeTranspilerCache.zig
    participant Parser as js_parser.zig
    participant JSC as JavaScriptCore
    participant EL as EventLoop

    CLI->>VM: VirtualMachine.create(allocator, args)
    VM->>JSC: ZigGlobalObject::create
    VM->>Loader: load entry path
    Loader->>Cache: lookup by content hash
    alt cache miss
        Loader->>Parser: parse to AST
        Parser-->>Loader: AST + sourcemap
        Loader->>Cache: write transpiled bytes + sourcemap
    else cache hit
        Cache-->>Loader: transpiled bytes
    end
    Loader->>JSC: JSModuleLoader::link + evaluate
    JSC-->>VM: top-level await Promise
    VM->>EL: drainMicrotasks + tick
    loop until idle or process.exit
        EL->>EL: poll uSockets / libuv
        EL->>JSC: drainMicrotasks
    end
    VM-->>CLI: exit code

The VM does not block on evaluate. Top-level await returns a Promise; the loop continues turning until the promise settles, all timers and listeners are gone, or process.exit is called.

Module loading

import and require flow through ModuleLoader.zig. The loader recognises three kinds of specifier:

  1. Hardcoded modulenode:fs, bun:sqlite, etc. Looked up in HardcodedModule.zig and dispatched to a native loader (Zig or C++). Some hardcoded modules are implemented in TypeScript under src/js/ and bundled into the binary at build time by src/codegen/bundle-modules.ts.
  2. NPM packagelodash, react/jsx-runtime, etc. Resolved via src/resolver/resolver.zig against node_modules/, applying package.json exports/imports/main/module/browser. Auto-install can fetch missing packages on demand if bunfig.toml enables it.
  3. Relative or absolute path./foo.ts, /abs/path.js, file:///.... Read from disk and transpiled.

CommonJS interop is handled in src/bun.js/bindings/CommonJSModuleRecord.cpp plus the parser's CJS detection. CJS modules expose a default export that aliases module.exports and let named imports work for static-analyzable shapes.

The transpiler cache (RuntimeTranspilerCache.zig) keys by (blake3_hash(source), parser_options_hash) and stores bytes under ~/.bun/install/cache/v1/jsc/. A second invocation of the same script skips parsing entirely on a cache hit.

See Module resolution and Built-in JS modules.

Bun.* API surface

The Bun global is built up in src/bun.js/api/BunObject.zig. Each property is one of:

  • A native function (Bun.spawn, Bun.write, Bun.file, Bun.gzipSync, Bun.deepEquals, ...).
  • A class constructor (Bun.S3Client, Bun.SQLite, Bun.SQL, Bun.Glob, ...).
  • A getter (Bun.argv, Bun.env, Bun.version).

The largest entries are:

Bun.* API Implementation
Bun.serve (HTTP/WebSocket server) src/bun.js/api/server.zig (~170 KB)
Bun.spawn / Bun.spawnSync src/bun.js/api/bun/process.zig
Bun.file / Bun.write src/bun.js/webcore/Blob.zig (~189 KB)
Bun.SQLite src/bun.js/bindings/sqlite/
Bun.SQL (Postgres / MySQL) src/sql/ + src/bun.js/api/sql.classes.ts
Bun.S3 / Bun.S3Client src/s3/ + src/bun.js/webcore/S3Client.zig
Bun.HTMLRewriter src/bun.js/api/html_rewriter.zig (lol-html wrapper)
Bun.FFI (bun:ffi) src/bun.js/api/ffi.zig (~100 KB) + src/bun.js/api/FFIObject.zig
Bun.$ (shell) src/shell/
Bun.Transpiler src/bun.js/api/JSTranspiler.zig
Bun.Glob src/bun.js/api/glob.zig
Bun.Cron src/bun.js/api/cron.zig

Web APIs

src/bun.js/webcore/ implements the fetch/Request/Response/Blob/URL/Headers family plus the WHATWG Streams machinery:

Web API File
fetch src/bun.js/webcore/fetch.zig
Request, Response, Body Request.zig, Response.zig, Body.zig
Blob, File Blob.zig
Headers src/http/Headers.zig
ReadableStream etc. streams.zig + builtins in src/js/builtins/
TextEncoder, TextDecoder, TextEncoderStream TextEncoder.zig, TextDecoder.zig, TextEncoderStreamEncoder.zig
crypto.subtle Crypto.zig (uses BoringSSL)
URL C++ binding into JSC's URL implementation; see Glossary for the host/hostname swap quirk.
Cookies (Bun.Cookie, Bun.CookieMap) CookieMap.zig

The Streams implementation is a hybrid: the public API (ReadableStream, WritableStream, TransformStream) is JSC builtins copied/adapted from WebKit (src/js/builtins/). Native sinks (FileSink, ArrayBufferSink, BunHTTPResponseSink, ...) are generated from src/codegen/generate-jssink.ts.

Node.js compatibility

Two layers cooperate:

  1. TypeScript implementations under src/js/node/. These are the public ESM modules exposed as node:fs, node:path, node:stream, etc. They are bundled into the binary at build time. Each file uses a domain-specific syntax ($cpp, $lazy, $bundled, $native) handled by src/codegen/builtin-parser.ts and src/codegen/bundle-modules.ts.
  2. Native modules under src/bun.js/node/. Performance-critical APIs (fs.read, dgram socket internals, crypto hash classes, assert, net.connect's low-level path) are written in Zig and exposed as private bindings that the TS layer calls into.

The split is per-API: node:path is pure TS, node:fs.read calls a native binding, node:net uses native sockets via uSockets.

The set of supported APIs is tracked in test/js/node/ and the long-running node:test compat suite (bun run node:test).

Event loop and tasks

event_loop.zig exposes one main entry: tick(). A tick:

  1. Pulls all queued macrotasks off the tasks queue and runs them.
  2. Drains microtasks via JSC.
  3. Polls uSockets (POSIX) or libuv (Windows) until something ready or all sources idle.
  4. Moves "next-tick" tasks (Node compat) into the macrotask queue.

Cross-thread events (HTTP responses, FS watcher events, worker postMessage) post to concurrent_tasks and signal the loop to wake.

The Timer subsystem (src/bun.js/api/Timer.zig) is built on a treap keyed by deadline. setTimeout, setInterval, setImmediate are all Timer flavors.

See Event loop.

Worker threads

new Worker(url) instantiates web_worker.zig::WebWorker, which:

  1. Spawns an OS thread.
  2. Creates a fresh VirtualMachine and ZigGlobalObject on that thread.
  3. Loads the worker script via the same ModuleLoader pipeline.
  4. Sets up a MessagePort pair using JSC structured-cloning.

The parent and worker share nothing except message channels; each has its own event loop.

Hot reload and watch

bun --watch script.ts and bun --hot route through hot_reloader.zig. The reloader:

  1. Subscribes to the file watcher (src/Watcher.zig).
  2. On a change, marks affected modules dirty in the VM's transpiler cache.
  3. For --watch it tears down the VM and re-runs the entry script.
  4. For --hot it replaces only the changed module and re-runs the side effects.

Bake (the dev server) uses a different, more sophisticated HMR runtime in src/bake/client/hmr-*.ts. See Bake.

Integration points

  • CLI — Created and torn down by src/cli/run_command.zig, src/cli/repl_command.zig, src/cli/test_command.zig.
  • Bundlerbun build does not start a VM unless plugins are configured; when it does, the same VirtualMachine runs plugin code.
  • Package managerbun install runs preinstall / postinstall scripts via src/install/lifecycle_script_runner.zig, which spawns a child bun rather than reusing the VM.
  • HTTP — Server requests dispatch into JS via src/bun.js/api/server.zig; outgoing fetch posts to HTTPThread.zig.
  • Workersweb_worker.zig reuses the VM machinery on a fresh thread.

Entry points for modification

  • To add a Bun API, edit src/bun.js/api/BunObject.classes.ts (signature) and a new or existing *.zig file in src/bun.js/api/.
  • To add a node:* module, drop the TS file in src/js/node/<name>.ts. Add an entry to src/js/CLAUDE.md's registry if it's new. Native bindings live in src/bun.js/node/.
  • To change parsing behavior at evaluation time, edit src/bun.js/RuntimeTranspilerStore.zig or src/transpiler.zig. Cache invalidation is keyed in RuntimeTranspilerCache.zig.
  • To add a Web API, prefer src/bun.js/webcore/. Generate the host class via *.classes.ts and src/codegen/generate-classes.ts.

Key source files

File Purpose
src/bun.js/VirtualMachine.zig Realm root; ~4,100 lines.
src/bun.js/ModuleLoader.zig Module load + link + evaluate.
src/bun.js/RuntimeTranspilerCache.zig On-disk transpile cache.
src/bun.js/event_loop.zig Tick + microtasks.
src/bun.js/api/BunObject.zig Bun.* namespace.
src/bun.js/webcore/Blob.zig Bun.file, Blob, File.
src/bun.js/api/server.zig Bun.serve HTTP/WebSocket server.
src/cli/run_command.zig The bun run / bun ./file entry.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Runtime – Bun wiki | Factory