Skip to main content
Bun natively supports JSX and TSX syntax without requiring a separate build step. JSX is transformed to JavaScript function calls at runtime.

Quick Start

Run JSX/TSX files directly:
Run JSX
bun run App.jsx
bun run Component.tsx
No Babel, no webpack needed.

JSX Transformation

Bun transforms JSX elements into function calls:
JSX input
const element = <div className="container">Hello</div>;
Output (classic runtime)
const element = React.createElement("div", { className: "container" }, "Hello");
Output (automatic runtime)
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("div", { className: "container", children: "Hello" });

JSX Runtimes

Bun supports multiple JSX transformation modes:

Classic Runtime (React.createElement)

Default behavior for React:
Classic runtime
import React from "react";

function Button({ children }) {
  return <button className="btn">{children}</button>;
}
Transformed to:
import React from "react";

function Button({ children }) {
  return React.createElement("button", { className: "btn" }, children);
}
Configuration:
tsconfig.json
{
  "compilerOptions": {
    "jsx": "react"
  }
}

Automatic Runtime (react/jsx-runtime)

Modern React 17+ runtime:
Automatic runtime
// No React import needed!
function Button({ children }) {
  return <button className="btn">{children}</button>;
}
Transformed to:
import { jsx as _jsx } from "react/jsx-runtime";

function Button({ children }) {
  return _jsx("button", { className: "btn", children });
}
Configuration:
tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  }
}

Development Runtime

Includes debug information:
tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsxdev"
  }
}
Adds __source and __self props for better debugging.

Preserve

Leaves JSX unchanged (for further processing):
tsconfig.json
{
  "compilerOptions": {
    "jsx": "preserve"
  }
}

Configuration

tsconfig.json

Configure JSX behavior:
tsconfig.json
{
  "compilerOptions": {
    // JSX transformation mode
    "jsx": "react-jsx",
    
    // Factory function (classic runtime)
    "jsxFactory": "h",
    
    // Fragment factory (classic runtime)
    "jsxFragmentFactory": "Fragment",
    
    // Import source (automatic runtime)
    "jsxImportSource": "preact"
  }
}

Runtime Configuration

Override JSX settings programmatically:
Runtime JSX config
import { Transpiler } from "bun";

const transpiler = new Transpiler({
  loader: "tsx",
  jsxFactory: "h",
  jsxFragment: "Fragment",
  jsxImportSource: "preact",
});

const code = transpiler.transformSync(source);

Custom JSX Factories

Use JSX with any framework:

Preact

tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}
Preact JSX
import { render } from "preact";

function App() {
  return <div>Hello Preact!</div>;
}

render(<App />, document.body);

Custom h() function

tsconfig.json
{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}
Custom h() function
function h(type, props, ...children) {
  return { type, props: { ...props, children } };
}

function Fragment({ children }) {
  return children;
}

const vdom = <div>Custom JSX</div>;
console.log(vdom); // { type: "div", props: { children: "Custom JSX" } }

Vue JSX

tsconfig.json
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "vue"
  }
}

Solid.js

tsconfig.json
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "solid-js"
  }
}

JSX Features

Children

JSX children
// Single child
<div>Hello</div>

// Multiple children
<div>
  <span>First</span>
  <span>Second</span>
</div>

// Expression children
<div>{user.name}</div>

// Array children
<ul>
  {items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>

Fragments

Fragments
import { Fragment } from "react";

// Explicit Fragment
<Fragment>
  <div>First</div>
  <div>Second</div>
</Fragment>

// Short syntax
<>
  <div>First</div>
  <div>Second</div>
</>

Spread Attributes

Spread attributes
const props = { className: "btn", disabled: true };

<button {...props}>Click</button>
// Becomes: <button className="btn" disabled={true}>Click</button>

Conditional Rendering

Conditional rendering
// Ternary
{isLoggedIn ? <UserPanel /> : <LoginForm />}

// Logical AND
{isAdmin && <AdminPanel />}

// Nullish coalescing
{userName ?? "Guest"}

Event Handlers

Event handlers
<button onClick={() => console.log("clicked")}>Click</button>

<input onChange={(e) => setValue(e.target.value)} />

Ref Attributes

Refs
import { useRef } from "react";

function Input() {
  const ref = useRef<HTMLInputElement>(null);
  
  return <input ref={ref} />;
}

Namespaced Attributes

Namespaced attributes
<svg>
  <use xlinkHref="#icon" />
</svg>

// Custom namespaces
<div data-test-id="button" aria-label="Submit" />

TypeScript + JSX (TSX)

Combine TypeScript types with JSX:
TypeScript JSX
interface ButtonProps {
  variant: "primary" | "secondary";
  onClick: () => void;
  children: React.ReactNode;
}

function Button({ variant, onClick, children }: ButtonProps) {
  return (
    <button className={`btn-${variant}`} onClick={onClick}>
      {children}
    </button>
  );
}

// Type-safe usage
<Button variant="primary" onClick={() => {}}>
  Click me
</Button>

Generic Components

Generic components
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => JSX.Element;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map(renderItem)}</ul>;
}

