Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2462,6 +2462,15 @@ declare module "bun" {
*/
macro?: MacroMap;

/**
* When the automatic JSX runtime is active, prepend the
* `import { jsx, ... } from "<jsxImportSource>/jsx-runtime"` statement that
* binds the generated `jsx`/`jsxs`/`jsxDEV`/`Fragment` calls.
*
* Has no effect when the classic runtime (`jsx: "react"`) is in use.
*
* @default true
Comment on lines +2465 to +2472

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the development runtime import path.

Automatic development transforms import <jsxImportSource>/jsx-dev-runtime—for example, the test expects react/jsx-dev-runtime on Line 2166—while this documentation only names /jsx-runtime despite mentioning jsxDEV.

Proposed documentation fix
-     * `import { jsx, ... } from "<jsxImportSource>/jsx-runtime"` statement that
+     * `import { jsx, ... } from "<jsxImportSource>/jsx-runtime"` (or
+     * `"<jsxImportSource>/jsx-dev-runtime"` in development) that
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* When the automatic JSX runtime is active, prepend the
* `import { jsx, ... } from "<jsxImportSource>/jsx-runtime"` statement that
* binds the generated `jsx`/`jsxs`/`jsxDEV`/`Fragment` calls.
*
* Has no effect when the classic runtime (`jsx: "react"`) is in use.
*
* @default true
/**
* When the automatic JSX runtime is active, prepend the
* `import { jsx, ... } from "<jsxImportSource>/jsx-runtime"` (or
* `"<jsxImportSource>/jsx-dev-runtime"` in development) statement that
* binds the generated `jsx`/`jsxs`/`jsxDEV`/`Fragment` calls.
*
* Has no effect when the classic runtime (`jsx: "react"`) is in use.
*
* `@default` true
🤖 Prompt for 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.

In `@packages/bun-types/bun.d.ts` around lines 2465 - 2472, Update the
documentation comment for the automatic JSX runtime option to mention that
development transforms import from <jsxImportSource>/jsx-dev-runtime, using
react/jsx-dev-runtime as an example, while retaining the existing /jsx-runtime
path for other automatic transforms.

*/
autoImportJSX?: boolean;
allowBunRuntime?: boolean;
exports?: {
Expand Down
1 change: 1 addition & 0 deletions src/runtime/api/JSTranspiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
log: bun_ast::Log::default(), // overwritten at construction
runtime: Runtime::Features {
top_level_await: true,
auto_import_jsx: true,

Check warning on line 100 in src/runtime/api/JSTranspiler.rs

View check run for this annotation

Claude / Claude Code Review

Stale docs: transpiler.mdx transformSync example still shows import-less output

The `.transformSync()` example output in `docs/runtime/transpiler.mdx` (lines 36-43) still shows the bare `jsxDEV_7x81h0kn(...)` call with no defining import — i.e. the pre-PR output. Now that `autoImportJSX` defaults to true, the real output for that exact snippet begins with `import { jsxDEV as jsxDEV_7x81h0kn } from "react/jsx-dev-runtime";`. Worth refreshing the doc block in this PR since it's the one place in the docs that shows what that mangled identifier is, and leaving it undefined-look

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.

🟡 The .transformSync() example output in docs/runtime/transpiler.mdx (lines 36-43) still shows the bare jsxDEV_7x81h0kn(...) call with no defining import — i.e. the pre-PR output. Now that autoImportJSX defaults to true, the real output for that exact snippet begins with import { jsxDEV as jsxDEV_7x81h0kn } from "react/jsx-dev-runtime";. Worth refreshing the doc block in this PR since it's the one place in the docs that shows what that mangled identifier is, and leaving it undefined-looking perpetuates the #7499 confusion.

Extended reasoning...

What's stale

docs/runtime/transpiler.mdx documents .transformSync() with this exact call shape:

const transpiler = new Bun.Transpiler({ loader: 'tsx' });
const code = `
import * as whatever from "./whatever.ts"
export function Home(props: {title: string}){
  return <p>{props.title}</p>;
}`;
const result = transpiler.transformSync(code);

and shows the output (lines 36-43) as:

import * as whatever from "./whatever.ts";
export function Home(props) {
  return jsxDEV_7x81h0kn("p", {
    children: props.title
  }, undefined, false, undefined, this);
}

That output block references jsxDEV_7x81h0kn with nothing defining it — which is precisely the "where does this identifier come from?" confusion that #7499 reports and this PR fixes.

Why it drifts after this PR

This PR flips Config::default().runtime.auto_import_jsx to true in src/runtime/api/JSTranspiler.rs:100. With no explicit autoImportJSX in the options, new Bun.Transpiler({ loader: 'tsx' }).transformSync(code) on JSX now prepends the runtime import. Running the doc's snippet against a build with this change produces:

import { jsxDEV as jsxDEV_7x81h0kn } from "react/jsx-dev-runtime";
import * as whatever from "./whatever.ts";
export function Home(props) {
  return jsxDEV_7x81h0kn("p", {
    children: props.title
  }, undefined, false, undefined, this);
}

The documented output no longer matches what Bun emits.

Step-by-step

  1. User reads docs/runtime/transpiler.mdx and copies the transformSync example verbatim.
  2. Config::default() now sets auto_import_jsx: true (JSTranspiler.rs:100); no autoImportJSX option is passed, so it stays true.
  3. get_parse_resulttranspiler.parse runs with auto_import_jsx = true, injecting the react/jsx-dev-runtime import statement.
  4. The printed output has an extra leading import { jsxDEV as jsxDEV_7x81h0kn } from "react/jsx-dev-runtime"; line that the doc's output block doesn't show.
  5. A user diffing their result against the doc sees a mismatch; a user reading only the doc still sees an apparently-undefined jsxDEV_7x81h0kn — the exact symptom this PR eliminates in the runtime.

Why nothing already covers this

The PR updates packages/bun-types/bun.d.ts to document the new default and updates the three existing tests in transpiler.test.js that asserted on import-free output by adding autoImportJSX: false. But docs/runtime/transpiler.mdx is not touched, and the doc example uses the default (no autoImportJSX key), so it now documents stale behavior. 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 .mdx output block is effectively one such assertion.

Fix

Add the auto-injected import as the first line of the output block in docs/runtime/transpiler.mdx:

 ```ts output
+import { jsxDEV as jsxDEV_7x81h0kn } from "react/jsx-dev-runtime";
 import * as whatever from "./whatever.ts";
 export function Home(props) {

Severity

Nit — doc drift only, no runtime impact. But it's worth doing in this PR because (a) the doc snippet is the exact call shape whose default this PR changes, and (b) the stale block is the only place in the docs that shows the mangled JSX identifier, so leaving it without the defining import keeps the very confusion #7499 filed alive in the documentation.

..Default::default()
},
tree_shaking: false,
Expand Down
37 changes: 37 additions & 0 deletions test/bundler/transpiler/transpiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,7 @@ export default <>hi</>
"process.env.NODE_ENV": JSON.stringify("development"),
},
logLevel: "error",
autoImportJSX: false,
});

expect(bun.transformSync("console.log(<div key={() => {}} points={() => {}}></div>);")).toBe(
Expand Down Expand Up @@ -2154,6 +2155,40 @@ console.log(<div {...obj} key="after" />);`),
);
});

