## Summary
Implements the `jsxSideEffects` option to control whether JSX elements
are marked as pure for dead code elimination, matching esbuild's
behavior from their TestJSXSideEffects test case.
## Features Added
- **tsconfig.json support**: `{"compilerOptions": {"jsxSideEffects":
true}}`
- **CLI flag support**: `--jsx-side-effects`
- **Dual runtime support**: Works with both classic
(`React.createElement`) and automatic (`jsx`/`jsxs`) JSX runtimes
- **Production/Development modes**: Works in both production and
development environments
- **Backward compatible**: Default value is `false` (maintains existing
behavior)
## Behavior
- **Default (`jsxSideEffects: false`)**: JSX elements marked with `/*
@__PURE__ */` comments (can be eliminated by bundlers)
- **When `jsxSideEffects: true`**: JSX elements NOT marked as pure
(always preserved)
## Example Usage
### tsconfig.json
```json
{
"compilerOptions": {
"jsxSideEffects": true
}
}
```
### CLI
```bash
bun build --jsx-side-effects
```
### Output Comparison
```javascript
// Input: console.log(<div>test</div>);
// Default (jsxSideEffects: false):
console.log(/* @__PURE__ */ React.createElement("div", null, "test"));
// With jsxSideEffects: true:
console.log(React.createElement("div", null, "test"));
```
## Implementation Details
- Added `side_effects: bool = false` field to `JSX.Pragma` struct
- Updated tsconfig.json parser to handle `jsxSideEffects` option
- Added CLI argument parsing for `--jsx-side-effects` flag
- Modified JSX element visiting logic to respect the `side_effects`
setting
- Updated API schema with proper encode/decode support
- Enhanced test framework to support the new JSX option
## Comprehensive Test Coverage (12 Tests)
### Core Functionality (4 tests)
- ✅ Classic JSX runtime with default behavior (includes `/* @__PURE__
*/`)
- ✅ Classic JSX runtime with `side_effects: true` (no `/* @__PURE__ */`)
- ✅ Automatic JSX runtime with default behavior (includes `/* @__PURE__
*/`)
- ✅ Automatic JSX runtime with `side_effects: true` (no `/* @__PURE__
*/`)
### Production Mode (4 tests)
- ✅ Classic JSX runtime in production with default behavior
- ✅ Classic JSX runtime in production with `side_effects: true`
- ✅ Automatic JSX runtime in production with default behavior
- ✅ Automatic JSX runtime in production with `side_effects: true`
### tsconfig.json Integration (4 tests)
- ✅ Default tsconfig.json behavior (automatic runtime, includes `/*
@__PURE__ */`)
- ✅ tsconfig.json with `jsxSideEffects: true` (automatic runtime, no `/*
@__PURE__ */`)
- ✅ tsconfig.json with `jsx: "react"` and `jsxSideEffects: true`
(classic runtime)
- ✅ tsconfig.json with `jsx: "react-jsx"` and `jsxSideEffects: true`
(automatic runtime)
### Snapshot Testing
All tests include inline snapshots demonstrating the exact output
differences, providing clear documentation of the expected behavior.
### Existing Compatibility
- ✅ All existing JSX tests continue to pass
- ✅ Cross-platform Zig compilation succeeds
## Closes
Fixes #22295
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
7.0 KiB
Bun supports .jsx and .tsx files out of the box. Bun's internal transpiler converts JSX syntax into vanilla JavaScript before execution.
function Component(props: {message: string}) {
return (
<body>
<h1 style={{color: 'red'}}>{props.message}</h1>
</body>
);
}
console.log(<Component message="Hello world!" />);
Configuration
Bun reads your tsconfig.json or jsconfig.json configuration files to determines how to perform the JSX transform internally. To avoid using either of these, the following options can also be defined in bunfig.toml.
The following compiler options are respected.
jsx
How JSX constructs are transformed into vanilla JavaScript internally. The table below lists the possible values of jsx, along with their transpilation of the following simple JSX component:
<Box width={5}>Hello</Box>
{% table %}
- Compiler options
- Transpiled output
-
{ "jsx": "react" } -
import { createElement } from "react"; createElement("Box", { width: 5 }, "Hello");
-
{ "jsx": "react-jsx" } -
import { jsx } from "react/jsx-runtime"; jsx("Box", { width: 5 }, "Hello");
-
{ "jsx": "react-jsxdev" } -
import { jsxDEV } from "react/jsx-dev-runtime"; jsxDEV( "Box", { width: 5, children: "Hello" }, undefined, false, undefined, this, );The
jsxDEVvariable name is a convention used by React. TheDEVsuffix is a visible way to indicate that the code is intended for use in development. The development version of React is slower and includes additional validity checks & debugging tools.
-
{ "jsx": "preserve" } -
// JSX is not transpiled // "preserve" is not supported by Bun currently <Box width={5}>Hello</Box>
{% /table %}
jsxFactory
{% callout %}
Note — Only applicable when jsx is react.
{% /callout %}
The function name used to represent JSX constructs. Default value is "createElement". This is useful for libraries like Preact that use a different function name ("h").
{% table %}
- Compiler options
- Transpiled output
-
{ "jsx": "react", "jsxFactory": "h" } -
import { h } from "react"; h("Box", { width: 5 }, "Hello");
{% /table %}
jsxFragmentFactory
{% callout %}
Note — Only applicable when jsx is react.
{% /callout %}
The function name used to represent JSX fragments such as <>Hello</>; only applicable when jsx is react. Default value is "Fragment".
{% table %}
- Compiler options
- Transpiled output
-
{ "jsx": "react", "jsxFactory": "myjsx", "jsxFragmentFactory": "MyFragment" } -
// input <>Hello</>; // output import { myjsx, MyFragment } from "react"; myjsx(MyFragment, null, "Hello");
{% /table %}
jsxImportSource
{% callout %}
Note — Only applicable when jsx is react-jsx or react-jsxdev.
{% /callout %}
The module from which the component factory function (createElement, jsx, jsxDEV, etc) will be imported. Default value is "react". This will typically be necessary when using a component library like Preact.
{% table %}
- Compiler options
- Transpiled output
-
{ "jsx": "react", // jsxImportSource is not defined // default to "react" } -
import { jsx } from "react/jsx-runtime"; jsx("Box", { width: 5, children: "Hello" });
-
{ "jsx": "react-jsx", "jsxImportSource": "preact", } -
import { jsx } from "preact/jsx-runtime"; jsx("Box", { width: 5, children: "Hello" });
-
{ "jsx": "react-jsxdev", "jsxImportSource": "preact", } -
// /jsx-runtime is automatically appended import { jsxDEV } from "preact/jsx-dev-runtime"; jsxDEV( "Box", { width: 5, children: "Hello" }, undefined, false, undefined, this, );
{% /table %}
jsxSideEffects
By default, Bun marks JSX expressions as /* @__PURE__ */ so they can be removed during bundling if they are unused (known as "dead code elimination" or "tree shaking"). Set jsxSideEffects to true to prevent this behavior.
{% table %}
- Compiler options
- Transpiled output
-
{ "jsx": "react", // jsxSideEffects is false by default } -
// JSX expressions are marked as pure /* @__PURE__ */ React.createElement("div", null, "Hello");
-
{ "jsx": "react", "jsxSideEffects": true, } -
// JSX expressions are not marked as pure React.createElement("div", null, "Hello");
-
{ "jsx": "react-jsx", "jsxSideEffects": true, } -
// Automatic runtime also respects jsxSideEffects jsx("div", { children: "Hello" });
{% /table %}
This option is also available as a CLI flag:
$ bun build --jsx-side-effects
JSX pragma
All of these values can be set on a per-file basis using pragmas. A pragma is a special comment that sets a compiler option in a particular file.
{% table %}
- Pragma
- Equivalent config
-
// @jsx h -
{ "jsxFactory": "h", }
-
// @jsxFrag MyFragment -
{ "jsxFragmentFactory": "MyFragment", }
-
// @jsxImportSource preact -
{ "jsxImportSource": "preact", }
{% /table %}
Logging
Bun implements special logging for JSX to make debugging easier. Given the following file:
import { Stack, UserCard } from "./components";
console.log(
<Stack>
<UserCard name="Dom" bio="Street racer and Corona lover" />
<UserCard name="Jakob" bio="Super spy and Dom's secret brother" />
</Stack>
);
Bun will pretty-print the component tree when logged:
{% image src="https://github.com/oven-sh/bun/assets/3084745/d29db51d-6837-44e2-b8be-84fc1b9e9d97" / %}
Prop punning
The Bun runtime also supports "prop punning" for JSX. This is a shorthand syntax useful for assigning a variable to a prop with the same name.
function Div(props: {className: string;}) {
const {className} = props;
// without punning
return <div className={className} />;
// with punning
return <div {className} />;
}