Skip to content

Latest commit

 

History

History
319 lines (240 loc) · 15 KB

File metadata and controls

319 lines (240 loc) · 15 KB

This is the Bun repository - an all-in-one JavaScript runtime & toolkit designed for speed, with a bundler, test runner, and Node.js-compatible package manager. It's written primarily in Zig with C++ for JavaScriptCore integration, powered by WebKit's JavaScriptCore engine.

Building and Running Bun

Build Commands

  • Build Bun: bun bd
    • Creates a debug build at ./build/debug/bun-debug
    • CRITICAL: do not set a timeout when running bun bd
  • Run tests with your debug build: bun bd test <test-file>
    • CRITICAL: Never use bun test directly - it won't include your changes
  • Run any command with debug build: bun bd <command>
  • Run with JavaScript exception scope verification: BUN_JSC_validateExceptionChecks=1 BUN_JSC_dumpSimulatedThrows=1 bun bd <command>

Tip: Bun is already installed and in $PATH. The bd subcommand is a package.json script.

All build scripts support build-then-exec. Any bun run build* command (and bun bd, and bun scripts/build.ts directly) accepts trailing args which are passed to the built executable after building. This is the recommended way to run your build — you never invoke ./build/debug/bun-debug directly.

bun bd test foo.test.ts                    # debug build + quiet debug logs
bun run build test foo.test.ts             # debug build
bun run build:release -p 'Bun.version'     # release build
bun run build:local run script.ts          # debug build with local WebKit

When exec args are present, build output is suppressed unless the build fails — you see only the binary's output. Build flags (e.g. --asan=off) go before the exec args; see scripts/build.ts header for the full arg routing rules.

Comparing builds: normally use the default build/<profile>/ dir. If you need to preserve a build as a comparison point (rare — e.g. benchmarking before/after a change), --build-dir parks it somewhere the next build won't overwrite:

bun run build:release --build-dir=build/baseline

Changes that don't require a build

Edits to TypeScript type declarations (packages/bun-types/**/*.d.ts) do not touch any compiled code, so bun bd is unnecessary. The types test just packs the .d.ts files and runs tsc against fixtures — it never executes your build. Run it directly with the system Bun:

bun test test/integration/bun-types/bun-types.test.ts

This is an explicit exception to the "never use bun test directly" rule. There are no native changes for a debug build to pick up, so don't wait on one.

Testing

Running Tests

  • Single test file: bun bd test test/js/bun/http/serve.test.ts
  • Fuzzy match test file: bun bd test http/serve.test.ts
  • With filter: bun bd test test/js/bun/http/serve.test.ts -t "should handle"

Test Organization

If a test is for a specific numbered GitHub Issue, it should be placed in test/regression/issue/${issueNumber}.test.ts. Ensure the issue number is REAL and not a placeholder!

If no valid issue number is provided, find the best existing file to modify instead, such as;

  • test/js/bun/ - Bun-specific API tests (http, crypto, ffi, shell, etc.)
  • test/js/node/ - Node.js compatibility tests
  • test/js/web/ - Web API tests (fetch, WebSocket, streams, etc.)
  • test/cli/ - CLI command tests (install, run, test, etc.)
  • test/bundler/ - Bundler and transpiler tests. Use itBundled helper.
  • test/integration/ - End-to-end integration tests
  • test/napi/ - N-API compatibility tests
  • test/v8/ - V8 C++ API compatibility tests

Writing Tests

Tests use Bun's Jest-compatible test runner with proper test fixtures.

  • For single-file tests, prefer -e over tempDir.
  • For multi-file tests, prefer tempDir and Bun.spawn.
import { test, expect } from "bun:test";
import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness";

test("(single-file test) my feature", async () => {
  await using proc = Bun.spawn({
    cmd: [bunExe(), "-e", "console.log('Hello, world!')"],
    env: bunEnv,
  });

  const [stdout, stderr, exitCode] = await Promise.all([
    proc.stdout.text(),
    proc.stderr.text(),
    proc.exited,
  ]);

  expect(normalizeBunSnapshot(stdout)).toMatchInlineSnapshot(`"Hello, world!"`);
  expect(exitCode).toBe(0);
});

