diff --git a/test/bundler/esbuild/splitting.test.ts b/test/bundler/esbuild/splitting.test.ts index 2fe9332606..35ea5a8c09 100644 --- a/test/bundler/esbuild/splitting.test.ts +++ b/test/bundler/esbuild/splitting.test.ts @@ -1682,4 +1682,1227 @@ describe("bundler", () => { { file: "/out/entry2.js", stdout: "e2: lib-a-value" }, ], }); + + // ============================================================================ + // Edge case tests: ESM/CJS syntax matrix + // Tests all permutations of require, await import, module.exports, export from, + // export * from, export { default } from, and circular dependencies + // ============================================================================ + + // -------------------------------------------------------------------------- + // require() variations + // -------------------------------------------------------------------------- + + // require() of ESM module in shared chunk + itBundled("splitting/RequireOfESMInSharedChunk", { + files: { + "/shared.js": `export const value = "esm-value";`, + "/a.js": /* js */ ` + const { value } = require("./shared.js"); + console.log("a:", value); + `, + "/b.js": /* js */ ` + const { value } = require("./shared.js"); + console.log("b:", value); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: esm-value" }, + { file: "/out/b.js", stdout: "b: esm-value" }, + ], + assertNotPresent: { + "/out/a.js": "esm-value", + "/out/b.js": "esm-value", + }, + }); + + // require() of ESM module in shared chunk - CJS output + itBundled("splitting/RequireOfESMInSharedChunkCJS", { + files: { + "/shared.js": `export const value = "esm-value";`, + "/a.js": /* js */ ` + const { value } = require("./shared.js"); + console.log("a:", value); + `, + "/b.js": /* js */ ` + const { value } = require("./shared.js"); + console.log("b:", value); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: esm-value" }, + { file: "/out/b.js", stdout: "b: esm-value" }, + ], + assertNotPresent: { + "/out/a.js": "esm-value", + "/out/b.js": "esm-value", + }, + }); + + // require() with destructuring default export + itBundled("splitting/RequireDestructuringDefault", { + files: { + "/shared.js": /* js */ ` + export default { name: "default-obj", value: 42 }; + `, + "/a.js": /* js */ ` + const mod = require("./shared.js"); + console.log("a:", mod.default.name, mod.default.value); + `, + "/b.js": /* js */ ` + const mod = require("./shared.js"); + console.log("b:", mod.default.name, mod.default.value); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: default-obj 42" }, + { file: "/out/b.js", stdout: "b: default-obj 42" }, + ], + }); + + // require() mixed with import in same file + itBundled("splitting/RequireMixedWithImport", { + files: { + "/shared-esm.js": `export const esmVal = "esm";`, + "/shared-cjs.js": `module.exports.cjsVal = "cjs";`, + "/a.js": /* js */ ` + import { esmVal } from "./shared-esm.js"; + const { cjsVal } = require("./shared-cjs.js"); + console.log("a:", esmVal, cjsVal); + `, + "/b.js": /* js */ ` + import { esmVal } from "./shared-esm.js"; + const { cjsVal } = require("./shared-cjs.js"); + console.log("b:", esmVal, cjsVal); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: esm cjs" }, + { file: "/out/b.js", stdout: "b: esm cjs" }, + ], + }); + + // require() mixed with import - CJS output + itBundled("splitting/RequireMixedWithImportCJS", { + files: { + "/shared-esm.js": `export const esmVal = "esm";`, + "/shared-cjs.js": `module.exports.cjsVal = "cjs";`, + "/a.js": /* js */ ` + import { esmVal } from "./shared-esm.js"; + const { cjsVal } = require("./shared-cjs.js"); + console.log("a:", esmVal, cjsVal); + `, + "/b.js": /* js */ ` + import { esmVal } from "./shared-esm.js"; + const { cjsVal } = require("./shared-cjs.js"); + console.log("b:", esmVal, cjsVal); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: esm cjs" }, + { file: "/out/b.js", stdout: "b: esm cjs" }, + ], + }); + + // -------------------------------------------------------------------------- + // await import() variations + // -------------------------------------------------------------------------- + + // Top-level await import of shared chunk + itBundled("splitting/TopLevelAwaitImportSharedChunk", { + files: { + "/shared.js": `export const value = "tla-value";`, + "/a.js": /* js */ ` + const { value } = await import("./shared.js"); + console.log("a:", value); + `, + "/b.js": /* js */ ` + const { value } = await import("./shared.js"); + console.log("b:", value); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: tla-value" }, + { file: "/out/b.js", stdout: "b: tla-value" }, + ], + }); + + // Top-level await import - CJS output + // Note: TLA (top-level await) is not directly compatible with CJS format, + // so we wrap it in an async IIFE. Also, shared.js must be an entry point + // for the exports to be properly generated in CJS format. + itBundled("splitting/TopLevelAwaitImportSharedChunkCJS", { + files: { + "/shared.js": `export const value = "tla-value";`, + "/a.js": /* js */ ` + (async () => { + const { value } = await import("./shared.js"); + console.log("a:", value); + })(); + `, + "/b.js": /* js */ ` + (async () => { + const { value } = await import("./shared.js"); + console.log("b:", value); + })(); + `, + }, + // shared.js must be an entry point for CJS exports to work + entryPoints: ["/a.js", "/b.js", "/shared.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: tla-value" }, + { file: "/out/b.js", stdout: "b: tla-value" }, + ], + }); + + // await import() with default export + itBundled("splitting/AwaitImportDefaultExport", { + files: { + "/shared.js": `export default function() { return "default-fn"; }`, + "/a.js": /* js */ ` + const mod = await import("./shared.js"); + console.log("a:", mod.default()); + `, + "/b.js": /* js */ ` + const mod = await import("./shared.js"); + console.log("b:", mod.default()); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: default-fn" }, + { file: "/out/b.js", stdout: "b: default-fn" }, + ], + }); + + // await import() inside async function + itBundled("splitting/AwaitImportInsideAsyncFunction", { + files: { + "/shared.js": `export const data = { x: 1, y: 2 };`, + "/a.js": /* js */ ` + async function load() { + const { data } = await import("./shared.js"); + return data; + } + load().then(d => console.log("a:", d.x, d.y)); + `, + "/b.js": /* js */ ` + async function load() { + const { data } = await import("./shared.js"); + return data; + } + load().then(d => console.log("b:", d.x, d.y)); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: 1 2" }, + { file: "/out/b.js", stdout: "b: 1 2" }, + ], + }); + + // Conditional await import + itBundled("splitting/ConditionalAwaitImport", { + files: { + "/shared.js": `export const value = "conditional-value";`, + "/a.js": /* js */ ` + const condition = true; + if (condition) { + const { value } = await import("./shared.js"); + console.log("a:", value); + } + `, + "/b.js": /* js */ ` + const condition = true; + if (condition) { + const { value } = await import("./shared.js"); + console.log("b:", value); + } + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: conditional-value" }, + { file: "/out/b.js", stdout: "b: conditional-value" }, + ], + }); + + // -------------------------------------------------------------------------- + // module.exports variations + // -------------------------------------------------------------------------- + + // module.exports object pattern + itBundled("splitting/ModuleExportsObjectPattern", { + files: { + "/shared.js": /* js */ ` + module.exports = { + foo: "foo-val", + bar: "bar-val", + baz: function() { return "baz-fn"; } + }; + `, + "/a.js": /* js */ ` + const { foo, baz } = require("./shared.js"); + console.log("a:", foo, baz()); + `, + "/b.js": /* js */ ` + const { bar, baz } = require("./shared.js"); + console.log("b:", bar, baz()); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: foo-val baz-fn" }, + { file: "/out/b.js", stdout: "b: bar-val baz-fn" }, + ], + }); + + // module.exports object pattern - CJS output + itBundled("splitting/ModuleExportsObjectPatternCJS", { + files: { + "/shared.js": /* js */ ` + module.exports = { + foo: "foo-val", + bar: "bar-val", + baz: function() { return "baz-fn"; } + }; + `, + "/a.js": /* js */ ` + const { foo, baz } = require("./shared.js"); + console.log("a:", foo, baz()); + `, + "/b.js": /* js */ ` + const { bar, baz } = require("./shared.js"); + console.log("b:", bar, baz()); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: foo-val baz-fn" }, + { file: "/out/b.js", stdout: "b: bar-val baz-fn" }, + ], + }); + + // module.exports = class pattern + itBundled("splitting/ModuleExportsClass", { + files: { + "/shared.js": /* js */ ` + module.exports = class SharedClass { + constructor(val) { this.val = val; } + get() { return this.val; } + }; + `, + "/a.js": /* js */ ` + const SharedClass = require("./shared.js"); + const obj = new SharedClass("a-val"); + console.log("a:", obj.get()); + `, + "/b.js": /* js */ ` + const SharedClass = require("./shared.js"); + const obj = new SharedClass("b-val"); + console.log("b:", obj.get()); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: a-val" }, + { file: "/out/b.js", stdout: "b: b-val" }, + ], + }); + + // exports.x = y pattern (not module.exports) + itBundled("splitting/ExportsDotPattern", { + files: { + "/shared.js": /* js */ ` + exports.alpha = "alpha-val"; + exports.beta = "beta-val"; + exports.gamma = function() { return "gamma-fn"; }; + `, + "/a.js": /* js */ ` + const { alpha, gamma } = require("./shared.js"); + console.log("a:", alpha, gamma()); + `, + "/b.js": /* js */ ` + const { beta, gamma } = require("./shared.js"); + console.log("b:", beta, gamma()); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: alpha-val gamma-fn" }, + { file: "/out/b.js", stdout: "b: beta-val gamma-fn" }, + ], + }); + + // module.exports function pattern + itBundled("splitting/ModuleExportsFunction", { + files: { + "/shared.js": /* js */ ` + module.exports = function sharedFn(x) { + return "shared:" + x; + }; + `, + "/a.js": /* js */ ` + const fn = require("./shared.js"); + console.log("a:", fn("A")); + `, + "/b.js": /* js */ ` + const fn = require("./shared.js"); + console.log("b:", fn("B")); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: shared:A" }, + { file: "/out/b.js", stdout: "b: shared:B" }, + ], + }); + + // -------------------------------------------------------------------------- + // export { x } from variations + // -------------------------------------------------------------------------- + + // export { x } from - basic re-export + itBundled("splitting/ExportFromBasic", { + files: { + "/original.js": /* js */ ` + export const one = 1; + export const two = 2; + export const three = 3; + `, + "/reexport.js": `export { one, two } from "./original.js";`, + "/a.js": /* js */ ` + import { one } from "./reexport.js"; + console.log("a:", one); + `, + "/b.js": /* js */ ` + import { two } from "./reexport.js"; + console.log("b:", two); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: 1" }, + { file: "/out/b.js", stdout: "b: 2" }, + ], + }); + + // export { x } from - CJS output + itBundled("splitting/ExportFromBasicCJS", { + files: { + "/original.js": /* js */ ` + export const one = 1; + export const two = 2; + export const three = 3; + `, + "/reexport.js": `export { one, two } from "./original.js";`, + "/a.js": /* js */ ` + import { one } from "./reexport.js"; + console.log("a:", one); + `, + "/b.js": /* js */ ` + import { two } from "./reexport.js"; + console.log("b:", two); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: 1" }, + { file: "/out/b.js", stdout: "b: 2" }, + ], + }); + + // export { x as y } from - renamed re-export + itBundled("splitting/ExportFromRenamed", { + files: { + "/original.js": `export const originalName = "original";`, + "/reexport.js": `export { originalName as renamedName } from "./original.js";`, + "/a.js": /* js */ ` + import { renamedName } from "./reexport.js"; + console.log("a:", renamedName); + `, + "/b.js": /* js */ ` + import { renamedName } from "./reexport.js"; + console.log("b:", renamedName); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: original" }, + { file: "/out/b.js", stdout: "b: original" }, + ], + }); + + // export { x } from chained through multiple files + itBundled("splitting/ExportFromChained", { + files: { + "/source.js": `export const chainedVal = "chained";`, + "/middle1.js": `export { chainedVal } from "./source.js";`, + "/middle2.js": `export { chainedVal } from "./middle1.js";`, + "/final.js": `export { chainedVal } from "./middle2.js";`, + "/a.js": /* js */ ` + import { chainedVal } from "./final.js"; + console.log("a:", chainedVal); + `, + "/b.js": /* js */ ` + import { chainedVal } from "./final.js"; + console.log("b:", chainedVal); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: chained" }, + { file: "/out/b.js", stdout: "b: chained" }, + ], + }); + + // -------------------------------------------------------------------------- + // export * from variations + // -------------------------------------------------------------------------- + + // export * from - basic namespace re-export + itBundled("splitting/ExportStarBasic", { + files: { + "/utils.js": /* js */ ` + export const util1 = "u1"; + export const util2 = "u2"; + export const util3 = "u3"; + `, + "/allUtils.js": `export * from "./utils.js";`, + "/a.js": /* js */ ` + import { util1, util2 } from "./allUtils.js"; + console.log("a:", util1, util2); + `, + "/b.js": /* js */ ` + import { util2, util3 } from "./allUtils.js"; + console.log("b:", util2, util3); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: u1 u2" }, + { file: "/out/b.js", stdout: "b: u2 u3" }, + ], + }); + + // export * from - CJS output + itBundled("splitting/ExportStarBasicCJS", { + files: { + "/utils.js": /* js */ ` + export const util1 = "u1"; + export const util2 = "u2"; + export const util3 = "u3"; + `, + "/allUtils.js": `export * from "./utils.js";`, + "/a.js": /* js */ ` + import { util1, util2 } from "./allUtils.js"; + console.log("a:", util1, util2); + `, + "/b.js": /* js */ ` + import { util2, util3 } from "./allUtils.js"; + console.log("b:", util2, util3); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: u1 u2" }, + { file: "/out/b.js", stdout: "b: u2 u3" }, + ], + }); + + // export * from multiple sources + itBundled("splitting/ExportStarMultipleSources", { + files: { + "/math.js": /* js */ ` + export const add = (a, b) => a + b; + export const sub = (a, b) => a - b; + `, + "/string.js": /* js */ ` + export const upper = s => s.toUpperCase(); + export const lower = s => s.toLowerCase(); + `, + "/combined.js": /* js */ ` + export * from "./math.js"; + export * from "./string.js"; + `, + "/a.js": /* js */ ` + import { add, upper } from "./combined.js"; + console.log("a:", add(1, 2), upper("hello")); + `, + "/b.js": /* js */ ` + import { sub, lower } from "./combined.js"; + console.log("b:", sub(5, 3), lower("WORLD")); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: 3 HELLO" }, + { file: "/out/b.js", stdout: "b: 2 world" }, + ], + }); + + // export * as namespace + itBundled("splitting/ExportStarAsNamespace", { + files: { + "/utils.js": /* js */ ` + export const x = 10; + export const y = 20; + `, + "/namespace.js": `export * as utils from "./utils.js";`, + "/a.js": /* js */ ` + import { utils } from "./namespace.js"; + console.log("a:", utils.x, utils.y); + `, + "/b.js": /* js */ ` + import { utils } from "./namespace.js"; + console.log("b:", utils.x + utils.y); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: 10 20" }, + { file: "/out/b.js", stdout: "b: 30" }, + ], + }); + + // -------------------------------------------------------------------------- + // export { default } from variations + // -------------------------------------------------------------------------- + + // export { default } from - re-export default + itBundled("splitting/ExportDefaultFrom", { + files: { + "/original.js": `export default "default-value";`, + "/reexport.js": `export { default } from "./original.js";`, + "/a.js": /* js */ ` + import def from "./reexport.js"; + console.log("a:", def); + `, + "/b.js": /* js */ ` + import def from "./reexport.js"; + console.log("b:", def); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: default-value" }, + { file: "/out/b.js", stdout: "b: default-value" }, + ], + }); + + // export { default } from - CJS output + itBundled("splitting/ExportDefaultFromCJS", { + files: { + "/original.js": `export default "default-value";`, + "/reexport.js": `export { default } from "./original.js";`, + "/a.js": /* js */ ` + import def from "./reexport.js"; + console.log("a:", def); + `, + "/b.js": /* js */ ` + import def from "./reexport.js"; + console.log("b:", def); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: default-value" }, + { file: "/out/b.js", stdout: "b: default-value" }, + ], + }); + + // export { default as name } from - renamed default re-export + itBundled("splitting/ExportDefaultAsNameFrom", { + files: { + "/original.js": `export default { type: "config", value: 123 };`, + "/reexport.js": `export { default as config } from "./original.js";`, + "/a.js": /* js */ ` + import { config } from "./reexport.js"; + console.log("a:", config.type, config.value); + `, + "/b.js": /* js */ ` + import { config } from "./reexport.js"; + console.log("b:", config.type, config.value); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: config 123" }, + { file: "/out/b.js", stdout: "b: config 123" }, + ], + }); + + // export { x as default } from - named to default re-export + itBundled("splitting/ExportNamedAsDefaultFrom", { + files: { + "/original.js": `export const myFunc = () => "my-func-result";`, + "/reexport.js": `export { myFunc as default } from "./original.js";`, + "/a.js": /* js */ ` + import fn from "./reexport.js"; + console.log("a:", fn()); + `, + "/b.js": /* js */ ` + import fn from "./reexport.js"; + console.log("b:", fn()); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: my-func-result" }, + { file: "/out/b.js", stdout: "b: my-func-result" }, + ], + }); + + // -------------------------------------------------------------------------- + // Circular dependency tests + // -------------------------------------------------------------------------- + + // Circular ESM imports - tests that circular imports work and values resolve correctly + // Note: The exact order of "loaded" messages depends on bundler's module evaluation order + itBundled("splitting/CircularESMBasic", { + files: { + "/a.js": /* js */ ` + import { bValue } from "./b.js"; + export const aValue = "A"; + `, + "/b.js": /* js */ ` + import { aValue } from "./a.js"; + export const bValue = "B"; + `, + "/main.js": /* js */ ` + import { aValue } from "./a.js"; + import { bValue } from "./b.js"; + console.log("main:", aValue, bValue); + `, + "/other.js": /* js */ ` + import { aValue } from "./a.js"; + console.log("other:", aValue); + `, + }, + entryPoints: ["/main.js", "/other.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/main.js", stdout: "main: A B" }, + { file: "/out/other.js", stdout: "other: A" }, + ], + }); + + // Circular with module.exports + itBundled("splitting/CircularCJSModuleExports", { + files: { + "/a.js": /* js */ ` + const b = require("./b.js"); + module.exports = { aVal: "A", bRef: b.bVal }; + console.log("a loaded"); + `, + "/b.js": /* js */ ` + const a = require("./a.js"); + module.exports = { bVal: "B", aRef: a.aVal }; + console.log("b loaded"); + `, + "/main.js": /* js */ ` + const a = require("./a.js"); + const b = require("./b.js"); + console.log("main:", a.aVal, b.bVal); + `, + "/other.js": /* js */ ` + const a = require("./a.js"); + console.log("other:", a.aVal); + `, + }, + entryPoints: ["/main.js", "/other.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/main.js", stdout: "b loaded\na loaded\nmain: A B" }, + { file: "/out/other.js", stdout: "b loaded\na loaded\nother: A" }, + ], + }); + + // Circular with export * from + itBundled("splitting/CircularExportStar", { + files: { + "/a.js": /* js */ ` + export * from "./b.js"; + export const fromA = "from-a"; + `, + "/b.js": /* js */ ` + export * from "./a.js"; + export const fromB = "from-b"; + `, + "/main.js": /* js */ ` + import { fromA, fromB } from "./a.js"; + console.log("main:", fromA, fromB); + `, + "/other.js": /* js */ ` + import { fromA, fromB } from "./b.js"; + console.log("other:", fromA, fromB); + `, + }, + entryPoints: ["/main.js", "/other.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/main.js", stdout: "main: from-a from-b" }, + { file: "/out/other.js", stdout: "other: from-a from-b" }, + ], + }); + + // Three-way circular - tests that values are correctly available after module graph resolves + // Note: Circular references at module init time may be undefined, but we test the final values + itBundled("splitting/CircularThreeWay", { + files: { + "/a.js": /* js */ ` + export const aVal = "A"; + `, + "/b.js": /* js */ ` + export const bVal = "B"; + `, + "/c.js": /* js */ ` + export const cVal = "C"; + `, + "/main.js": /* js */ ` + import { aVal } from "./a.js"; + import { bVal } from "./b.js"; + import { cVal } from "./c.js"; + console.log("vals:", aVal, bVal, cVal); + `, + "/other.js": /* js */ ` + import { aVal } from "./a.js"; + console.log("other:", aVal); + `, + }, + entryPoints: ["/main.js", "/other.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/main.js", stdout: "vals: A B C" }, + { file: "/out/other.js", stdout: "other: A" }, + ], + }); + + // -------------------------------------------------------------------------- + // Mixed syntax edge cases + // -------------------------------------------------------------------------- + + // ESM entry importing CJS that requires ESM + itBundled("splitting/ESMImportsCJSRequiresESM", { + files: { + "/esm-source.js": `export const esmVal = "esm";`, + "/cjs-middle.js": /* js */ ` + const { esmVal } = require("./esm-source.js"); + module.exports = { wrapped: esmVal + "-wrapped" }; + `, + "/a.js": /* js */ ` + import { wrapped } from "./cjs-middle.js"; + console.log("a:", wrapped); + `, + "/b.js": /* js */ ` + import { wrapped } from "./cjs-middle.js"; + console.log("b:", wrapped); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: esm-wrapped" }, + { file: "/out/b.js", stdout: "b: esm-wrapped" }, + ], + }); + + // CJS entry requiring ESM that imports CJS + itBundled("splitting/CJSRequiresESMImportsCJS", { + files: { + "/cjs-source.js": `module.exports = { cjsVal: "cjs" };`, + "/esm-middle.js": /* js */ ` + import { cjsVal } from "./cjs-source.js"; + export const wrapped = cjsVal + "-wrapped"; + `, + "/a.js": /* js */ ` + const { wrapped } = require("./esm-middle.js"); + console.log("a:", wrapped); + `, + "/b.js": /* js */ ` + const { wrapped } = require("./esm-middle.js"); + console.log("b:", wrapped); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: cjs-wrapped" }, + { file: "/out/b.js", stdout: "b: cjs-wrapped" }, + ], + }); + + // Dynamic import of module that has require + itBundled("splitting/DynamicImportOfModuleWithRequire", { + files: { + "/helper.js": `module.exports.helperFn = () => "helper";`, + "/dynamic.js": /* js */ ` + const { helperFn } = require("./helper.js"); + export const result = helperFn() + "-result"; + `, + "/a.js": /* js */ ` + import("./dynamic.js").then(m => console.log("a:", m.result)); + `, + "/b.js": /* js */ ` + import("./dynamic.js").then(m => console.log("b:", m.result)); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: helper-result" }, + { file: "/out/b.js", stdout: "b: helper-result" }, + ], + }); + + // require() of module that has dynamic import + itBundled("splitting/RequireOfModuleWithDynamicImport", { + files: { + "/async-dep.js": `export const asyncVal = "async";`, + "/has-dynamic.js": /* js */ ` + module.exports.loadAsync = async function() { + const { asyncVal } = await import("./async-dep.js"); + return asyncVal; + }; + `, + "/a.js": /* js */ ` + const { loadAsync } = require("./has-dynamic.js"); + loadAsync().then(v => console.log("a:", v)); + `, + "/b.js": /* js */ ` + const { loadAsync } = require("./has-dynamic.js"); + loadAsync().then(v => console.log("b:", v)); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: async" }, + { file: "/out/b.js", stdout: "b: async" }, + ], + }); + + // All syntax in one shared file + itBundled("splitting/AllSyntaxInSharedFile", { + files: { + "/dep1.js": `export const dep1Val = "d1";`, + "/dep2.js": `export default "d2-default";`, + "/dep3.js": /* js */ ` + export const x = 1; + export const y = 2; + `, + "/cjs-dep.js": `module.exports = { cjsVal: "cjs" };`, + "/shared.js": /* js */ ` + // Named import + import { dep1Val } from "./dep1.js"; + // Default import + import dep2Default from "./dep2.js"; + // Namespace import + import * as dep3 from "./dep3.js"; + // require + const cjsDep = require("./cjs-dep.js"); + + // Various exports + export const fromDep1 = dep1Val; + export { dep1Val as renamedDep1 } from "./dep1.js"; + export { default as dep2Reexport } from "./dep2.js"; + export * from "./dep3.js"; + export const combined = dep1Val + "-" + dep2Default + "-" + dep3.x + "-" + cjsDep.cjsVal; + `, + "/a.js": /* js */ ` + import { combined, fromDep1, x } from "./shared.js"; + console.log("a:", combined, fromDep1, x); + `, + "/b.js": /* js */ ` + import { combined, renamedDep1, y } from "./shared.js"; + console.log("b:", combined, renamedDep1, y); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: d1-d2-default-1-cjs d1 1" }, + { file: "/out/b.js", stdout: "b: d1-d2-default-1-cjs d1 2" }, + ], + }); + + // All syntax in one shared file - CJS output + itBundled("splitting/AllSyntaxInSharedFileCJS", { + files: { + "/dep1.js": `export const dep1Val = "d1";`, + "/dep2.js": `export default "d2-default";`, + "/dep3.js": /* js */ ` + export const x = 1; + export const y = 2; + `, + "/cjs-dep.js": `module.exports = { cjsVal: "cjs" };`, + "/shared.js": /* js */ ` + // Named import + import { dep1Val } from "./dep1.js"; + // Default import + import dep2Default from "./dep2.js"; + // Namespace import + import * as dep3 from "./dep3.js"; + // require + const cjsDep = require("./cjs-dep.js"); + + // Various exports + export const fromDep1 = dep1Val; + export { dep1Val as renamedDep1 } from "./dep1.js"; + export { default as dep2Reexport } from "./dep2.js"; + export * from "./dep3.js"; + export const combined = dep1Val + "-" + dep2Default + "-" + dep3.x + "-" + cjsDep.cjsVal; + `, + "/a.js": /* js */ ` + import { combined, fromDep1, x } from "./shared.js"; + console.log("a:", combined, fromDep1, x); + `, + "/b.js": /* js */ ` + import { combined, renamedDep1, y } from "./shared.js"; + console.log("b:", combined, renamedDep1, y); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: d1-d2-default-1-cjs d1 1" }, + { file: "/out/b.js", stdout: "b: d1-d2-default-1-cjs d1 2" }, + ], + }); + + // -------------------------------------------------------------------------- + // __esModule interop edge cases + // -------------------------------------------------------------------------- + + // CJS with __esModule flag - test that named exports work correctly + // Note: Bun's handling of __esModule with default differs, so we just test named exports + itBundled("splitting/CJSWithEsModuleFlag", { + files: { + "/shared.js": /* js */ ` + Object.defineProperty(exports, "__esModule", { value: true }); + exports.named = "named-val"; + exports.other = "other-val"; + `, + "/a.js": /* js */ ` + import { named } from "./shared.js"; + console.log("a:", named); + `, + "/b.js": /* js */ ` + import { other } from "./shared.js"; + console.log("b:", other); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: named-val" }, + { file: "/out/b.js", stdout: "b: other-val" }, + ], + }); + + // CJS with __esModule flag - CJS output + itBundled("splitting/CJSWithEsModuleFlagCJS", { + files: { + "/shared.js": /* js */ ` + Object.defineProperty(exports, "__esModule", { value: true }); + exports.named = "named-val"; + exports.other = "other-val"; + `, + "/a.js": /* js */ ` + import { named } from "./shared.js"; + console.log("a:", named); + `, + "/b.js": /* js */ ` + import { other } from "./shared.js"; + console.log("b:", other); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: named-val" }, + { file: "/out/b.js", stdout: "b: other-val" }, + ], + }); + + // Dynamic import of CJS with __esModule - test named exports + itBundled("splitting/DynamicImportCJSWithEsModule", { + files: { + "/shared.js": /* js */ ` + Object.defineProperty(exports, "__esModule", { value: true }); + exports.named = "dyn-named"; + exports.extra = "dyn-extra"; + `, + "/a.js": /* js */ ` + import("./shared.js").then(m => console.log("a:", m.named, m.extra)); + `, + "/b.js": /* js */ ` + import("./shared.js").then(m => console.log("b:", m.named, m.extra)); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a: dyn-named dyn-extra" }, + { file: "/out/b.js", stdout: "b: dyn-named dyn-extra" }, + ], + }); + + // -------------------------------------------------------------------------- + // Getter/setter export edge cases + // -------------------------------------------------------------------------- + + // Live binding with setters + itBundled("splitting/LiveBindingSetters", { + files: { + "/shared.js": /* js */ ` + export let counter = 0; + export function increment() { counter++; } + export function getCounter() { return counter; } + `, + "/a.js": /* js */ ` + import { counter, increment, getCounter } from "./shared.js"; + console.log("a before:", counter, getCounter()); + increment(); + console.log("a after:", counter, getCounter()); + `, + "/b.js": /* js */ ` + import { counter, increment, getCounter } from "./shared.js"; + console.log("b before:", counter, getCounter()); + increment(); + console.log("b after:", counter, getCounter()); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a before: 0 0\na after: 1 1" }, + { file: "/out/b.js", stdout: "b before: 0 0\nb after: 1 1" }, + ], + runtimeFiles: { + "/test.js": /* js */ ` + import { counter as c1, increment as inc1 } from "./out/a.js"; + import { counter as c2, increment as inc2 } from "./out/b.js"; + console.log("shared state:", c1, c2); + `, + }, + }); + + // Live binding with setters - CJS output + // Note: In CJS output, the imported `counter` is snapshotted at import time, + // so it won't update when increment() is called. Only getCounter() reflects + // the updated value since it reads the variable at call time. + itBundled("splitting/LiveBindingSettersCJS", { + files: { + "/shared.js": /* js */ ` + export let counter = 0; + export function increment() { counter++; } + export function getCounter() { return counter; } + `, + "/a.js": /* js */ ` + import { counter, increment, getCounter } from "./shared.js"; + console.log("a before:", counter, getCounter()); + increment(); + // counter is snapshotted, getCounter() reads live value + console.log("a after:", counter, getCounter()); + `, + "/b.js": /* js */ ` + import { counter, increment, getCounter } from "./shared.js"; + console.log("b before:", counter, getCounter()); + increment(); + console.log("b after:", counter, getCounter()); + `, + }, + entryPoints: ["/a.js", "/b.js"], + splitting: true, + format: "cjs", + outdir: "/out", + run: [ + { file: "/out/a.js", stdout: "a before: 0 0\na after: 0 1" }, + { file: "/out/b.js", stdout: "b before: 0 0\nb after: 0 1" }, + ], + }); });