This page documents Bun's built-in support for JSX syntax and TypeScript language features during transpilation and bundling. Bun treats JSX and TypeScript as first-class citizens, handling them in its Rust-based parser/transpiler without external dependencies like Babel or tsc.
The core of this support resides in the lexer and parser, which perform type stripping and JSX lowering on-the-fly. This allows Bun to execute .ts, .tsx, .js, and .jsx files with near-zero configuration. The same pipeline is used by both bun run (single-file transpilation) and bun build (bundled output).
Relevant source modules:
| Module | Path | Role |
|---|---|---|
| Parser struct | src/js_parser/p.rs | Core P<'a, TYPESCRIPT, SCAN_ONLY> struct |
| JSX parsing | src/js_parser/parse/parse_jsx.rs | Parses JSX elements into AST nodes |
| TS type skipping | src/js_parser/parse/parse_skip_typescript.rs | Advances lexer past type annotations |
| TS statements | src/js_parser/parse/parse_typescript.rs | Handles enums, namespaces, decorators |
| Expression visitor | src/js_parser/visit/visit_expr.rs | JSX lowering, define substitution, macro expansion |
| Statement visitor | src/js_parser/visit/visit_stmt.rs | Enum/namespace lowering, declare erasure |
| Printer | src/js_printer/lib.rs | Serializes the final AST to text |
Sources: src/js_parser/p.rs1-100 src/js_parser/parse/mod.rs1-15
Bun transforms JSX syntax into standard JavaScript function calls. It supports the Classic runtime (e.g., React.createElement), the Automatic runtime (e.g., _jsx / _jsxDEV), and can be configured with a custom factory.
JSX Source-to-Output Pipeline (code entity view)
Sources: src/js_parser/p.rs221-230 src/js_parser/parse/parse_jsx.rs1-30 src/js_parser/visit/visit_expr.rs1-25
The P struct stores JSX handling as a runtime field rather than a const-generic, because JSX only affects a small subset of expression arms and using a const-generic would create redundant monomorphizations:
P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> {
pub jsx_transform: JSXTransformType, // runtime field — not a const generic
...
}
The ParserFeatures struct mirrors the three axes of parser configuration:
ParserFeatures {
typescript: bool,
jsx: JSXTransformType,
scan_only: bool,
}
Sources: src/js_parser/p.rs92-102 src/js_parser/p.rs221-228
The parser, via parse_jsx.rs, recognizes JSX elements and classifies the tag to determine if it is an intrinsic HTML element or a component reference:
| Pattern | Example | AST Representation | Detail |
|---|---|---|---|
| Lowercase tag | <div> | Tag as string literal | Treated as intrinsic HTML element |
| Uppercase tag | <Button> | Tag as identifier reference | Treated as user-defined component |
| Member expression | <UI.Button> | Tag as E::Dot | Property access on an object |
| Fragment | <></> | Fragment symbol | Maps to the configured fragment factory |
Sources: src/js_parser/parse/parse_jsx.rs1-50 test/bundler/bundler_npm.test.ts6-75
JSX transform behavior is configured via tsconfig.json's compilerOptions.jsx field or directly via Bun.Transpiler / bun build options.
| Mode | Behavior | Import Injection |
|---|---|---|
| Classic | JSX becomes React.createElement(tag, props, ...children) | None — factory must be in scope |
| Automatic | JSX becomes _jsx(tag, props) or _jsxs / _jsxDEV | Auto-injected from react/jsx-runtime or the configured importSource |
In automatic mode, _jsxDEV is used when the development flag is set (for better error messages with source location). _jsxs is used for elements with multiple static children.
The JSXImport type (imported into p.rs) represents the set of runtime imports that the visitor will inject based on what JSX constructs it encounters during the visit pass.
Bun.TranspilerThe Bun.Transpiler API exposes JSX options directly:
Sources: src/js_parser/p.rs24-29 test/bundler/transpiler/transpiler.test.js1-21 test/bundler/bundler_npm.test.ts6-75
Bun strips TypeScript types during the parsing phase. Type checking is not performed — that is left to tsc or an IDE. The parser advances the lexer past type-only syntax and emits nothing for type-only constructs.
The const-generic TYPESCRIPT: bool on P<'a, TYPESCRIPT, SCAN_ONLY> gates the TypeScript code paths at the Rust monomorphization level. When TYPESCRIPT = false, the compiler elides all TypeScript-specific branches entirely.
TypeScript Parsing Flow
Sources: src/js_parser/p.rs221-240 src/js_parser/parse/parse_skip_typescript.rs1-50 src/js_parser/parse/parse_typescript.rs1-30
| Construct | Handling |
|---|---|
interface Foo {} / type Foo = ... | Declaration parsed and dropped entirely |
: string type annotations | skip_type_script_type() advances past the type; nothing emitted |
as Type / <Type>expr assertions | Assertion dropped; expression value preserved |
declare var/let/const/class/function | Parsed but not emitted test/bundler/esbuild/ts.test.ts11-84 |
const enum | Inlined as numeric literals at use sites |
enum | Lowered to an IIFE that populates a bidirectional map object |
namespace / module | Lowered to a scoped IIFE that assigns to a shared object |
Type parameters <T> | Stripped from function/class/call-expression signatures |
import type ... | Removed entirely; no runtime import emitted |
The skip_type_script_type_with_opts() function in parse_skip_typescript.rs is the central workhorse for advancing past complex type expressions. It handles generics, conditional types, template literal types, mapped types, and infer constraints — including memoizing backtracked infer extends attempts to avoid exponential re-parsing src/js_parser/p.rs330-355
TypeScript class fields obey the useDefineForClassFields option from tsconfig.json:
useDefineForClassFields: true — fields are emitted as Object.defineProperty calls (standard ECMAScript semantics) test/bundler/esbuild/ts.test.ts134-150useDefineForClassFields: false (TypeScript default when target < ES2022) — fields are assigned in the constructor body test/bundler/esbuild/ts.test.ts102-133declare field annotations are dropped without any runtime effect:
Sources: test/bundler/esbuild/ts.test.ts85-150 src/js_parser/parse/parse_skip_typescript.rs16-55
Regular enums are lowered to an IIFE pattern at runtime:
const enum members are inlined as their constant numeric or string values at every use site, and the enum declaration itself is dropped. Enum member values support TypeScript's constant expression rules (arithmetic, bitwise operations, references to other constant members) src/js_parser/p.rs343-367
Sources: test/bundler/transpiler/transpiler.test.js519-539 src/js_parser/p.rs343-367
namespace and module declarations are lowered to IIFEs that accumulate members on a shared object:
Nested namespaces are handled recursively. declare namespace blocks are erased entirely test/bundler/transpiler/transpiler.test.js261-285
Sources: test/bundler/transpiler/transpiler.test.js261-285 src/js_parser/visit/visit_stmt.rs1-30
Bun supports two decorator models:
TYPESCRIPT = true and the source uses @ syntax before class declarations.p.options.features.standard_decorators.In parse_stmt.rs, the t_at handler checks both flags and dispatches to parse_type_script_decorators(). The parsed decorator expressions are stored as DeferredTsDecorators on the ParseStatementOptions and applied when the class body is subsequently parsed:
t_at (parse_stmt.rs)
→ parse_type_script_decorators()
→ stores in opts.ts_decorators: DeferredTsDecorators { values, scope_index }
→ parse_class_stmt() applies them
During the visit pass (visit_stmt.rs), decorators are lowered to call expressions applied around the class definition.
Sources: src/js_parser/parse/parse_stmt.rs77-128 src/js_parser/visit/visit_stmt.rs1-30
Bun uses tsconfig.json to configure the transpiler and resolver. The resolver parses this file and propagates relevant settings into ParserOptions before each file is parsed. Key settings that affect JSX and TypeScript behavior:
tsconfig.json field | Effect |
|---|---|
compilerOptions.jsx | Sets JSXTransformType (react, react-jsx, react-jsxdev, preserve) |
compilerOptions.jsxFactory | Sets the Classic runtime factory name (default: React.createElement) |
compilerOptions.jsxFragmentFactory | Sets the fragment factory name (default: React.Fragment) |
compilerOptions.jsxImportSource | Sets the import source for Automatic runtime (default: react) |
compilerOptions.useDefineForClassFields | Controls class field emit semantics |
compilerOptions.paths + baseUrl | Used by the resolver for module path mapping (see page 2.3) |
extends | Config inheritance — settings are merged from the base config |
Bun also supports ${configDir} template variables in paths entries, allowing paths to be anchored to the directory containing the tsconfig.json file.
Sources: test/bundler/bundler_edgecase.test.ts433-477 test/bundler/esbuild/ts.test.ts85-150
The BundleV2 struct coordinates the transpiler and linker. During bundling, every module is passed through the Transpiler which applies the JSX and TypeScript rules described above.
Bundler Interaction Diagram
Key Code Entities:
BundleV2: The main orchestrator for the bundling process src/bundler/bundle_v2.zig107-150Transpiler: Handles the conversion of individual files into executable JavaScript src/bundler/bundle_v2.zig108LinkerContext: Manages the overall state of the bundle, including tree-shaking and symbol resolution src/bundler/bundle_v2.zig117TSConfigJSON: Represents the parsed configuration used to guide the transpiler's behavior src/resolver/tsconfig_json.zig12-45Sources: src/bundler/bundle_v2.zig107-150 test/bundler/expectBundled.ts148-214 src/resolver/tsconfig_json.zig12-45
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.