Skip to content

Bun.Transpiler: default autoImportJSX to true for the automatic runtime - #35557

Open
robobun wants to merge 15 commits into
mainfrom
farm/5470f44d/transpiler-auto-import-jsx-default
Open

Bun.Transpiler: default autoImportJSX to true for the automatic runtime#35557
robobun wants to merge 15 commits into
mainfrom
farm/5470f44d/transpiler-auto-import-jsx-default

Conversation

@robobun

@robobun robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Fixes #7499.

Bun.Transpiler with the automatic JSX runtime replaces <div/> with a call through a generated binding such as jsxDEV_7x81h0kn(...). That binding only exists when the matching import { jsxDEV as jsxDEV_... } from ".../jsx-dev-runtime" is emitted alongside it. The transpiler defaulted autoImportJSX to false, so the default output referenced a name that was never declared and threw at runtime:

ReferenceError: Can't find variable: jsxDEV

Repro

new Bun.Transpiler({ loader: "tsx" }).transformSync(
  "export default function App() { return <div>hello</div>; }"
);

Before:

export default function App() {
  return jsxDEV_7x81h0kn("div", { children: "hello" }, undefined, false, undefined, this);
}

After:

import { jsxDEV as jsxDEV_7x81h0kn } from "react/jsx-dev-runtime";
export default function App() {
  return jsxDEV_7x81h0kn("div", { children: "hello" }, undefined, false, undefined, this);
}

Why is this fix correct?

The whole point of the automatic JSX runtime is that the compiler inserts the import for you. Bun.build already defaults auto_import_jsx to true (see BundleOptions in src/bundler/options.rs), as do esbuild, tsc, swc, and Babel when targeting the automatic runtime. Without the import the output is dead on arrival: the generated identifier carries a collision-avoidance hash suffix, so there is no name a user could import manually to make it work.

The classic runtime (React.createElement) is unaffected. Users who want the raw output without the import can still pass autoImportJSX: false.

.scan() / .scanImports()

.scan() on JSX input now reports the react/jsx-runtime (or react/jsx-dev-runtime) dependency, since the emitted code actually imports it.

.scanImports() previously reported two require-call records (<src>/jsx-dev-runtime and the classic source) for any JSX regardless of the configured runtime or autoImportJSX. It now mirrors the full-parse gate: a single import-statement record for the runtime subpath, only under the automatic runtime with autoImportJSX enabled, so .scanImports() and .scan() agree on the injected import. The scan pass still has no per-symbol use counts, so the deprecated key-after-spread createElement fallback (which imports from the bare package instead of the runtime subpath) remains a known .scanImports() inaccuracy.

How did you verify your code works?

Added coverage in test/bundler/transpiler/transpiler.test.js for the default now emitting the import across the zero-arg constructor, .transformSync() in dev and production (react-jsx) mode, async .transform(), a custom jsxImportSource, and the createElement fallback after a key-after-spread. Also asserts the classic runtime is unchanged, autoImportJSX: false still suppresses the import, and .scan()/.scanImports() both surface the runtime import across the automatic-runtime variants while staying empty for the classic runtime, the opt-out, and sources with no JSX.

The three existing tests that asserted on the exact un-prefixed output now set autoImportJSX: false explicitly. Updated the sample output in docs/runtime/transpiler.mdx and added autoImportJSX to its reference section and to the TranspilerOptions JSDoc in bun.d.ts.


[review] gate passed · iteration 8 · 5 files touched

fails on main (without fix)
ASAN without fix: 17 failed, 22 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/transpiler/transpiler.test.js
bun test v1.4.0 (2f2125e73)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing macros [4.47ms]
(pass) Bun.Transpiler > normalizes \r\n [5.66ms]
1
(pass) Bun.Transpiler > doesn't hang indefinitely #2746 [3.73ms]
(pass) Bun.Transpiler > property access inlining > bails out with spread [6.76ms]
(pass) Bun.Transpiler > property access inlining > bails out with multiple items [2.49ms]
(pass) Bun.Transpiler > property access inlining > works [2.09ms]
(pass) Bun.Transpiler > property access inlining > works nested [2.52ms]
(pass) Bun.Transpiler > property access inlining > bails out when the array item is an optional chain [42.93ms]
(pass) Bun.Transpiler > property access inlining > bails out or strips `this` when the index is a call/assignment target [20.19ms]
(pass) Bun.Transpiler > property access inlining > preserves runtime semantics when inlining from a literal index [300.66ms]
(pass) Bun.Transpiler > property access inlining > bails out on optional
... (truncated)

