oven-sh/bun
Parser & transpiler
The parser, lexer, printer, and transpiler are the heart of Bun. The same code is used by the runtime (to load import "./foo.ts"), the bundler (to produce a build), and the bundled type-stripper (for bun build --target=node).
Files
| File | Size | Role |
|---|---|---|
src/js_lexer.zig |
~143 KB | Tokeniser. Handles UTF-8/UTF-16 input, JSX, template literals, regex literals, numeric literals (BigInt, hex/oct/bin, separators). |
src/js_lexer_tables.zig |
~28 KB | Static tables generated for the lexer. |
src/js_parser.zig |
~47 KB top-level + ~25 KB of generated includes | The parser. Produces an ast.zig AST. |
src/js_printer.zig |
~260 KB | The printer. Emits source from an AST with sourcemap entries. The largest single Zig file in src/. |
src/transpiler.zig |
~72 KB | High-level wrapper that ties parser + printer + sourcemap together. |
src/ast.zig |
~25 KB | AST node tags and shared helpers. |
src/ast/ |
~16 KB dir | Per-node modules used by the parser. |
src/renamer.zig |
~35 KB | Symbol renaming utilities used by the printer to mangle / dedupe identifiers. |
src/runtime.zig, src/runtime.js |
~20 KB | The injected runtime (e.g. __commonJS, __esm, JSX _jsx helper) prepended into bundle output. |
How a TS file becomes JS
graph LR
Bytes["foo.ts bytes"] --> Lexer[js_lexer.zig]
Lexer --> Tokens
Tokens --> Parser[js_parser.zig]
Parser --> AST[ast.zig nodes]
AST --> Pass[bundler / runtime passes]
Pass --> Printer[js_printer.zig]
Printer --> Out[JS bytes + sourcemap]A "pass" here might be: tree-shaking marks (bundler), macro substitution (bundler), CommonJS detection (runtime), or just nothing extra (runtime fast path).
AST shape
Every AST node has a tag like E.Identifier, E.Call, S.For, B.Object. E.* are expressions, S.* are statements, B.* are bindings (destructuring patterns). The full set is enumerated in src/ast.zig plus per-node files under src/ast/.
A Ref is the parser's identifier handle: a (source_index, inner_index) tuple that maps into a Symbol table. Symbols carry name, kind (hoisted, import, class, ...), use count, and link information. The bundler renames symbols by editing the table — the AST keeps the same Refs.
Parser shape
js_parser.zig is a recursive descent parser. The top-level entry is Parser.parse which returns an Ast with toplevel statements. Internal helpers (parseExpression, parseStatement, parseBinding, ...) follow the ECMAScript grammar with extensions for TypeScript syntax, JSX, decorators, and Bun-specific bits (like macro imports).
TypeScript handling is structural rather than typed: the parser strips type annotations, parses generics for syntax validity, and emits the equivalent JavaScript. There is no type checking. For type checking, the project relies on tsc invoked via bun run typecheck.
Printer shape
js_printer.zig walks the AST and emits a byte stream. It also produces a sourcemap mapping output positions back to input positions. The printer has knobs for:
minify_whitespace,minify_identifiers,minify_syntax(each independently togglable).module_type— ESM, CJS, IIFE.target— bun, node, browser. Determines which globals are available to inline.mangle_props— runtime/bundler property mangling for size.runtime— which helpers to prepend (__commonJS,__esm, JSX, ...).
Almost every code-emission decision is in js_printer.zig rather than in transformations. Source-aware optimisations (constant folding, dead branch elision) are split between the parser (js_parser.zig's constant-folding pass) and the bundler.
Transpiler wrapper
src/transpiler.zig is the integration point that the runtime and JSTranspiler.zig both call. It:
- Reads the source from disk or memory.
- Picks options from the loader and
tsconfig.json. - Invokes the parser.
- Runs minimal post-passes (CJS detection, runtime injection).
- Invokes the printer.
- Caches the result in
RuntimeTranspilerCache.zig(runtime) or returns it directly (bundler).
JSTranspiler.zig (~46 KB) is the JS-visible wrapper that exposes Bun.Transpiler, allowing scripts to transpile arbitrary code.
Renamer
src/renamer.zig implements two independent renaming strategies:
- A "minify" renamer that picks the shortest unused identifier per scope.
- A "preserve" renamer that picks unique names but keeps them readable, used during bundling to avoid collisions across merged modules.
Both run after parsing but before printing, so the printer just consumes a (Ref → name) map.
Macros
A macro import (import { x } from "./macros.ts" with { type: "macro" }) is detected at parse time. The bundler then evaluates the macro body in the runtime VM during the bundle, turns the resulting JS value into AST, and substitutes it back into the call site. Only the bundler invokes macros; the runtime ignores the type: "macro" attribute.
Tests
Unit tests for parsing/printing live under test/bundler/ (using itBundled for end-to-end input/output assertions) and test/transpiler/. Edge cases for TypeScript and JSX have their own files.
Performance
The parser is a hot path for both the runtime (every imported file) and the bundler (every input file across many threads). Specific design decisions for speed:
- Tokens are emitted into a
Lexerthat the parser drives withnext(). There's no token array — the parser consumes the lexer directly. bun.stringsSIMD-accelerated comparisons are used wherever the parser checks for keywords or identifier characters.- Symbol tables are flat arrays indexed by
Ref, not hash maps. - The printer writes to a pre-allocated buffer; sourcemap generation uses a delta encoding so the buffer doesn't need rewinding.
Integration points
- Runtime — Via
src/transpiler.zigandsrc/bun.js/RuntimeTranspilerCache.zig. See Runtime. - Bundler — Via
src/bundler/ParseTask.zigcalling the parser directly. See Bundler. Bun.Transpiler— Viasrc/bun.js/api/JSTranspiler.zig.- Tests —
bun testparses the test files through the same pipeline.
Entry points for modification
- To add or change syntax handling, edit
src/js_parser.zig. Parsers for new constructs typically live in helper functions namedparseFoo. - To change emitted output, edit
src/js_printer.zig. There are large regions tagged with comments describing the output shape (e.g. JSX, async/await, decorators). - To change how the transpiler caches, edit
src/bun.js/RuntimeTranspilerCache.zig.
Key source files
| File | Purpose |
|---|---|
src/js_lexer.zig |
Tokeniser. |
src/js_parser.zig |
Parser. |
src/js_printer.zig |
Printer. |
src/transpiler.zig |
Glue layer used by runtime and Bun.Transpiler. |
src/renamer.zig |
Symbol renamer. |
src/ast.zig |
AST tags. |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.