test("(multi-file test) my feature", async () => {
  // Create temp directory with test files
  using dir = tempDir("test-prefix", {
    "index.js": `import { foo } from "./foo.ts"; foo();`,
    "foo.ts": `export function foo() { console.log("foo"); }`,
  });

  // Spawn Bun process
  await using proc = Bun.spawn({
    cmd: [bunExe(), "index.js"],
    env: bunEnv,
    cwd: String(dir),
    stderr: "pipe",
  });

  const [stdout, stderr, exitCode] = await Promise.all([
    proc.stdout.text(),
    proc.stderr.text(),
    proc.exited,
  ]);

  // Prefer snapshot tests over expect(stdout).toBe("hello\n");
  expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`"hello"`);

  // Assert the exit code last. This gives you a more useful error message on test failure.
  expect(exitCode).toBe(0);
});
  • Always use port: 0. Do not hardcode ports. Do not use your own random port number function.
  • Use normalizeBunSnapshot to normalize snapshot output of the test.
  • NEVER write tests that check for no "panic" or "uncaught exception" or similar in the test output. These tests will never fail in CI.
  • Use tempDir from "harness" to create a temporary directory. Do not use tmpdirSync or fs.mkdtempSync to create temporary directories.
  • When spawning processes, tests should expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0). This gives you a more useful error message on test failure.
  • CRITICAL: Do not write flaky tests. Do not use setTimeout in tests. Instead, await the condition to be met. You are not testing the TIME PASSING, you are testing the CONDITION.
  • CRITICAL: Verify your test fails with USE_SYSTEM_BUN=1 bun test <file> and passes with bun bd test <file>. Your test is NOT VALID if it passes with USE_SYSTEM_BUN=1.

Code Architecture