release without fix: 18 failed, 22 skipped
bun test v1.4.0-canary.1 (0ffabf64d)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing macros [0.09ms]
(pass) Bun.Transpiler > normalizes \r\n [0.11ms]
1
(pass) Bun.Transpiler > doesn't hang indefinitely #2746 [0.08ms]
(pass) Bun.Transpiler > property access inlining > bails out with spread [0.13ms]
(pass) Bun.Transpiler > property access inlining > bails out with multiple items [0.04ms]
(pass) Bun.Transpiler > property access inlining > works [0.03ms]
(pass) Bun.Transpiler > property access inlining > works nested [0.03ms]
(pass) Bun.Transpiler > property access inlining > bails out when the array item is an optional chain [0.66ms]
(pass) Bun.Transpiler > property access inlining > bails out or strips `this` when the index is a call/assignment target [0.31ms]
(pass) Bun.Transpiler > property access inlining > preserves runtime semantics when inlining from a literal index [7.54ms]
(pass) Bun.Transpiler > property access inlining > bails out on optional-chain index into enum [0.33ms]
(pass) Bun.Transpiler > TypeScript > import Foo = Baz.Bar [0.05ms]
(pass) Bun.Transpiler > TypeScript > ternary should parse correctly when
... (truncated)
passes on PR (with fix)
ASAN with fix: 22 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/transpiler/transpiler.test.js
bun test v1.4.0 (2f2125e73)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing macros [4.52ms]
(pass) Bun.Transpiler > normalizes \r\n [5.80ms]
1
(pass) Bun.Transpiler > doesn't hang indefinitely #2746 [3.75ms]
(pass) Bun.Transpiler > property access inlining > bails out with spread [6.94ms]
(pass) Bun.Transpiler > property access inlining > bails out with multiple items [2.51ms]
(pass) Bun.Transpiler > property access inlining > works [2.23ms]
(pass) Bun.Transpiler > property access inlining > works nested [2.52ms]
(pass) Bun.Transpiler > property access inlining > bails out when the array item is an optional chain [43.88ms]
(pass) Bun.Transpiler > property access inlining > bails out or strips `this` when the index is a call/assignment target [20.51ms]
(pass) Bun.Transpiler > property access inlining > preserves runtime semantics when inlining from a literal index [302.88ms]
(pass) Bun.Transpiler > property access inlining > bails out on optional
... (truncated)

release with fix: 22 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     2f2125e73a
  features     baseline

22 deps, 105 codegen, 1175 objects in 738ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1237] install /workspace/bun
bun install v1.4.0-canary.1 (0ffabf64d)

Checked 107 installs across 153 packages (no changes) [9.00ms]
[2/1237] gen ErrorCode+*.h
[3/1237] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (0ffabf64d)

Checked 1 install across 2 packages (no changes) [1.00ms]
[4/1237] gen bindgenv2
[5/1237] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (0ffabf64d)

Checked 129 installs across 147 packages (no changes) [6.00ms]
[6/1237] fetch picohttpparser
[picohttpparser] up to date
[7/1237] fetch tinycc
[tinycc] up to date
[8/1236] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[9/1236] fetch zlib
[zlib] up to date
[10/1236] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingConstants.lut.h from /workspace/bun/src/jsc/bindings/ProcessBindingConst
... (truncated)
diff hotspot
docs/runtime/transpiler.mdx                |   8 ++
 packages/bun-types/bun.d.ts                |   9 ++
 src/js_parser/parse/parse_entry.rs         |  35 +++++---
 src/runtime/api/JSTranspiler.rs            |   2 +
 test/bundler/transpiler/transpiler.test.js | 137 +++++++++++++++++++++++++++++
 5 files changed, 177 insertions(+), 14 deletions(-)

gate history · 5 passed · 2 rejected · iteration 8

evidence per changed file
file                                        reads  edits  tests
docs/runtime/transpiler.mdx                     3      2      0
packages/bun-types/bun.d.ts                     2      3      0
src/js_parser/parse/parse_entry.rs              7      9      0
src/runtime/api/JSTranspiler.rs                 3      2      0
test/bundler/transpiler/transpiler.test.js      9     15      0

