This page documents Bun's built-in support for JSX syntax and TypeScript language features during transpilation and bundling. It covers the parsing, transformation, and code generation phases that convert JSX elements and TypeScript-specific syntax into standard JavaScript.
For information about the overall bundling architecture, see Bundler Architecture. For details on the lexer and parser infrastructure, see Lexer and Parser. For code generation and output formatting, see Code Generation and Printing.
Bun transforms JSX syntax into JavaScript function calls during the transpilation phase. The transformation supports multiple JSX runtimes (classic React, automatic, and Solid.js) and can be configured via CLI options, tsconfig.json, or pragma comments in source files.
High-Level Pipeline: Source to Output
Sources: src/js_parser.zig244-302 src/js_parser.zig37-100 src/js_lexer.zig17-35 src/js_printer.zig1-500 src/options.zig1-100
The parser recognizes JSX syntax through the JSXTag structure defined in js_parser.zig. This structure handles opening tags, self-closing tags, and JSX fragments. The parser is instantiated with ParserFeatures.jsx set to the appropriate JSXTransformType.
Key Structures and Functions:
JSXTag.parse() - Main entry point for parsing JSX tags src/js_parser.zig244-302JSXTag.Data - Union type with fragment: u8 or tag: Expr variants src/js_parser.zig224-238JSXTag.TagType - Enum distinguishing fragments from regular tags src/js_parser.zig223NewParser() - Parser factory with compile-time JSX configuration src/ast/P.zig8-16Tag Name Classification:
| Pattern | Example | AST Node | Condition |
|---|---|---|---|
| Lowercase start | <div> | E.String | First char is a-z src/js_parser.zig265 |
Contains - or : | <my-element> | E.String | Contains hyphen or colon src/js_parser.zig265 |
| Uppercase start | <Button> | E.Identifier | First char is uppercase |
| Member expression | <Button.Primary> | E.Dot | Contains . tokens src/js_parser.zig280-299 |
| Fragment | <> | Data.fragment | Empty tag after < src/js_parser.zig248-253 |
Sources: src/js_parser.zig244-302 src/js_parser.zig222-243
JSX Tag Parsing Flow
Sources: src/js_parser.zig244-302 src/js_lexer.zig1-100
Bun supports three JSX runtime modes: classic, automatic (including React 17+ automatic runtime), and Solid.js. Each mode generates different JavaScript output and has different import requirements.
Runtime Configuration Flow
Sources: src/js_parser.zig37-100 src/js_lexer.zig17-35 src/options.zig1-100 src/cli/Arguments.zig72-77 packages/bun-types/bun.d.ts186-193
The JSXImport enum and JSXImport::Symbols struct track which JSX helper functions are used and need to be imported. This tracking enables the bundler to inject only the necessary imports.
JSXImport Symbol Tracking
| Symbol Field | Used In Runtime | JSX Pattern | Import Path (Automatic) |
|---|---|---|---|
jsx: ?LocRef | Automatic (production) | Static children | [importSource]/jsx-runtime |
jsxDEV: ?LocRef | Automatic (development) | All JSX with debug info | [importSource]/jsx-dev-runtime |
jsxs: ?LocRef | Automatic (production) | Static children array | [importSource]/jsx-runtime |
Fragment: ?LocRef | All runtimes | <></> fragments | Runtime-dependent |
createElement: ?LocRef | Classic | All JSX | Manual import required |
Key Functions:
JSXImport::Symbols.get(name: []const u8) - Retrieves ref by symbol name string src/js_parser.zig51-58JSXImport::Symbols.getWithTag(tag: JSXImport) - Retrieves ref by enum tag src/js_parser.zig60-68JSXImport::Symbols.runtimeImportNames(buf) - Returns array of names to import from jsx-runtime src/js_parser.zig70-95JSXImport::Symbols.sourceImportNames() - Returns names to import from source (e.g., React) src/js_parser.zig96-98Import Generation Logic:
Sources: src/js_parser.zig37-100 src/js_parser.zig70-98
JSX configuration can be overridden on a per-file basis using pragma comments. The lexer parses these during the comment-scanning phase and stores them in the JSXPragma struct.
Supported Pragma Directives:
JSXPragma Structure:
The JSXPragma struct stores pragma values as js_ast.Span references to the source text, avoiding string copies src/js_lexer.zig17-35:
Pragma comments override CLI and config file settings for the specific file being parsed. They are parsed by the lexer and stored on a per-source basis.
Sources: src/js_lexer.zig17-35
Bun removes TypeScript type annotations while preserving runtime JavaScript behavior. This process occurs during the parsing phase, where type-related syntax is recognized but not emitted in the output.
Sources: src/js_parser.zig1-100 src/js_parser.zig799-830 src/js_lexer.zig1-100 src/options.zig425
The following TypeScript constructs are completely removed during transpilation:
| Construct | Lexer Token | Parser Handling | Example |
|---|---|---|---|
| Type annotations | T.t_colon + type expression | Parsed and discarded | let x: number |
| Interface declarations | T.t_interface | Skipped entirely | interface Foo {} |
| Type aliases | T.t_type | Skipped entirely | type T = string |
| Type assertions | T.t_as or angle brackets | Expression kept, type removed | x as string |
| Type parameters | T.t_less_than in type position | Parsed via SkipTypeParameterResult | <T> |
| Ambient declarations | T.t_declare | Skipped | declare module 'x' |
| Namespace | T.t_namespace | Skipped | namespace N {} |
Sources: src/js_parser.zig18-35 src/js_lexer.zig1-200
Unlike most TypeScript features, enums are transformed into runtime JavaScript code. The transformation depends on whether the enum is a numeric or string enum.
Numeric Enum Example:
String Enum Example:
Enum handling is implemented through the Ast.TsEnumsMap structure src/options.zig425 which stores enum information during parsing and is used during code generation src/js_printer.zig424
Sources: src/js_printer.zig424 src/options.zig425
React Fast Refresh is a hot-reloading feature that preserves component state during development. Bun supports React Fast Refresh when bundling with the automatic JSX runtime in development mode.
Fast Refresh Integration
Configuration Options:
Fast Refresh is enabled via:
--react-fast-refresh flag src/cli/Arguments.zig95-97react_fast_refresh: bool field src/cli/build_command.zig72BundlerOptions.react_fast_refresh src/cli/Arguments.zig448Implementation Notes:
When react_fast_refresh is enabled, the bundler:
api.Jsx.development = true to use jsxDEV instead of jsx packages/bun-types/bun.d.ts193BundleV2.Watcher for live updatesThe Fast Refresh transformation occurs after JSX transformation but before final code generation.
Sources: src/cli/build_command.zig72 src/bundler/bundle_v2.zig50 packages/bun-types/bun.d.ts193 src/cli/Arguments.zig95-97 src/cli/Arguments.zig448
Type parameter parsing uses a specialized result enum to handle ambiguity between type parameters (<T>) and comparison operators (<):
Type Parameter Parsing Flow
SkipTypeParameterResult Enum Values:
did_not_skip_anything - < is a comparison operator, not a type parameter src/js_parser.zig19could_be_type_cast - Could be <Type>expr cast or comparison src/js_parser.zig20definitely_type_parameters - Definitely <T> or <T, U> type parameters src/js_parser.zig21TypeParameterFlag Packed Struct:
allow_in_out_variance_annotations: bool - TypeScript 4.7 variance keywords src/js_parser.zig26allow_const_modifier: bool - TypeScript 5.0 const type parameters src/js_parser.zig29allow_empty_type_parameters: bool - Allow <> with no parameters src/js_parser.zig32Sources: src/js_parser.zig18-35 src/js_parser.zig24-35
The useDefineForClassFields option from tsconfig.json controls how class fields are initialized. This affects whether fields are defined as own properties or on the prototype.
Configuration Points:
--use-define-for-class-fields src/cli/Arguments.zig1-100useDefineForClassFields boolean test/bundler/expectBundled.ts219Sources: test/bundler/expectBundled.ts219 src/cli/Arguments.zig1-100
Bun supports TypeScript's experimental decorator syntax. Decorator handling involves several parser states and flags.
Decorator Processing
Sources: src/js_parser.zig799-830 src/js_parser.zig809-813 src/js_parser.zig741-756 src/js_parser.zig606-636
Key Structures:
DeferredTsDecorators - Stores decorators with scope information for potential rollback src/js_parser.zig799-805ParseClassOptions.ts_decorators - Array of decorator expressions src/js_parser.zig810PropertyOpts.ts_decorators - Decorators for class properties src/js_parser.zig753FnOrArrowDataParse.has_argument_decorators - Flag for parameter decorators src/js_parser.zig617Decorators may be stripped or preserved depending on the target environment and bundler configuration. When targeting environments that don't support decorators, Bun removes them during transpilation.
Sources: src/js_parser.zig799-830 src/js_parser.zig606-655
JSX and TypeScript features can be configured through multiple sources: CLI arguments, bunfig.toml, tsconfig.json, and pragma comments.
| Option | CLI Flag | Config File | Pragma Comment | Type |
|---|---|---|---|---|
| Runtime mode | --jsx-runtime | jsx.runtime | @jsxRuntime | "automatic" | "classic" | "solid" |
| Import source | --jsx-import-source | jsx.importSource | @jsxImportSource | string |
| Factory | --jsx-factory | jsx.factory | @jsx | string |
| Fragment | --jsx-fragment | jsx.fragment | @jsxFrag | string |
| Development | N/A | jsx.development | N/A | boolean |
| Side effects | N/A | jsx.sideEffects | N/A | boolean |
Sources: packages/bun-types/bun.d.ts186-193 src/cli/Arguments.zig1-200
TypeScript-specific options are primarily loaded from tsconfig.json:
Compiler Options:
useDefineForClassFields - Controls class field semanticsexperimentalDecorators - Enables decorator syntaxjsx - JSX factory mode ("react", "react-jsx", "preserve")jsxFactory - Classic mode factory functionjsxFragmentFactory - Fragment factory functionjsxImportSource - Automatic mode import sourcePath Resolution:
paths - Path mapping for imports src/options.zig1-500baseUrl - Base URL for resolving non-relative importsThe TSConfigJSON structure parses and validates these options src/bun.js/api/JSTranspiler.zig1-100
Sources: src/bun.js/api/JSTranspiler.zig1-100 src/options.zig1-500
Different file loaders apply different JSX and TypeScript transformations:
| Loader | JSX Parsing | Type Stripping | Special Handling |
|---|---|---|---|
.tsx | Yes | Yes | Full TypeScript + JSX |
.jsx | Yes | No | JSX only, no type syntax |
.ts | No | Yes | TypeScript only, no JSX |
.js | No | No | Plain JavaScript |
.mts/.cts | No | Yes | TypeScript with explicit module type |
Loader selection is determined by file extension or the --loader CLI option src/options.zig1-500
Sources: src/options.zig1-500 src/js_parser.zig960-968
JSX and TypeScript transformations are applied during the parsing phase of bundling, before tree-shaking and code generation.
Sources: src/bundler/bundle_v2.zig1-500 src/js_parser.zig960-968
The bundler selects the appropriate parser based on the file's loader. These are instantiated via NewParser() with different ParserFeatures:
JavaScriptParser - NewParser(.{ .typescript = false, .jsx = .none }) src/js_parser.zig960JSXParser - NewParser(.{ .typescript = false, .jsx = ... }) src/js_parser.zig961TSXParser - NewParser(.{ .typescript = true, .jsx = ... }) src/js_parser.zig962TypeScriptParser - NewParser(.{ .typescript = true, .jsx = .none }) src/js_parser.zig963Import scanners exist for faster dependency resolution without full parsing src/js_parser.zig964-967:
JavaScriptImportScanner - ESM import scanning onlyJavaScriptImportScannerCommonJS - CommonJS require scanningTypeScriptImportScanner - TypeScript import scanningSources: src/js_parser.zig960-968 src/ast/P.zig8-30
For on-the-fly transpilation (e.g., in bun run), Bun uses a runtime transpiler cache that stores the result of JSX and TypeScript transformations. Changes to the parser that affect runtime behavior require incrementing expected_version in RuntimeTranspilerCache.zig src/js_parser.zig1-4
Sources: src/js_parser.zig1-4 src/bun.js/api/JSTranspiler.zig1-100
JSX-specific escape sequences in template literals are preserved when quote character is backtick src/js_printer.zig262-280:
Sources: src/js_printer.zig262-280
TypeScript namespace imports require special handling to avoid conflicts with ESM namespace objects src/js_parser.zig1-100
Sources: src/js_parser.zig1-100
When TypeScript files mix CommonJS and ESM syntax, Bun determines the module format and applies appropriate transformations. The cjs2esm option controls whether CommonJS-style exports are converted to ESM test/bundler/expectBundled.ts260
Sources: test/bundler/expectBundled.ts260
JSX component names follow specific rules:
These rules are implemented in JSXTag::parse() src/js_parser.zig257-302
Sources: src/js_parser.zig257-302
Refresh this wiki