Language Structure

  • Zig code (src/*.zig): Core runtime, JavaScript bindings, package manager
  • C++ code (src/bun.js/bindings/*.cpp): JavaScriptCore bindings, Web APIs
  • TypeScript (src/js/): Built-in JavaScript modules with special syntax (see JavaScript Modules section)
  • Generated code: Many files are auto-generated from .classes.ts and other sources. Bun will automatically rebuild these files when you make changes to them.

Core Source Organization

Runtime Core (src/)

  • bun.zig - Main entry point
  • cli.zig - CLI command orchestration
  • js_parser.zig, js_lexer.zig, js_printer.zig - JavaScript parsing/printing
  • transpiler.zig - Wrapper around js_parser with sourcemap support
  • resolver/ - Module resolution system
  • allocators/ - Custom memory allocators for performance

JavaScript Runtime (src/bun.js/)

  • bindings/ - C++ JavaScriptCore bindings
    • Generated classes from .classes.ts files
    • Manual bindings for complex APIs
  • api/ - Bun-specific APIs
    • server.zig - HTTP server implementation
    • FFI.zig - Foreign Function Interface
    • crypto.zig - Cryptographic operations
    • glob.zig - File pattern matching
  • node/ - Node.js compatibility layer
    • Module implementations (fs, path, crypto, etc.)
    • Process and Buffer APIs
  • webcore/ - Web API implementations
    • fetch.zig - Fetch API
    • streams.zig - Web Streams
    • Blob.zig, Response.zig, Request.zig
  • event_loop/ - Event loop and task management

Build Tools & Package Manager

  • src/bundler/ - JavaScript bundler
    • Advanced tree-shaking
    • CSS processing
    • HTML handling
  • src/install/ - Package manager
    • lockfile/ - Lockfile handling
    • npm.zig - npm registry client
    • lifecycle_script_runner.zig - Package scripts

Other Key Components

  • src/shell/ - Cross-platform shell implementation
  • src/css/ - CSS parser and processor
  • src/http/ - HTTP client implementation
    • websocket_client/ - WebSocket client (including deflate support)
  • src/sql/ - SQL database integrations
  • src/bake/ - Server-side rendering framework

Vendored Dependencies (vendor/)

Third-party C/C++ libraries are vendored locally and can be read from disk (these are not git submodules):

  • vendor/boringssl/ - BoringSSL (TLS/crypto)
  • vendor/brotli/ - Brotli compression
  • vendor/cares/ - c-ares (async DNS)
  • vendor/hdrhistogram/ - HdrHistogram (latency tracking)
  • vendor/highway/ - Google Highway (SIMD)
  • vendor/libarchive/ - libarchive (tar/zip)
  • vendor/libdeflate/ - libdeflate (fast deflate)
  • vendor/libuv/ - libuv (Windows event loop)
  • vendor/lolhtml/ - lol-html (HTML rewriter)
  • vendor/lshpack/ - ls-hpack (HTTP/2 HPACK)
  • vendor/mimalloc/ - mimalloc (memory allocator)
  • vendor/nodejs/ - Node.js headers (compatibility)
  • vendor/picohttpparser/ - PicoHTTPParser (HTTP parsing)
  • vendor/tinycc/ - TinyCC (FFI JIT compiler, fork: oven-sh/tinycc)
  • vendor/WebKit/ - WebKit/JavaScriptCore (JS engine)
  • vendor/zig/ - Zig compiler/stdlib
  • vendor/zlib/ - zlib (compression, cloudflare fork)
  • vendor/zstd/ - Zstandard (compression)

Build configuration for these is in scripts/build/deps/*.ts.

JavaScript Class Implementation (C++)

When implementing JavaScript classes in C++:

  1. Create three classes if there's a public constructor:

    • class Foo : public JSC::JSDestructibleObject (if has C++ fields)
    • class FooPrototype : public JSC::JSNonFinalObject
    • class FooConstructor : public JSC::InternalFunction
  2. Define properties using HashTableValue arrays

  3. Add iso subspaces for classes with C++ fields

  4. Cache structures in ZigGlobalObject

Code Generation

Code generation happens automatically as part of the build process. The main scripts are:

  • src/codegen/generate-classes.ts - Generates Zig & C++ bindings from *.classes.ts files
  • src/codegen/generate-jssink.ts - Generates stream-related classes
  • src/codegen/bundle-modules.ts - Bundles built-in modules like node:fs
  • src/codegen/bundle-functions.ts - Bundles global functions like ReadableStream

In development, bundled modules can be reloaded without rebuilding Zig by running bun run build.

JavaScript Modules (src/js/)

Built-in JavaScript modules use special syntax and are organized as:

  • node/ - Node.js compatibility modules (node:fs, node:path, etc.)
  • bun/ - Bun-specific modules (bun:ffi, bun:sqlite, etc.)
  • thirdparty/ - NPM modules we replace (like ws)
  • internal/ - Internal modules not exposed to users
  • builtins/ - Core JavaScript builtins (streams, console, etc.)

Code Review Self-Check

  • Before writing code that makes a non-obvious choice, pre-emptively ask "why this and not the alternative?" If you can't answer, research until you can — don't write first and justify later.
  • Don't take a bug report's suggested fix at face value; verify it's the right layer.
  • If neighboring code does something differently than you're about to, find out why before deviating — its choices are often load-bearing, not stylistic.

Important Development Notes

  1. Never use bun test or bun <file> directly - always use bun bd test or bun bd <command>. bun bd compiles & runs the debug build.
  2. All changes must be tested - if you're not testing your changes, you're not done.
  3. Get your tests to pass. If you didn't run the tests, your code does not work.
  4. Follow existing code style - check neighboring files for patterns
  5. Create tests in the right folder in test/ and the test must end in .test.ts or .test.tsx
  6. Use absolute paths - Always use absolute paths in file operations
  7. Avoid shell commands - Don't use find or grep in tests; use Bun's Glob and built-in tools
  8. Memory management - In Zig code, be careful with allocators and use defer for cleanup
  9. Cross-platform - Run bun run zig:check-all to compile the Zig code on all platforms when making platform-specific changes
  10. Debug builds - Use BUN_DEBUG_QUIET_LOGS=1 to disable debug logging, or BUN_DEBUG_<scopeName>=1 to enable specific Output.scoped(.${scopeName}, .visible)s
  11. Be humble & honest - NEVER overstate what you got done or what actually works in commits, PRs or in messages to the user.
  12. Branch names must start with claude/ - This is a requirement for the CI to work.

ONLY push up changes after running bun bd test <file> and ensuring your tests pass.

Debugging CI Failures

Requires the BuildKite CLI (brew install buildkite/buildkite/bk) and a read-scoped token in BUILDKITE_API_TOKEN. The repo's .bk.yaml sets the org/pipeline so -p bun is not needed.

# Show rendered test-failure output for the current branch's latest build,
# tagged [new] vs [also on main]
bun run ci:errors
bun run ci:errors '#26173'          # or a PR number / URL / branch / build number

# One-screen progress summary (job counts, failed jobs, failing tests so far)
bun run ci:status

# Save full logs for every failed job to ./tmp/ci-<build>/
bun run ci:logs

# Just the build number, for composing with raw `bk`
bun run ci:find
bk job log <job-uuid> -b $(bun run ci:find)

# Watch the current branch's build until it finishes
bun run ci:watch

For anything else, use bk directly — bk build list, bk api, bk artifacts, etc.

If output from these commands looks wrong — mis-parsed annotation HTML, confusing wording, a field BuildKite changed shape on — fix scripts/find-build.ts directly rather than working around it. It's a thin presenter over bk; keep it accurate.

Reading PR Feedback

gh pr view --comments is fine for a quick look at the Conversation tab, but it has a footgun worth knowing about: it only returns issue-stream comments and silently omits review summaries and line-level review comments. If a reviewer leaves an inline comment on a specific file line, it will not show up — no error, no hint that anything is missing.

When you want the complete picture — especially when responding to a review or checking whether anyone requested changes — use bun run pr:comments. It fetches all three GitHub endpoints (/issues/N/comments, /pulls/N/reviews, /pulls/N/comments) and prints them in one chronological listing, each labelled with its actual type (issue comment, review verdict, line comment, reply, suggestion block).

bun run pr:comments                    # current branch's PR — XML, resolved threads hidden
bun run pr:comments 28838              # by PR number
bun run pr:comments '#28838'           # also works
bun run pr:comments https://github.com/oven-sh/bun/pull/28838
bun run pr:comments --include-resolved # also show threads already marked resolved

# Machine-readable output for jq pipelines — one object per entry with
# { when, user, tag, state?, suggestion?, location?, body, url?, resolved?, outdated? }.
# Resolved threads and bot noise (robobun's CI status comment, CodeRabbit
# body-level summaries) are filtered out; --include-resolved restores the former.
bun run pr:comments --json | jq '.[] | select(.user == "Jarred-Sumner")'