@robobun
robobun requested a review from alii as a code owner July 25, 2026 05:03
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Bun.Transpiler now enables automatic JSX runtime imports by default. JSX pragmas select the runtime and development mode. Documentation, option wiring, and transform and scan tests cover the behavior.

Automatic JSX Runtime Imports

Layer / File(s) Summary
Option documentation and default wiring
docs/runtime/transpiler.mdx, packages/bun-types/bun.d.ts, src/runtime/api/JSTranspiler.rs
Documents autoImportJSX, updates example output, and enables the option by default.
JSX runtime import resolution
src/js_parser/parse/parse_entry.rs, src/runtime/api/JSTranspiler.rs
Resolves JSX runtime pragmas and injects the appropriate automatic-runtime import when enabled.
Automatic runtime import coverage
test/bundler/transpiler/transpiler.test.js
Tests runtime variants, import sources, asynchronous transforms, scanning, disabling, classic JSX behavior, and existing output assertions.

Possibly related issues

  • Issue 7499 — Tracks missing automatic JSX runtime imports that cause unresolved jsxDEV.

Possibly related PRs

  • oven-sh/bun#35582 — Directly extends automatic JSX import defaults, parser synchronization, documentation, and test coverage.
  • oven-sh/bun#36261 — Modifies automatic JSX runtime configuration, import handling, and related tests.
  • oven-sh/bun#36269 — Relates to JSX runtime configuration and development/production runtime selection.

Suggested reviewers: alii, jarred-sumner, dylan-conway

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #7499 by importing the appropriate JSX runtime for automatic JSX transformations and preventing undeclared JSX bindings.
Out of Scope Changes check ✅ Passed All changes support the linked issue through implementation, tests, API documentation, and scan behavior updates.
Title check ✅ Passed The title clearly summarizes the main change: enabling automatic JSX runtime imports by default in Bun.Transpiler.
Description check ✅ Passed The description includes both required sections and provides clear details about the fix, verification, behavior, tests, and documentation changes.

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Reproduced with new Bun.Transpiler({ loader: "tsx" }).transformSync("export default <div/>") emitting jsxDEV_7x81h0kn(...) with no import for it.

All review feedback addressed. Coverage spans the zero-arg constructor, .transformSync dev/prod, async .transform, jsxImportSource, key-after-spread, classic runtime, the autoImportJSX: false opt-out, .scan(), and .scanImports() including // @jsxRuntime pragma overrides in both directions. Docs and JSDoc updated.

CI: test/bundler/transpiler/transpiler.test.js passes on every lane. The only hard failure in build 83670 is test/js/bun/http/serve.test.ts (request-body backpressure EPIPE on macOS), now tagged [pre-existing] against main and reported separately; it does not touch any code this PR changes. Ready for maintainer review.

@github-actions

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. Bun.transpiler JSX empty result #14789 - Bun.Transpiler returns empty output for JSX because autoImportJSX defaults to false, so the jsxDEV call is emitted without its import and the expression is eliminated
  2. Bun.Transpiler using { jsxImportSource: "solid-js" } outputs React code #3528 - Bun.Transpiler ignores jsxImportSource: "solid-js" and falls back to React.createElement because autoImportJSX is false, preventing the automatic runtime path

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #14789
Fixes #3528

🤖 Generated with Claude Code

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond the inline findings, I checked the other Bun.Transpiler JSX assertions in transpiler.test.js for breakage from the new import prefix — the remaining ones either use .toContain (lines 2168-2177, 2295-2297), have no JSX elements in the input (2291-2294), or are under it.todo (2300-2402), so the three autoImportJSX: false additions are sufficient. react-compiler.test.ts uses loader: "js" and is unaffected.

Extended reasoning...

