From 2df6b51ebb87e8d406d6201d8f3158aefefa7cc5 Mon Sep 17 00:00:00 2001 From: Rudy Mizrahi Celekli <47457359+rudycelekli@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:05:43 -0400 Subject: [PATCH] fix(replay): preserve CommonJS object as ESM default export (#519) Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> --- src/replay/JavaScriptReplayWorker.ts | 2 +- .../replay/javascriptReplayWorker.test.ts | 108 +++++++++++++++++- 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/src/replay/JavaScriptReplayWorker.ts b/src/replay/JavaScriptReplayWorker.ts index 3b9df270c..715659e5b 100644 --- a/src/replay/JavaScriptReplayWorker.ts +++ b/src/replay/JavaScriptReplayWorker.ts @@ -226,7 +226,7 @@ const loadEntry = async ( function () { this.setExport("default", exports); for (const name of Object.keys(exports)) - this.setExport(name, exports[name]); + if (name !== "default") this.setExport(name, exports[name]); }, { context, identifier: `rea:${alias}` }, ); diff --git a/tests/boundary/providers/replay/javascriptReplayWorker.test.ts b/tests/boundary/providers/replay/javascriptReplayWorker.test.ts index 5a2f2db89..042911c06 100644 --- a/tests/boundary/providers/replay/javascriptReplayWorker.test.ts +++ b/tests/boundary/providers/replay/javascriptReplayWorker.test.ts @@ -1,9 +1,12 @@ -import { spawn } from "node:child_process"; -import { readFile } from "node:fs/promises"; -import { resolve } from "node:path"; +import { execFile, spawn } from "node:child_process"; +import { readFile, writeFile } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import { pathToFileURL } from "node:url"; +import { promisify } from "node:util"; import { describe, expect, it } from "vitest"; +import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js"; import type { WorkerModule } from "../../../../src/replay/JavaScriptReplayWorkerTypes.js"; const fixture = async (name: string): Promise => @@ -228,6 +231,105 @@ describe("disposable JavaScript replay worker", () => { }); }); +describe("ESM imports of CommonJS replay factories", () => { + it.each([false, true])( + "matches native Node default import when __esModule is %s", + async (marked) => { + const directory = await createTestTempDirectory("rea-replay-interop-"); + const source = `module.exports={default:'named-default',answer:42,__esModule:${String(marked)}};`; + const path = join(directory, "dependency.cjs"); + await writeFile(path, source, "utf8"); + const { stdout } = await promisify(execFile)( + process.execPath, + [ + "--input-type=module", + "-e", + "const {default:value}=await import(process.argv[1]); console.log(JSON.stringify({kind:typeof value,property:value.default,answer:value.answer}));", + pathToFileURL(path).href, + ], + { timeout: 5000 }, + ); + const expected: unknown = JSON.parse(stdout); + expect(expected).toEqual({ + kind: "object", + property: "named-default", + answer: 42, + }); + const workerRequest = await request("parser.mjs", "esm", "default", []); + workerRequest.left.modules = [ + { + alias: "entry", + format: "esm", + dependencies: { "./dependency": "dependency" }, + source: + "import value from './dependency'; export default function(){ return {kind:typeof value,property:value.default,answer:value.answer}; }", + }, + { + alias: "dependency", + format: "commonjs-factory", + dependencies: {}, + source: `function(module){ ${source} }`, + }, + ]; + const result = await runWorker(workerRequest); + expect(result.code).toBe(0); + expect(JSON.parse(result.stdout)).toMatchObject({ + left: [{ outcome: "return", value: expected }], + }); + }, + ); + + it.each([false, true])( + "preserves Rspack require.n behavior when __esModule is %s", + async (marked) => { + const workerRequest = await request( + "clipboard.factory.txt", + "commonjs-factory", + "default", + [], + ); + workerRequest.left.modules = [ + { + alias: "entry", + format: "commonjs-factory", + dependencies: { "./dependency": "dependency" }, + source: + "function(module,exports,require){ const normalized=require.n(require('./dependency'))(); module.exports.default=()=>typeof normalized; }", + }, + { + alias: "dependency", + format: "commonjs-factory", + dependencies: {}, + source: `function(module){ module.exports={default:'named-default',answer:42,__esModule:${String(marked)}}; }`, + }, + ]; + const result = await runWorker(workerRequest); + expect(JSON.parse(result.stdout)).toMatchObject({ + left: [{ outcome: "return", value: marked ? "string" : "object" }], + }); + }, + ); + + it("preserves direct factory default-export selection", async () => { + const workerRequest = await request( + "clipboard.factory.txt", + "commonjs-factory", + "default", + [], + ); + workerRequest.left.modules[0] = { + alias: "entry", + format: "commonjs-factory", + dependencies: {}, + source: "function(module){ module.exports={default:()=>42}; }", + }; + const result = await runWorker(workerRequest); + expect(JSON.parse(result.stdout)).toMatchObject({ + left: [{ outcome: "return", value: 42 }], + }); + }); +}); + describe("replay ESM graph linking", () => { const esmModule = ( alias: string,