// https://github.com/oven-sh/bun/issues/7499
// The automatic JSX runtime emits calls to generated symbols (jsxDEV_7x81h0kn
// etc.). Bun.Transpiler previously defaulted autoImportJSX to false, so those
// calls were left undefined in the output.
Comment on lines +2158 to +2161

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep regression-test comments to the issue URL only.

The repository guideline requires regression tests to contain exactly the issue URL comment. Keep the URL at Line 2158 and remove the explanatory comments in this test.

Proposed cleanup
 // https://github.com/oven-sh/bun/issues/7499
-// The automatic JSX runtime emits calls to generated symbols ...
-// ...
-// Every generated JSX symbol that is called/used must be bound by the import.
-// tsconfig jsxImportSource is honored by the default-on auto-import.
-// The classic runtime has no auto-import and must stay import-free.
-// autoImportJSX: false opts out of the import.

Also applies to: 2167-2167, 2173-2173, 2180-2180, 2187-2187

🤖 Prompt for 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.

In `@test/bundler/transpiler/transpiler.test.js` around lines 2158 - 2161, In the
regression test around the issue 7499 URL, remove the explanatory comments
describing automatic JSX runtime behavior and retain only the issue URL comment.
Apply the same cleanup to the corresponding repeated regression-test comment
locations, preserving the test code unchanged.