Checked whether the three existing tests that gained autoImportJSX: false were the complete set of exact-output JSX assertions that would break under the new default. Grepped test/ for jsxDEV_7x81h0kn and new Bun.Transpiler with JSX loaders; the remaining hits in transpiler.test.js either assert via .toContain (import prefix doesn't matter), transpile inputs without JSX elements (no auto-import emitted), or sit under it.todo. react-compiler.test.ts re-parses bundler output with loader: "js", so JSX auto-import doesn't apply. No missed test-suite breakage found.

Comment thread src/runtime/api/JSTranspiler.rs Outdated
Comment thread src/runtime/api/JSTranspiler.rs Outdated
Comment thread test/bundler/transpiler/transpiler.test.js Outdated
@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 9:19 PM PT - Aug 6th, 2026

@robobun, your commit 2f2125e73a65cebef62c32c32acd3d114ac67e09 passed in Build #89838! 🎉


🧪   To try this PR locally:

bunx bun-pr 35557

That installs a local version of the PR into your bun-35557 executable, so you can run:

bun-35557 --bun

Comment thread src/runtime/api/JSTranspiler.rs Outdated
Comment thread src/runtime/api/JSTranspiler.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All four items from my earlier passes are addressed — the .transform()/.scan() coverage, the mdx output block, the regression-test comment, and the multi-line comment on auto_import_jsx: true are all fixed in dbfb1e8. I'm not auto-approving only because this flips a documented public-API default (and deliberately changes .scan() output on JSX to include the synthetic runtime import), which is the kind of user-visible behavior shift a maintainer should sign off on.

What was reviewed:

  • The one-line Config::default() change and its flow into transpiler.options.auto_import_jsx via the constructor — no other codepath reads config.runtime.auto_import_jsx.
  • Test coverage now spans no-arg/dev/prod/jsxImportSource/key-after-spread createElement/classic-runtime/opt-out/.scan()/async .transform(); the three pre-existing tests that asserted the raw output correctly opt out with autoImportJSX: false rather than being weakened.
  • .scanImports() goes through the lexer-only ScanPassResult path (not Transpiler::parse), so it does not pick up the synthetic import — checked and that's unchanged.
Extended reasoning...

Overview

One-line runtime change in src/runtime/api/JSTranspiler.rs (Config::default() now sets runtime.auto_import_jsx = true), plus a JSDoc addition in bun.d.ts, an mdx doc update, and ~85 lines of new tests in transpiler.test.js. Fixes #7499 where Bun.Transpiler under the automatic JSX runtime emitted jsxDEV_7x81h0kn(...) with no import for it, yielding unrunnable output.

Security risks

None. No untrusted input handling, no auth/crypto, no memory-safety changes. The Rust diff is a single boolean field initializer inside a Default impl.

Level of scrutiny

Medium. The mechanical change is trivial and clearly correct — the previous default produced code that referenced an undeclared identifier, and Bun.build, esbuild, tsc, swc, and Babel all default this on for the automatic runtime. But it is a public API default flip: three existing tests had to add autoImportJSX: false to keep passing, and .scan() on JSX now returns [{path:"react/jsx-dev-runtime", kind:"import-statement"}] where it used to return []. That .scan() change was made deliberate and tested per my earlier feedback, but whether the synthetic import should surface there (vs. being filtered like IS_INTERNAL records) is a product call a maintainer should confirm.

Other factors

All four points from my prior reviews (2026-07-25T05:18 and 07:01) are addressed as of dbfb1e8: async .transform() and .scan() coverage added, mdx example output now shows the import line, the regression-test comment is trimmed to just the issue URL, and the multi-line comment above auto_import_jsx: true is dropped (comment-cop is satisfied). I also verified .scanImports() is unaffected — it uses the lexer-only bun_bundler::cache::JavaScript::scan() path against scan_pass_result, not Transpiler::parse(), so auto_import_jsx never reaches it. The no-arg-constructor test added in dbfb1e8 closes the last gap I'd have asked about.

Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
Comment thread src/js/internal/inspector/cdp.ts
robobun and others added 7 commits July 27, 2026 21:14
scanImports() on JSX unconditionally reported two require-call
records (jsx-dev-runtime plus the classic source) regardless of the
configured JSX runtime or the autoImportJSX flag, diverging from
scan() which reports the single import-statement the transform
actually emits.

_scan_imports now uses the same gate as the full-parse auto-import
path (jsx.parse && auto_import_jsx && runtime == Automatic), emits
ImportKind::Stmt, and drops the phantom classic record. The
JSTranspiler scanImports entry point now plumbs auto_import_jsx into
the parser options so the gate has the user's value.
@robobun
robobun force-pushed the farm/5470f44d/transpiler-auto-import-jsx-default branch from eb79cf4 to c3c0717 Compare July 27, 2026 21:25
Comment thread src/js_parser/parse/parse_entry.rs Outdated
Comment thread src/js_parser/parse/parse_entry.rs Outdated
Comment thread test/bundler/transpiler/transpiler.test.js Outdated
Comment thread src/js_parser/parse/parse_entry.rs
Comment thread src/js_parser/parse/parse_entry.rs
Comment thread src/js_parser/parse/parse_entry.rs Outdated
Comment thread test/bundler/transpiler/transpiler.test.js
Comment thread src/js_parser/parse/parse_entry.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bun-types/bun.d.ts`:
- Around line 2466-2469: Update the automatic JSX runtime documentation near the
JSX transform option to show that jsxDEV is imported from
"<jsxImportSource>/jsx-dev-runtime", including the configured import source such
as preact/jsx-dev-runtime, rather than describing it as a bare jsx-dev-runtime
path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4ea9a67a-5bbc-44dd-b491-5b01baa923d0

📥 Commits

Reviewing files that changed from the base of the PR and between e232917 and a1f3775.

📒 Files selected for processing (5)
  • docs/runtime/transpiler.mdx
  • packages/bun-types/bun.d.ts
  • src/js_parser/parse/parse_entry.rs
  • src/runtime/api/JSTranspiler.rs
  • test/bundler/transpiler/transpiler.test.js

Comment thread packages/bun-types/bun.d.ts

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior feedback has been addressed and no new issues turned up this round. Deferring to a human for the final call since this flips a user-facing Bun.Transpiler default and changes what .scanImports() reports for JSX inputs.

What was reviewed

  • The auto_import_jsx: true default in JSTranspiler.rs and its propagation into the scan_imports parser opts.
  • The _scan_imports gate rewrite in parse_entry.rs: pragma application now mirrors prepare_for_visit_pass (both runtime and development), the comment sits on the right block, and the record kind is Stmt.
  • Test matrix pins NODE_ENV via define on every row that asserts a dev/prod runtime path; the key-after-spread .scanImports() divergence is documented in the PR body as an accepted inaccuracy.
Extended reasoning...

Overview

The PR flips Bun.Transpiler's autoImportJSX default from false to true so that automatic-runtime JSX output includes the react/jsx-runtime (or dev) import it depends on — fixing #7499 where the default output referenced an undeclared jsxDEV_... binding. It touches five files: the Config::default() in JSTranspiler.rs (+1 line to propagate the flag into scan_imports's parser opts), the _scan_imports JSX gate in parse_entry.rs (rewritten to mirror the full-parse gate: honors auto_import_jsx, checks runtime == Automatic, applies the @jsxRuntime pragma, and emits a single ImportKind::Stmt record instead of two Require records), ~130 lines of new tests in transpiler.test.js, and doc/type updates.

Security risks

None. No untrusted-input parsing changes, no auth/crypto/permissions surface. The parser change only refines which import records are synthesized for JSX in the scan pass.

Level of scrutiny

Medium-high. The core fix is a two-line default flip that is clearly correct — the previous default produced code that throws ReferenceError at runtime, and Bun.build/esbuild/tsc/swc/Babel all default this on. But it is still a user-facing behavior change: (a) .transformSync()/.transform() output now includes an import statement it didn't before, and (b) .scanImports() on JSX input now returns one import-statement record for the runtime subpath instead of two require-call records regardless of config. Any tooling that consumed either output verbatim will observe the change. Per the repo's "API design" guidance for changing user-facing surface, a maintainer should sign off.

Other factors

This PR went through six review iterations. Earlier rounds surfaced and fixed: NODE_ENV-dependent test flakiness (now pinned via define), the @jsxRuntime pragma not being consulted in scan mode (now applied, including the development half of RUNTIME_MAP), a misplaced code comment, and bug-history narration in test comments. The one consciously-accepted gap — .scanImports() reports the runtime subpath rather than the bare package for the deprecated key-after-spread createElement fallback — is documented in the PR description with the rationale (merge_jsx doesn't propagate package_name; scanImports is spec'd as marginally less accurate). Three pre-existing tests that asserted on the un-prefixed output now pass autoImportJSX: false explicitly. The evidence block shows the new tests fail on main and pass on the PR under both ASAN-debug and release.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/runtime/transpiler.mdx`:
- Around line 220-223: Update the autoImportJSX documentation to cover all
automatic runtime import forms: production imports such as jsx from the
configured jsxImportSource’s runtime module, development imports of jsxDEV from
its dev-runtime module, and how custom jsxImportSource changes those paths.

In `@src/js_parser/parse/parse_entry.rs`:
- Around line 488-510: Update the JSX handling in _scan_imports to apply the
`@jsxImportSource` pragma via jsx_import_source() before constructing the
automatic runtime import record, matching prepare_for_visit_pass behavior.
Ensure both .scan() and .scanImports() resolve the pragma source instead of
defaulting to react/jsx-runtime, and add regression coverage for each API.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8d98203a-a69b-4d71-8b7a-d71542ff75b2

📥 Commits

Reviewing files that changed from the base of the PR and between eba6c07 and 57c7ff1.

📒 Files selected for processing (5)
  • docs/runtime/transpiler.mdx
  • packages/bun-types/bun.d.ts
  • src/js_parser/parse/parse_entry.rs
  • src/runtime/api/JSTranspiler.rs
  • test/bundler/transpiler/transpiler.test.js

Comment thread docs/runtime/transpiler.mdx Outdated
Comment thread src/js_parser/parse/parse_entry.rs

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional findings (outside current diff — PR may have been updated during review):

  • 🔴 test/bundler/transpiler/transpiler.test.js:2594 — The merge from main (57c7ff1) pulled in a fourth exact-match JSX test — "JSX tag names containing '-' or ':' are string tags regardless of case" at transpiler.test.js:2614-2635 — that constructs new Bun.Transpiler({ loader: "jsx", define: {...} }) without autoImportJSX: false and does six .toBe() assertions expecting un-prefixed export var foo = jsxDEV_7x81h0kn(...). With this PR's default flip, each output is now prefixed with import { jsxDEV as ... } from "react/jsx-dev-runtime";\n and every assertion fails. Add autoImportJSX: false to the options at line 2621, matching the three sibling fixes already in this diff.

    Extended reasoning...

    What the issue is

    This PR flips Bun.Transpiler's autoImportJSX default from false to true (src/runtime/api/JSTranspiler.rs:99). To keep the existing suite passing, the diff adds autoImportJSX: false to the three pre-existing tests that assert exact un-prefixed JSX output via .toBe() — at lines 2207, 2452, and 2594 of test/bundler/transpiler/transpiler.test.js. But a fourth such test now exists at lines 2614–2635, and it does not have autoImportJSX: false.

    How it got here

    The test "JSX tag names containing '-' or ':' are string tags regardless of case" was added on main in commit 2c5c312 (PR #37052, 2026-08-06) and pulled into this branch by merge commit 57c7ff1 — the most recent commit on the branch. This PR's evidence block shows the last gate run (iteration 6) was against a1f37750e, which predates the merge. So the gate never exercised this test with the flipped default, and the three-site sweep in this diff was complete against the pre-merge tree but is now one short.

    The code path

    The test (transpiler.test.js:2617-2622) constructs:

    const bun = new Bun.Transpiler({
      loader: "jsx",
      define: {
        "process.env.NODE_ENV": JSON.stringify("development"),
      },
    });

    With no explicit autoImportJSX, Config::default() now sets auto_import_jsx: true (JSTranspiler.rs:99), which propagates to transpiler.options.auto_import_jsx and then into the parser's options.features.auto_import_jsx. The full-parse auto-import gate in parse_entry.rs (p.options.jsx.parse && p.options.features.auto_import_jsx && p.options.jsx.runtime == Automatic) passes, so generate_import_stmt("react/jsx-dev-runtime", ["jsxDEV"], ...) prepends the import to the output.

    Step-by-step proof

    Take the first loop iteration, tag = "Foo-Bar", expected = '"Foo-Bar"':

    1. Input: export var foo = <Foo-Bar />.
    2. transformSync runs the automatic JSX transform in dev mode → the JSX becomes jsxDEV_7x81h0kn("Foo-Bar", {}, undefined, false, undefined, this), and jsxDEV is recorded as used.
    3. With auto_import_jsx = true (the new default), the post-visit pass emits import { jsxDEV as jsxDEV_7x81h0kn } from "react/jsx-dev-runtime"; as the first statement.
    4. Actual output:
      import { jsxDEV as jsxDEV_7x81h0kn } from "react/jsx-dev-runtime";
      export var foo = jsxDEV_7x81h0kn("Foo-Bar", {}, undefined, false, undefined, this);
      
    5. The assertion at line 2631 is:
      expect(bun.transformSync(`export var foo = <${tag} />`)).toBe(
        `export var foo = jsxDEV_7x81h0kn(${expected}, {}, undefined, false, undefined, this);\n`,
      );
      The expected string has no import prefix, so .toBe() fails. The same happens for all six [tag, expected] pairs.

    Why existing code doesn't prevent it

    The three autoImportJSX: false additions in this diff (2207, 2452, 2594) each patch a specific transpiler-instance construction site; there is no shared helper. The new test at 2617 constructs its own Bun.Transpiler locally, so nothing in this PR's diff touches it. Per REVIEW.md: "When changing output/defaults/messages, grep the suite for assertions on the old behavior and update them in the same PR" — the merge introduced a new site matching the old-behavior assertion pattern after that grep was done.

    Impact

    Concrete CI failure: bun bd test test/bundler/transpiler/transpiler.test.js will report 6 failed assertions in this test, blocking merge.

    Fix

    Add autoImportJSX: false to the transpiler options at line 2621, exactly matching the three sibling fixes already in this diff:

    const bun = new Bun.Transpiler({
      loader: "jsx",
      define: {
        "process.env.NODE_ENV": JSON.stringify("development"),
      },
      autoImportJSX: false,
    });

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues found — all eight rounds of prior feedback are addressed and resolved. Deferring to a maintainer because this flips a documented Bun.Transpiler default (autoImportJSX false→true) and reshapes .scan()/.scanImports() output for JSX input, which is a user-visible API behavior change worth a human sign-off even though the old default produced unrunnable output.

What was reviewed

  • _scan_imports pragma handling (2f2125e) mirrors prepare_for_visit_pass at p.rs:2781-2795 field-for-field, including development and package_name.
  • Confirmed _scan_imports is only reachable via Bun.Transpiler.scanImports() (bundler proper uses the full-parse path), so the import-record shape change is scoped to that one API.
  • New test matrix pins NODE_ENV via define/tsconfig on every row that asserts a specific dev/prod runtime path — the ambient-env fragility from earlier rounds is not reintroduced.
Extended reasoning...

Overview

Flips the Bun.Transpiler default for autoImportJSX from false to true, so the automatic JSX runtime emits the import { jsx… } from '<src>/jsx-runtime' statement alongside the transformed calls (fixes #7499 — the previous default produced output referencing an undeclared hashed identifier). Also rewrites the JSX-import block in _scan_imports (parse_entry.rs) to mirror prepare_for_visit_pass: it now applies @jsxImportSource and @jsxRuntime pragmas, gates on auto_import_jsx && runtime == Automatic, and emits a single ImportKind::Stmt record instead of two Require records. JSTranspiler.rs wires the config default and threads auto_import_jsx into the scanImports parser opts. Docs (transpiler.mdx) and bun.d.ts JSDoc updated; ~140 lines of tests added.

Security risks

None. No untrusted-input parsing changes, no allocation-size arithmetic, no FFI/lifetime changes. The arena-copy of import_source in _scan_imports was already there; only the gate and record kind changed.

Level of scrutiny

Medium-high. The core fix (default flip in Config::default() + one line in scan_imports) is trivial, and the _scan_imports rewrite is a faithful subset of the reference in prepare_for_visit_pass. But this is a user-visible behavioral change to three documented APIs (transformSync/transform output now prepends an import; .scan().imports and .scanImports() now report the synthetic JSX runtime import where they previously did not / reported different records). The rationale is strong — Bun.build, esbuild, tsc, swc, and Babel all default this on, and the old output was dead-on-arrival — but flipping a documented default is a product decision a maintainer should confirm.

Other factors

  • This PR has been through eight review iterations; every prior inline finding from me (variant-matrix coverage, .scan() behavior, @jsxRuntime pragma in scan mode, RUNTIME_MAP.development field, ambient-NODE_ENV test fragility, comment placement) and from CodeRabbit (@jsxImportSource pragma in scan mode, doc wording) is marked resolved and verified against the current diff.
  • Test coverage is thorough: zero-arg constructor, dev/prod, sync/async, custom jsxImportSource, key-after-spread createElement fallback, classic-runtime no-op, explicit opt-out, and a 10-row .scanImports().scan() agreement matrix including three pragma-override cases and the @jsxImportSource pragma.
  • Three pre-existing tests that asserted exact un-prefixed output were updated to pass autoImportJSX: false rather than having their assertions weakened.
  • Blast radius of the _scan_imports rewrite is limited: the only caller of Parser::scan_imports is bun_bundler::cache::JavaScript::scan, and that is only invoked from JSTranspiler::scan_imports — the bundler's own parse path is unaffected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto import jsx-runtime to fix: Can't find variable: jsxDEV

2 participants