9.1 KiB
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
- Creates a debug build at
- Run tests with your debug build:
bun bd test <test-file>- CRITICAL: Never use
bun testdirectly - it won't include your changes
- CRITICAL: Never use
- Run any command with debug build:
bun bd <command>
Tip: Bun is already installed and in $PATH. The bd subcommand is a package.json script.
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 teststest/js/web/- Web API tests (fetch, WebSocket, streams, etc.)test/cli/- CLI command tests (install, run, test, etc.)test/bundler/- Bundler and transpiler tests. UseitBundledhelper.test/integration/- End-to-end integration teststest/napi/- N-API compatibility teststest/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
-eovertempDir. - For multi-file tests, prefer
tempDirandBun.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
normalizeBunSnapshotto 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
tempDirfrom"harness"to create a temporary directory. Do not usetmpdirSyncorfs.mkdtempSyncto 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
setTimeoutin tests. Instead,awaitthe 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 withbun bd test <file>. Your test is NOT VALID if it passes withUSE_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.tsand other sources. Bun will automatically rebuild these files when you make changes to them.
Core Source Organization
Runtime Core (src/)
bun.zig- Main entry pointcli.zig- CLI command orchestrationjs_parser.zig,js_lexer.zig,js_printer.zig- JavaScript parsing/printingtranspiler.zig- Wrapper around js_parser with sourcemap supportresolver/- Module resolution systemallocators/- Custom memory allocators for performance
JavaScript Runtime (src/bun.js/)
bindings/- C++ JavaScriptCore bindings- Generated classes from
.classes.tsfiles - Manual bindings for complex APIs
- Generated classes from
api/- Bun-specific APIsserver.zig- HTTP server implementationFFI.zig- Foreign Function Interfacecrypto.zig- Cryptographic operationsglob.zig- File pattern matching
node/- Node.js compatibility layer- Module implementations (fs, path, crypto, etc.)
- Process and Buffer APIs
webcore/- Web API implementationsfetch.zig- Fetch APIstreams.zig- Web StreamsBlob.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 managerlockfile/- Lockfile handlingnpm.zig- npm registry clientlifecycle_script_runner.zig- Package scripts
Other Key Components
src/shell/- Cross-platform shell implementationsrc/css/- CSS parser and processorsrc/http/- HTTP client implementationwebsocket_client/- WebSocket client (including deflate support)
src/sql/- SQL database integrationssrc/bake/- Server-side rendering framework
JavaScript Class Implementation (C++)
When implementing JavaScript classes in C++:
-
Create three classes if there's a public constructor:
class Foo : public JSC::JSDestructibleObject(if has C++ fields)class FooPrototype : public JSC::JSNonFinalObjectclass FooConstructor : public JSC::InternalFunction
-
Define properties using HashTableValue arrays
-
Add iso subspaces for classes with C++ fields
-
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.tsfilessrc/codegen/generate-jssink.ts- Generates stream-related classessrc/codegen/bundle-modules.ts- Bundles built-in modules likenode:fssrc/codegen/bundle-functions.ts- Bundles global functions likeReadableStream
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 (likews)internal/- Internal modules not exposed to usersbuiltins/- Core JavaScript builtins (streams, console, etc.)
Important Development Notes
- Never use
bun testorbun <file>directly - always usebun bd testorbun bd <command>.bun bdcompiles & runs the debug build. - All changes must be tested - if you're not testing your changes, you're not done.
- Get your tests to pass. If you didn't run the tests, your code does not work.
- Follow existing code style - check neighboring files for patterns
- Create tests in the right folder in
test/and the test must end in.test.tsor.test.tsx - Use absolute paths - Always use absolute paths in file operations
- Avoid shell commands - Don't use
findorgrepin tests; use Bun's Glob and built-in tools - Memory management - In Zig code, be careful with allocators and use defer for cleanup
- Cross-platform - Run
bun run zig:check-allto compile the Zig code on all platforms when making platform-specific changes - Debug builds - Use
BUN_DEBUG_QUIET_LOGS=1to disable debug logging, orBUN_DEBUG_<scopeName>=1to enable specificOutput.scoped(.${scopeName}, .visible)s - Be humble & honest - NEVER overstate what you got done or what actually works in commits, PRs or in messages to the user.
- 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.