Source: Coding guidelines

it("JSX automatic runtime import is emitted by default", () => {
for (const loader of ["jsx", "tsx"]) {
const out = new Bun.Transpiler({ loader }).transformSync("export default <><div /></>");
expect(out).toStartWith("import {");
expect(out).toContain('from "react/jsx-dev-runtime"');
// Every generated JSX symbol that is called/used must be bound by the import.
for (const [name] of out.matchAll(/\b(jsxDEV|jsx|jsxs|Fragment|createElement)_\w+\b/g)) {
expect(out).toContain(` as ${name}`);
}
}

// tsconfig jsxImportSource is honored by the default-on auto-import.
const preact = new Bun.Transpiler({
loader: "tsx",
tsconfig: { compilerOptions: { jsx: "react-jsx", jsxImportSource: "preact" } },
}).transformSync("export default <div />");
expect(preact).toContain('from "preact/jsx-runtime"');

// The classic runtime has no auto-import and must stay import-free.
const classic = new Bun.Transpiler({
loader: "tsx",
tsconfig: { compilerOptions: { jsx: "react" } },
}).transformSync("export default <div />");
expect(classic).not.toContain("import ");

// autoImportJSX: false opts out of the import.
const optOut = new Bun.Transpiler({ loader: "tsx", autoImportJSX: false }).transformSync("export default <div />");
expect(optOut).not.toContain("import ");
Comment on lines +2173 to +2189

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover both jsx and tsx for every changed behavior.

The default-import assertion iterates over both loaders, but the jsxImportSource, classic-runtime, and autoImportJSX: false cases only exercise tsx. A regression in the corresponding jsx path could therefore pass unnoticed. Parameterize these cases across both loaders, preferably using the repository’s matrix-test convention.

🤖 Prompt for 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.

In `@test/bundler/transpiler/transpiler.test.js` around lines 2173 - 2189, Update
the JSX runtime tests around the preact, classic, and autoImportJSX opt-out
cases to run for both “jsx” and “tsx” loaders, following the repository’s
existing matrix-test convention. Preserve each case’s current assertion while
parameterizing only the loader so both transformation paths are covered.

Source: Coding guidelines

});

// Non-bundle transpile without `minify.identifiers` uses NoOpRenamer
// (prints symbol.original_name verbatim), so the `generatedSymbolName`
// hash suffix on the automatic JSX runtime import is the sole collision
Expand Down Expand Up @@ -2203,6 +2238,7 @@ console.log(<div {...obj} key="after" />);`),
loader: "jsx",
define: { "process.env.NODE_ENV": JSON.stringify("development") },
logLevel: "error",
autoImportJSX: false,
});
process.stdout.write(t.transformSync('console.log(<div key key="duplicate"></div>);'));
process.stdout.write(t.transformSync('console.log(<div key className="x" key="duplicate"></div>);'));
Expand Down Expand Up @@ -2344,6 +2380,7 @@ console.log(<div {...obj} key="after" />);`),
define: {
"process.env.NODE_ENV": JSON.stringify("development"),
},
autoImportJSX: false,
});
expect(bun.transformSync("export var foo = <div>{...a}b</div>")).toBe(
`export var foo = jsxDEV_7x81h0kn("div", {
Expand Down
Loading