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