| 1 | import * as Bundle from "@/Bundle/Bundle"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { expect, layer } from "alchemy-test"; |
| 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; |
| 6 | import * as Path from "effect/Path"; |
| 7 | |
| 8 | /** |
| 9 | * Rolldown's default `resolve.conditionNames` are import-kind specific |
| 10 | * (`import` for `import` statements, `require` for `require()` calls). An |
| 11 | * explicit list is applied as one set to BOTH kinds, so listing `"import"` |
| 12 | * makes every `require()` also match a package's `"import"` export — and |
| 13 | * `exports` maps resolve in the PACKAGE's key order. `pg-pool` lists |
| 14 | * `import` before `require`, so `pg`'s `require("pg-pool")` received the |
| 15 | * ESM namespace and died with `TypeError: The superclass is not a |
| 16 | * constructor`. {@link Bundle.BUN_CONDITION_NAMES} / |
| 17 | * {@link Bundle.NODE_CONDITION_NAMES} leave the kind to rolldown. |
| 18 | */ |
| 19 | layer(NodeServices.layer)("bundle conditionNames", (it) => { |
| 20 | /** |
| 21 | * A dual package shaped like `pg-pool`: `import` listed first, each |
| 22 | * entry carrying a distinct marker so the bundle shows which one a |
| 23 | * given import kind resolved. |
| 24 | */ |
| 25 | const project = Effect.gen(function* () { |
| 26 | const fs = yield* FileSystem.FileSystem; |
| 27 | const path = yield* Path.Path; |
| 28 | const root = yield* fs.makeTempDirectory({ prefix: "alchemy-conditions-" }); |
| 29 | const pkg = path.join(root, "node_modules", "dual-pkg"); |
| 30 | yield* fs.makeDirectory(pkg, { recursive: true }); |
| 31 | yield* fs.writeFileString( |
| 32 | path.join(pkg, "package.json"), |
| 33 | JSON.stringify({ |
| 34 | name: "dual-pkg", |
| 35 | exports: { ".": { import: "./esm.mjs", require: "./cjs.cjs" } }, |
| 36 | }), |
| 37 | ); |
| 38 | yield* fs.writeFileString( |
| 39 | path.join(pkg, "esm.mjs"), |
| 40 | `export const kind = "DUAL_PKG_ESM_ENTRY";\n`, |
| 41 | ); |
| 42 | yield* fs.writeFileString( |
| 43 | path.join(pkg, "cjs.cjs"), |
| 44 | `module.exports = { kind: "DUAL_PKG_CJS_ENTRY" };\n`, |
| 45 | ); |
| 46 | // A CommonJS consumer (what `pg` is) requiring the dual package… |
| 47 | yield* fs.writeFileString( |
| 48 | path.join(root, "consumer.cjs"), |
| 49 | `module.exports = { viaRequire: require("dual-pkg").kind };\n`, |
| 50 | ); |
| 51 | // …and an ESM entry importing it directly as well. |
| 52 | const entry = path.join(root, "entry.mjs"); |
| 53 | yield* fs.writeFileString( |
| 54 | entry, |
| 55 | `import { kind as viaImport } from "dual-pkg";\nimport consumer from "./consumer.cjs";\nexport default { viaImport, viaRequire: consumer.viaRequire };\n`, |
| 56 | ); |
| 57 | return { root, entry }; |
| 58 | }); |
| 59 | |
| 60 | const bundleWith = (conditionNames: readonly string[]) => |
| 61 | Effect.gen(function* () { |
| 62 | const fs = yield* FileSystem.FileSystem; |
| 63 | const { root, entry } = yield* project; |
| 64 | try { |
| 65 | const result = yield* Bundle.build( |
| 66 | { |
| 67 | cwd: root, |
| 68 | input: entry, |
| 69 | platform: "node", |
| 70 | resolve: { conditionNames: [...conditionNames] }, |
| 71 | }, |
| 72 | { format: "esm" }, |
| 73 | ); |
| 74 | return result.files |
| 75 | .filter((file) => typeof file.content === "string") |
| 76 | .map((file) => file.content as string) |
| 77 | .join("\n"); |
| 78 | } finally { |
| 79 | yield* fs.remove(root, { recursive: true }).pipe(Effect.ignore); |
| 80 | } |
| 81 | }); |
| 82 | |
| 83 | it.effect( |
| 84 | "BUN_CONDITION_NAMES: require() resolves the CJS export, import the ESM one", |
| 85 | () => |
| 86 | Effect.gen(function* () { |
| 87 | const code = yield* bundleWith(Bundle.BUN_CONDITION_NAMES); |
| 88 | expect(code).toContain("DUAL_PKG_CJS_ENTRY"); |
| 89 | expect(code).toContain("DUAL_PKG_ESM_ENTRY"); |
| 90 | }), |
| 91 | { tags: ["unit", "local"] }, |
| 92 | ); |
| 93 | |
| 94 | it.effect( |
| 95 | "NODE_CONDITION_NAMES: require() resolves the CJS export, import the ESM one", |
| 96 | () => |
| 97 | Effect.gen(function* () { |
| 98 | const code = yield* bundleWith(Bundle.NODE_CONDITION_NAMES); |
| 99 | expect(code).toContain("DUAL_PKG_CJS_ENTRY"); |
| 100 | expect(code).toContain("DUAL_PKG_ESM_ENTRY"); |
| 101 | }), |
| 102 | { tags: ["unit", "local"] }, |
| 103 | ); |
| 104 | |
| 105 | // The control: the former list, with `"import"` baked in, hands the CJS |
| 106 | // consumer's require() the ESM entry — the `pg` crash. |
| 107 | it.effect( |
| 108 | "a list containing `import` mis-resolves require() to ESM", |
| 109 | () => |
| 110 | Effect.gen(function* () { |
| 111 | const code = yield* bundleWith(["bun", "import", "module", "default"]); |
| 112 | expect(code).not.toContain("DUAL_PKG_CJS_ENTRY"); |
| 113 | expect(code).toContain("DUAL_PKG_ESM_ENTRY"); |
| 114 | }), |
| 115 | { tags: ["unit", "local"] }, |
| 116 | ); |
| 117 | }); |