From 18b6960f4742cfeec3724197cf63645d23047720 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 25 Jul 2026 07:41:31 +0000 Subject: [PATCH] Bun.Transpiler: default autoImportJSX to true for the automatic JSX runtime With the automatic JSX runtime (the default), Bun.Transpiler rewrites
to a call against a generated symbol (jsxDEV_7x81h0kn, etc.) but only emits the matching import when autoImportJSX is true. The option previously defaulted to false, so the default transpiler output referenced an undefined identifier and could not be run or re-bundled. This flips the Bun.Transpiler default to true. The bundler and the module loader already enable it, so this only changes the standalone API. The classic runtime and an explicit autoImportJSX: false are unchanged. Fixes #7499. --- packages/bun-types/bun.d.ts | 9 ++++++ src/runtime/api/JSTranspiler.rs | 1 + test/bundler/transpiler/transpiler.test.js | 37 ++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 46096c814092..bc5b0835f75d 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -2462,6 +2462,15 @@ declare module "bun" { */ macro?: MacroMap; + /** + * When the automatic JSX runtime is active, prepend the + * `import { jsx, ... } from "/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 + */ autoImportJSX?: boolean; allowBunRuntime?: boolean; exports?: { diff --git a/src/runtime/api/JSTranspiler.rs b/src/runtime/api/JSTranspiler.rs index cdde6429ea73..ac20af05dffd 100644 --- a/src/runtime/api/JSTranspiler.rs +++ b/src/runtime/api/JSTranspiler.rs @@ -97,6 +97,7 @@ impl Default for Config { log: bun_ast::Log::default(), // overwritten at construction runtime: Runtime::Features { top_level_await: true, + auto_import_jsx: true, ..Default::default() }, tree_shaking: false, diff --git a/test/bundler/transpiler/transpiler.test.js b/test/bundler/transpiler/transpiler.test.js index b8fcdca585ac..c008e5dcbcea 100644 --- a/test/bundler/transpiler/transpiler.test.js +++ b/test/bundler/transpiler/transpiler.test.js @@ -2086,6 +2086,7 @@ export default <>hi "process.env.NODE_ENV": JSON.stringify("development"), }, logLevel: "error", + autoImportJSX: false, }); expect(bun.transformSync("console.log(
{}} points={() => {}}>
);")).toBe( @@ -2154,6 +2155,40 @@ console.log(
);`), ); }); + // 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. + it("JSX automatic runtime import is emitted by default", () => { + for (const loader of ["jsx", "tsx"]) { + const out = new Bun.Transpiler({ loader }).transformSync("export default <>
"); + 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
"); + 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
"); + expect(classic).not.toContain("import "); + + // autoImportJSX: false opts out of the import. + const optOut = new Bun.Transpiler({ loader: "tsx", autoImportJSX: false }).transformSync("export default
"); + expect(optOut).not.toContain("import "); + }); + // 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 @@ -2203,6 +2238,7 @@ console.log(
);`), loader: "jsx", define: { "process.env.NODE_ENV": JSON.stringify("development") }, logLevel: "error", + autoImportJSX: false, }); process.stdout.write(t.transformSync('console.log(
);')); process.stdout.write(t.transformSync('console.log(
);')); @@ -2344,6 +2380,7 @@ console.log(
);`), define: { "process.env.NODE_ENV": JSON.stringify("development"), }, + autoImportJSX: false, }); expect(bun.transformSync("export var foo =
{...a}b
")).toBe( `export var foo = jsxDEV_7x81h0kn("div", {