diff --git a/src/runtime/bake/config/transpiler.rs b/src/runtime/bake/config/transpiler.rs index 031283d76ba2..576c9eea0b19 100644 --- a/src/runtime/bake/config/transpiler.rs +++ b/src/runtime/bake/config/transpiler.rs @@ -47,6 +47,8 @@ impl Framework { out.options.out_extensions = bun_collections::StringHashMap::new(); out.options.hot_module_reloading = mode == Mode::Development; out.options.code_splitting = mode != Mode::Development; + // Server chunks are loaded from memory, where `import.meta.require` finds no chunk file. + out.options.split_require = false; // force disable filesystem output, even though bundle_v2 // is special cased to return before that code is reached. diff --git a/test/bake/dev/production.test.ts b/test/bake/dev/production.test.ts index 6771c0f96261..28d516eb89ff 100644 --- a/test/bake/dev/production.test.ts +++ b/test/bake/dev/production.test.ts @@ -703,6 +703,137 @@ export default function Counter() { expect(foundCounterBundle).toBe(true); }); + // Server chunks are loaded from memory, so the bundler must not print a + // require() of an ES module as a load of a sibling chunk file. One page per shape. + test.concurrent("server code can require() an ES module", async () => { + const dir = await tempDirWithBakeDeps("bake-production-require-esm", { + "src/index.tsx": `export default { app: { framework: "react" } };`, + + // A CommonJS file requires it. + "pages/index.tsx": `import { text } from "../lib/read.mjs"; +export default function Page() { + return
{text}
; +}`, + "lib/read.mjs": `import lib from "./index.cjs"; +export const text = "app: " + lib.version + " " + typeof lib.add;`, + "lib/index.cjs": `const esm = require("./esm.mjs"); module.exports = esm;`, + "lib/esm.mjs": `export const version = "1.2.3"; +export function add(a, b) { return a + b; } +export default { version: "the default object" };`, + + // The page requires it. + "pages/in-page.tsx": `const dep = require("../lib/in-page.ts"); +export default function Page() { + return{"in-page: " + dep.version}
; +}`, + "lib/in-page.ts": `export const version = "1.2.3";`, + + // A function requires it, twice, during the render. It does not run before that. + "pages/lazy.tsx": `import { load } from "../lib/lazy.cjs"; +const before = typeof globalThis.lazyRuns; +export default function Page() { + const first = load(); + return{"lazy: " + before + " " + first.version + " " + (first === load()) + " " + globalThis.lazyRuns}
; +}`, + "lib/lazy.cjs": `exports.load = () => require("./lazy-dep.mjs");`, + "lib/lazy-dep.mjs": `globalThis.lazyRuns = (globalThis.lazyRuns ?? 0) + 1; +export const version = "1.2.3";`, + + // A client component requires it (the SSR graph). + "pages/client.tsx": `import Client from "../lib/Client"; +export default function Page() { + return{"client: " + dep.version}
; +}`, + "lib/client-dep.ts": `export const version = "1.2.3";`, + + // It renders a client component. The page must load the client code. + "pages/island.tsx": `const { Island } = require("../lib/island.tsx"); +export default function Page() { + returnisland: button
; +}`, + + // The require() is in a try block. The catch block must not run. + "pages/optional.tsx": `let dep; +try { + dep = require("../lib/optional-dep.ts"); +} catch { + dep = { version: "missing" }; +} +export default function Page() { + return{"optional: " + dep.version}
; +}`, + "lib/optional-dep.ts": `export const version = "1.2.3";`, + + // require() and import() of one module give one instance. + "pages/both.tsx": `const required = require("../lib/both-dep.ts"); +export default async function Page() { + const imported = await import("../lib/both-dep.ts"); + return{"both: " + required.version + " " + (required.state === imported.state) + " " + globalThis.bothRuns}
; +}`, + "lib/both-dep.ts": `globalThis.bothRuns = (globalThis.bothRuns ?? 0) + 1; +export const version = "1.2.3"; +export const state = {};`, + + // What already worked: require() of JSON, of CommonJS and of a builtin, a + // require() that never runs, and import(). + "pages/controls.tsx": `import { never, text } from "../lib/controls.cjs"; +export default async function Page() { + const { name } = await import("../lib/dynamic.ts"); + return{"controls: " + text + " " + name + " " + typeof never + " " + typeof globalThis.neverRan}
; +}`, + "lib/controls.cjs": `const json = require("./data.json"); +const cjs = require("./plain.cjs"); +const util = require("node:util"); +exports.never = () => require("./never.mjs"); +exports.text = [json.name, cjs.name, typeof util.inspect].join(" ");`, + "lib/data.json": `{ "name": "json" }`, + "lib/plain.cjs": `exports.name = "cjs";`, + "lib/never.mjs": `globalThis.neverRan = true; +export const name = "never";`, + "lib/dynamic.ts": `export const name = "import";`, + }); + + const build = await Bun.$`${bunExe()} build --app ./src/index.tsx --outdir ./dist` + .cwd(dir) + .env(bunEnv) + .throws(false); + + expect(build.stderr.toString()).not.toContain("error:"); + + const rendered: Record(.*?)<\/p>/)?.[1]; + if (html.includes('