Open-Source Wikis

/

Bun

/

Systems

/

Parser & transpiler

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:

  1. Reads the source from disk or memory.
  2. Picks options from the loader and tsconfig.json.
  3. Invokes the parser.
  4. Runs minimal post-passes (CJS detection, runtime injection).
  5. Invokes the printer.
  6. 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 Lexer that the parser drives with next(). There's no token array — the parser consumes the lexer directly.
  • bun.strings SIMD-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.zig and src/bun.js/RuntimeTranspilerCache.zig. See Runtime.
  • Bundler — Via src/bundler/ParseTask.zig calling the parser directly. See Bundler.
  • Bun.Transpiler — Via src/bun.js/api/JSTranspiler.zig.
  • Testsbun test parses 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 named parseFoo.
  • 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.

Parser & transpiler – Bun wiki | Factory