diff --git a/test/bundler/transpiler/jsx-dev/jsx-dev.tsx b/test/bundler/transpiler/jsx-dev/jsx-dev.tsx deleted file mode 100644 index ee854a800268..000000000000 --- a/test/bundler/transpiler/jsx-dev/jsx-dev.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { renderToReadableStream } from "react-dom/server.browser"; - -const HelloWorld = () => { - return
Hello World
; -}; - -const stream = new Response(await renderToReadableStream()); - -console.log(await stream.text()); - -if (!process.env.NO_BUILD) { - const self = await Bun.build({ - entrypoints: [import.meta.path], - define: { - "process.env.NODE_ENV": JSON.stringify(process.env.CHILD_NODE_ENV), - "process.env.NO_BUILD": "1", - }, - }); - const code = await self.outputs[0].text(); - let shouldHaveJSXDev = process.env.CHILD_NODE_ENV === "development"; - let shouldHaveJSX = process.env.CHILD_NODE_ENV === "production"; - - if (shouldHaveJSXDev) { - if (!code.includes("jsx_dev_runtime.jsxDEV")) { - throw new Error("jsxDEV is not included"); - } - } - - if (shouldHaveJSX) { - if (!code.includes("jsx_runtime.jsx")) { - throw new Error("Jsx is not included"); - } - } - - const url = URL.createObjectURL(self.outputs[0]); - await import(url); -} diff --git a/test/bundler/transpiler/jsx-dev/tsconfig.json b/test/bundler/transpiler/jsx-dev/tsconfig.json deleted file mode 100644 index 12a075f81a97..000000000000 --- a/test/bundler/transpiler/jsx-dev/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "jsx": "react-jsxdev" - } -} diff --git a/test/bundler/transpiler/jsx-production-entry.ts b/test/bundler/transpiler/jsx-production-entry.ts deleted file mode 100644 index eec766abee15..000000000000 --- a/test/bundler/transpiler/jsx-production-entry.ts +++ /dev/null @@ -1 +0,0 @@ -import "./jsx-production"; diff --git a/test/bundler/transpiler/jsx-production.test.ts b/test/bundler/transpiler/jsx-production.test.ts index 537efad04e7b..5fa717789adc 100644 --- a/test/bundler/transpiler/jsx-production.test.ts +++ b/test/bundler/transpiler/jsx-production.test.ts @@ -1,46 +1,80 @@ import { describe, expect, test } from "bun:test"; -import { bunEnv, bunExe } from "harness"; -import path from "path"; +import { bunEnv, bunExe, tempDir } from "harness"; // https://github.com/oven-sh/bun/issues/3768 -describe.concurrent("jsx", () => { - for (const node_env of ["production", "development", "test", ""]) { - for (const child_node_env of ["production", "development", "test", ""]) { - test(`react-jsxDEV parent: ${node_env} child: ${child_node_env} should work`, async () => { - const env = { ...bunEnv }; - env.NODE_ENV = node_env; - env.CHILD_NODE_ENV = child_node_env; - env.TSCONFIG_JSX = "react-jsxdev"; - await using proc = Bun.spawn({ - cmd: [bunExe(), "run", path.join(import.meta.dirname, "jsx-dev", "jsx-dev.tsx")], - cwd: import.meta.dirname, - env: env, - stdout: "pipe", - stderr: "inherit", - stdin: "ignore", - }); - const out = await new Response(proc.stdout).text(); - expect(out).toBe("
Hello World
" + "\n" + "
Hello World
" + "\n"); - expect(await proc.exited).toBe(0); +// +// app.tsx is transpiled twice per case: once by `bun run` (NODE_ENV comes from the +// environment) and once by the Bun.build() call inside it, which defines +// process.env.NODE_ENV to CHILD_NODE_ENV and then executes the bundle it produced. +// The stub `react` runtimes return their own name, so each pass prints which runtime +// the entry point and the file it imports were compiled against. (This used to render +// with the real react-dom and bundle it; that took several seconds per case on debug +// builds, and the 32 concurrent cases timed out.) +const files = { + "node_modules/react/jsx-runtime.js": /* js */ ` + export const Fragment = Symbol.for("react.fragment"); + export const jsx = () => "jsx"; + export const jsxs = jsx; + `, + "node_modules/react/jsx-dev-runtime.js": /* js */ ` + export const Fragment = Symbol.for("react.fragment"); + export const jsxDEV = () => "jsxDEV"; + `, + "child.tsx": /* tsx */ ` + export const child = ; + `, + "app.tsx": /* tsx */ ` + import { child } from "./child"; + + console.log(process.env.BUNDLED ? "bundle:" : "run:",
, child); + + if (!process.env.BUNDLED) { + const build = await Bun.build({ + entrypoints: [import.meta.path], + define: { + "process.env.NODE_ENV": JSON.stringify(process.env.CHILD_NODE_ENV), + "process.env.BUNDLED": "1", + }, }); + await import(URL.createObjectURL(build.outputs[0])); + } + `, +}; - test(`react-jsx parent: ${node_env} child: ${child_node_env} should work`, async () => { - const env = { ...bunEnv }; - env.NODE_ENV = node_env; - env.CHILD_NODE_ENV = child_node_env; - env.TSCONFIG_JSX = "react-jsx"; - await using proc = Bun.spawn({ - cmd: [bunExe(), "run", path.join(import.meta.dirname, "jsx-production-entry.ts")], - cwd: import.meta.dirname, - env: env, - stdout: "pipe", - stderr: "inherit", - stdin: "ignore", +const runtimes = ["jsx", "jsxDEV"]; +const nodeEnvs = ["production", "development", "test", ""]; + +describe.concurrent("jsx", () => { + for (const jsx of ["react-jsx", "react-jsxdev"]) { + for (const node_env of nodeEnvs) { + for (const child_node_env of nodeEnvs) { + test(`tsconfig ${jsx}, NODE_ENV=${JSON.stringify(node_env)}, bundle defines NODE_ENV=${JSON.stringify(child_node_env)}`, async () => { + using dir = tempDir("jsx-production", { + ...files, + "tsconfig.json": JSON.stringify({ compilerOptions: { jsx } }), + }); + await using proc = Bun.spawn({ + cmd: [bunExe(), "run", "app.tsx"], + cwd: String(dir), + env: { ...bunEnv, BUN_ENV: undefined, NODE_ENV: node_env, CHILD_NODE_ENV: child_node_env }, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + // NODE_ENV=production selects the production runtime in both passes, and a bundle + // whose define says "development" selects jsxDEV no matter what the parent process + // or tsconfig said. The remaining combinations fall back to the tsconfig and the + // parent environment; they are not pinned here, but both passes still have to pick + // a runtime that exists and compile both files against the same one. + const runPass = node_env === "production" ? ["jsx"] : runtimes; + const bundlePass = + child_node_env === "production" ? ["jsx"] : child_node_env === "development" ? ["jsxDEV"] : runtimes; + expect(stderr).toBe(""); + expect(stdout).toBeOneOf(runPass.flatMap(r => bundlePass.map(b => `run: ${r} ${r}\nbundle: ${b} ${b}\n`))); + expect(exitCode).toBe(0); }); - const out = await new Response(proc.stdout).text(); - expect(out).toBe("
Hello World
" + "\n" + "
Hello World
" + "\n"); - expect(await proc.exited).toBe(0); - }); + } } } }); diff --git a/test/bundler/transpiler/jsx-production.tsx b/test/bundler/transpiler/jsx-production.tsx deleted file mode 100644 index ee854a800268..000000000000 --- a/test/bundler/transpiler/jsx-production.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { renderToReadableStream } from "react-dom/server.browser"; - -const HelloWorld = () => { - return
Hello World
; -}; - -const stream = new Response(await renderToReadableStream()); - -console.log(await stream.text()); - -if (!process.env.NO_BUILD) { - const self = await Bun.build({ - entrypoints: [import.meta.path], - define: { - "process.env.NODE_ENV": JSON.stringify(process.env.CHILD_NODE_ENV), - "process.env.NO_BUILD": "1", - }, - }); - const code = await self.outputs[0].text(); - let shouldHaveJSXDev = process.env.CHILD_NODE_ENV === "development"; - let shouldHaveJSX = process.env.CHILD_NODE_ENV === "production"; - - if (shouldHaveJSXDev) { - if (!code.includes("jsx_dev_runtime.jsxDEV")) { - throw new Error("jsxDEV is not included"); - } - } - - if (shouldHaveJSX) { - if (!code.includes("jsx_runtime.jsx")) { - throw new Error("Jsx is not included"); - } - } - - const url = URL.createObjectURL(self.outputs[0]); - await import(url); -}