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", {