<List
  items={[1, 2, 3]}
  renderItem={(n) => <li key={n}>{n}</li>}
/>

Type Assertions in JSX

Type assertions
// Generic components
const div = <div /> as React.ReactElement<HTMLDivElement>;

// Type casting
const input = <input type="text" /> as HTMLInputElement;

JSX Performance

Automatic Import Optimization

Bun automatically imports JSX functions only when needed:
Automatic imports
// Input
function Component() {
  return <div>Hello</div>;
}

// Output (automatic runtime)
import { jsx as _jsx } from "react/jsx-runtime";

function Component() {
  return _jsx("div", { children: "Hello" });
}
No imports added if no JSX in file.

Static Children Optimization

Static optimization
// Multiple children optimized
<div>
  <span>A</span>
  <span>B</span>
</div>

// Uses jsxs() for static children array
import { jsxs as _jsxs } from "react/jsx-runtime";
_jsxs("div", { children: [_jsx("span", { children: "A" }), _jsx("span", { children: "B" })] });

React Fast Refresh

Bun supports React Fast Refresh for hot reloading:
React Fast Refresh
// Automatically detected
export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Enable Fast Refresh:
bun run --hot app.tsx
Implementation: src/runtime.zig:147 - React Fast Refresh feature flag

JSX Outside React

Use JSX with any virtual DOM library:

Minimal JSX Runtime

Custom JSX runtime
// jsx-runtime.ts
export function jsx(type: string, props: any) {
  return { type, props };
}

export function jsxs(type: string, props: any) {
  return { type, props };
}

export const Fragment = ({ children }: any) => children;
tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "./jsx-runtime"
  }
}

DOM JSX

Create real DOM elements:
DOM JSX
function h(type: string, props: any, ...children: any[]) {
  const el = document.createElement(type);
  Object.assign(el, props);
  children.forEach(child => {
    if (typeof child === "string") {
      el.appendChild(document.createTextNode(child));
    } else {
      el.appendChild(child);
    }
  });
  return el;
}

// Usage
const app = <div className="app">Hello!</div>;
document.body.appendChild(app);

Debugging JSX

Inspecting Transformed Code

View transpiled output
bun build app.jsx --target=bun --format=esm > output.js
cat output.js

Source Maps

Bun generates inline source maps for JSX:
Original source
function App() {
  return <div>Hello</div>;
}
Debugger shows original JSX, not transformed JavaScript.

JSX Debug Mode

Debug JSX transform
BUN_DEBUG_QUIET_LOGS=0 bun run app.jsx 2>&1 | grep -i jsx

Implementation

JSX transformation implementation:
  • Parser: src/js_parser.zig - Parses JSX syntax
  • Printer: src/js_printer.zig - Generates function calls
  • Runtime: src/runtime.zig:159 - Auto-import JSX configuration
  • Options: src/options.zig - JSX pragma configuration

Feature Flags

Runtime JSX configuration (src/runtime.zig:159)
auto_import_jsx: bool = false,

Common Patterns

Conditional Class Names

Conditional classes
function Button({ primary, disabled }) {
  return (
    <button
      className={[
        "btn",
        primary && "btn-primary",
        disabled && "btn-disabled",
      ].filter(Boolean).join(" ")}
    >
      Click
    </button>
  );
}

Render Props

Render props
interface DataFetcherProps<T> {
  url: string;
  children: (data: T) => JSX.Element;
}

function DataFetcher<T>({ url, children }: DataFetcherProps<T>) {
  const [data, setData] = useState<T | null>(null);
  
  useEffect(() => {
    fetch(url).then(r => r.json()).then(setData);
  }, [url]);
  
  return data ? children(data) : <div>Loading...</div>;
}

// Usage
<DataFetcher url="/api/users">
  {(users) => <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>}
</DataFetcher>

Component Composition

Component composition
function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
    </div>
  );
}

function App() {
  return (
    <Card title="Welcome">
      <p>This is the card content</p>
    </Card>
  );
}

Troubleshooting

React is not defined

Error
ReferenceError: React is not defined
Solution: Use automatic runtime or import React:
tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx"
  }
}
Or:
import React from "react";

JSX not transforming

Ensure file has .jsx or .tsx extension:
# Won't transform
bun run app.js

# Will transform
bun run app.jsx

Custom pragma not working

Check tsconfig.json is in project root and properly formatted.

Build docs developers (and LLMs) love