// require() of an ES module (sketches/require-esm.md). The goldens under // tests/goldens/guests/require-esm are what Node v24.16 printed for the guests in // tests/fixtures/guests/require-esm; each must match or be a known divergence, // asserted as what the kernel prints instead. import { readFileSync } from "node:fs"; import { test, expect, type Page } from "@playwright/test"; import type { Kernel } from "../src/host/index"; import { listGoldens, readGolden } from "../scripts/golden-files.ts"; import { goldenTest, runGoldenGuest } from "./guest"; import { cacheTarballs } from "./tarball-cache"; declare global { interface Window { __runtime?: Kernel } } const DIVERGES: Record = { "require-esm/esm-cycle.cjs": { why: "a named import read inside a cycle before its exporter ran is undefined, not a TDZ ReferenceError", stdout: '["A","B","A"] undefined:undefined\n', }, "require-esm/live-binding.cjs": { why: "a named import is a snapshot taken as the graph evaluates, so its exporter's later reassignment is not seen", stdout: '[[0,"f"],[0,"f"]]\n', }, "require-esm/strict-write.cjs": { why: "the namespace is a Proxy, so a strict-mode write throws V8's Proxy message", stdout: "TypeError 'set' on proxy: trap returned falsish for property 'n'\n", }, }; const guests = listGoldens("guests", "require-esm/"); test("every listed guest has a golden", () => { expect(Object.keys(DIVERGES).filter((g) => !guests.includes(g))).toEqual([]); }); for (const guest of guests) { const g = readGolden("guests", guest); const diverges = DIVERGES[guest]; if (diverges === undefined) goldenTest(g); else { test(`golden ${guest} diverges: ${diverges.why}`, async ({ page }) => { const out = await runGoldenGuest(page, g); expect(out.stdout).not.toBe(g.stdout); expect(out).toEqual({ code: g.code, signal: null, stdout: diverges.stdout, stderr: g.stderr }); }); } } /** `npm ci` from the fixture's lockfile in /app, then `node ` there. */ async function installAndRun(page: Page, dir: string, entry: string): Promise<{ code: number; stdout: string; stderr: string }> { await page.goto("/demo/fixture.html"); await page.waitForFunction(() => window.__runtime !== undefined, undefined, { timeout: 20_000 }); const files = Object.fromEntries(["package.json", "package-lock.json", entry].map((f) => [`/app/${f}`, readFileSync(`${dir}/${f}`, "utf8")])); return page.evaluate(async ({ files, entry }) => { const runtime = window.__runtime!; runtime.writeTree(files); await runtime.install(); const proc = runtime.exec(`/app/${entry}`, { cwd: "/app" }); const dec = new TextDecoder(); let stdout = ""; let stderr = ""; await Promise.all([ proc.stdout.pipeTo(new WritableStream({ write: (c) => { stdout += dec.decode(c); } })), proc.stderr.pipeTo(new WritableStream({ write: (c) => { stderr += dec.decode(c); } })), proc.exited, ]); return { code: await proc.exited, stdout, stderr }; }, { files, entry }); } test.describe("rolldown's wasm32-wasi binding", () => { test.beforeEach(async ({ context }) => cacheTarballs(context)); // tests/fixtures/require-esm-emnapi pins the graph rolldown's binding loads: // @napi-rs/wasm-runtime's runtime.cjs requires @emnapi/core, which is ESM only at // 2.0.0-beta.2. node.txt is `npm ci && node main.cjs` there under Node v24.16. test("require('@napi-rs/wasm-runtime') loads @emnapi/core v2 as Node does", async ({ page }) => { const dir = "tests/fixtures/require-esm-emnapi"; expect(await installAndRun(page, dir, "main.cjs")).toEqual({ code: 0, stdout: readFileSync(`${dir}/node.txt`, "utf8"), stderr: "" }); }); // The installer adds @rolldown/binding-wasm32-wasi, which rolldown's loader falls // back to. node.txt is what Node v24.16 printed for main.mjs with only that binding // installed and NAPI_RS_FORCE_WASI=1; the lockfile is npm 11.7's. test("rolldown parses and bundles through its wasm binding as Node does", async ({ page }) => { test.setTimeout(120_000); const dir = "tests/fixtures/rolldown-wasi"; const res = await installAndRun(page, dir, "main.mjs"); const warning = "(node:PID) ExperimentalWarning: WASI is an experimental feature and might change at any time\n" + "(Use `node --trace-warnings ...` to show where the warning was created)\n"; expect({ ...res, stderr: res.stderr.replace(/\(node:\d+\)/g, "(node:PID)") }) .toEqual({ code: 0, stdout: readFileSync(`${dir}/node.txt`, "utf8"), stderr: warning + warning }); }); });