From e927f9ce6b9374ce884c75c504c53f91016a2815 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Fri, 30 Oct 2026 16:53:31 +0800 Subject: [PATCH 1/2] fix(pack): align SWC helpers for named RegExp groups --- .github/workflows/pack-integration.yml | 3 + packages/pack/package.json | 2 +- packages/pack/src/__test__/swcHelpers.test.ts | 89 +++++++++++++++++ packages/pack/src/__test__/swcHelpersChild.ts | 97 +++++++++++++++++++ 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 packages/pack/src/__test__/swcHelpers.test.ts create mode 100644 packages/pack/src/__test__/swcHelpersChild.ts diff --git a/.github/workflows/pack-integration.yml b/.github/workflows/pack-integration.yml index 3068b24b3..0e06e5ed6 100644 --- a/.github/workflows/pack-integration.yml +++ b/.github/workflows/pack-integration.yml @@ -83,6 +83,9 @@ jobs: # Exercise the generated NAPI loader on the minimum supported Node major. NAPI_RS_ENFORCE_VERSION_CHECK=1 node -e 'require("./packages/pack/src/binding.js")' + # SWC-generated imports must resolve to compatible runtime helpers. + npm test --workspace @utoo/pack -- src/__test__/swcHelpers.test.ts + # Regression coverage for independent PostCSS and loader evaluator graphs sharing one # build-time runtime (utooland/utoo#3316). npm test --workspace shared-runtime-async-module diff --git a/packages/pack/package.json b/packages/pack/package.json index 09c48c22b..2e8f1a15d 100644 --- a/packages/pack/package.json +++ b/packages/pack/package.json @@ -38,7 +38,7 @@ "@hono/node-server": "^1.19.11", "@hono/node-ws": "^1.3.0", "@jridgewell/trace-mapping": "^0.3.31", - "@swc/helpers": "0.5.15", + "@swc/helpers": "0.5.23", "@utoo/pack-shared": "*", "browserslist": "^4.28.5", "domparser-rs": "^0.0.7", diff --git a/packages/pack/src/__test__/swcHelpers.test.ts b/packages/pack/src/__test__/swcHelpers.test.ts new file mode 100644 index 000000000..26906771d --- /dev/null +++ b/packages/pack/src/__test__/swcHelpers.test.ts @@ -0,0 +1,89 @@ +import { spawn } from "child_process"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { describe, expect, it } from "vitest"; + +const testDir = path.dirname(fileURLToPath(import.meta.url)); +const packageRoot = path.resolve(testDir, "../.."); +const repoRoot = path.resolve(packageRoot, "../.."); +const resultPrefix = "__SWC_HELPERS__"; + +// Keep native worker threads and shutdown isolated from the Vitest process. +function runFixture(): Promise { + const targetDir = path.join(repoRoot, "target"); + fs.mkdirSync(targetDir, { recursive: true }); + const projectPath = fs.mkdtempSync(path.join(targetDir, "swc-helpers-")); + + return new Promise((resolve, reject) => { + const child = spawn( + process.execPath, + [ + path.join(repoRoot, "node_modules/vite-node/vite-node.mjs"), + path.join(testDir, "swcHelpersChild.ts"), + projectPath, + ], + { + cwd: packageRoot, + env: { + ...process.env, + NODE_PATH: [ + path.join(packageRoot, "node_modules"), + path.join(repoRoot, "node_modules"), + process.env.NODE_PATH, + ] + .filter(Boolean) + .join(path.delimiter), + }, + stdio: ["ignore", "pipe", "pipe"], + }, + ); + let stdout = ""; + let stderr = ""; + child.stdout.on("data", (chunk) => { + stdout += chunk; + }); + child.stderr.on("data", (chunk) => { + stderr += chunk; + }); + const timeout = setTimeout(() => child.kill("SIGKILL"), 50_000); + const cleanup = () => { + clearTimeout(timeout); + fs.rmSync(projectPath, { recursive: true, force: true }); + }; + child.on("error", (error) => { + cleanup(); + reject(error); + }); + child.on("close", (code, signal) => { + try { + if (code !== 0) { + throw new Error( + `SWC helpers fixture failed (${code}, ${signal})\n${stderr}\n${stdout}`, + ); + } + const line = stdout + .split(/\r?\n/) + .find((item) => item.startsWith(resultPrefix)); + if (!line) + throw new Error(`Missing fixture result\n${stderr}\n${stdout}`); + resolve(JSON.parse(line.slice(resultPrefix.length))); + } catch (error) { + reject(error); + } finally { + cleanup(); + } + }); + }); +} + +describe("SWC runtime helpers", () => { + it("preserves named RegExp groups when compiling for older browsers", async () => { + await expect(runFixture()).resolves.toEqual({ + word: "hello", + matched: "hello-hello", + rejectsDifferentWord: true, + replacement: "[hello]", + }); + }, 60_000); +}); diff --git a/packages/pack/src/__test__/swcHelpersChild.ts b/packages/pack/src/__test__/swcHelpersChild.ts new file mode 100644 index 000000000..d801e1bbc --- /dev/null +++ b/packages/pack/src/__test__/swcHelpersChild.ts @@ -0,0 +1,97 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import vm from "node:vm"; + +const projectPath = process.argv[2]; +if (!projectPath || !path.isAbsolute(projectPath)) { + throw new Error("An absolute fixture directory is required"); +} +const repoRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "../../../..", +); + +type RegExpResult = { + word: string; + matched: string; + rejectsDifferentWord: boolean; + replacement: string; +}; + +async function main() { + // The override belongs to pack's addon only. HtmlPlugin's domparser-rs + // dependency must load its own native library. + if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { + await import("../binding"); + delete process.env.NAPI_RS_NATIVE_LIBRARY_PATH; + } + const { build } = await import("../commands/build"); + const sourceDir = path.join(projectPath, "src"); + const outputDir = path.join(projectPath, "dist"); + fs.mkdirSync(sourceDir, { recursive: true }); + fs.writeFileSync( + path.join(sourceDir, "regexp-dependency.js"), + `export function inspectRegExp(value) { + const pattern = /^(?[a-z]+)-\\k$/; + const match = pattern.exec(value); + return { + word: match.groups.word, + matched: match[0], + rejectsDifferentWord: !pattern.test("hello-world"), + replacement: value.replace(pattern, "[$]"), + }; +} +`, + ); + fs.writeFileSync( + path.join(sourceDir, "index.js"), + 'export { inspectRegExp } from "./regexp-dependency.js";\n', + ); + + await build( + { + buildId: "swc-helpers-test", + tracing: false, + config: { + mode: "production", + target: "chrome 49", + entry: [ + { + name: "index", + import: "./src/index.js", + library: { name: "SwcHelpersRegression" }, + }, + ], + output: { path: outputDir, filename: "index.js", clean: true }, + sourceMaps: false, + persistentCaching: false, + optimization: { minify: false }, + }, + }, + projectPath, + repoRoot, + ); + + const filename = path.join(outputDir, "index.js"); + const body = fs.readFileSync(filename, "utf8"); + // Modern Node supports the original syntax. Require an emitted helper so + // native RegExp support cannot hide a missing compatibility transform. + assert.ok(body.includes("_wrap_reg_exp"), "Missing emitted RegExp helper"); + const context: { + URL: typeof URL; + self?: unknown; + SwcHelpersRegression?: { inspectRegExp: (value: string) => RegExpResult }; + } = { URL }; + context.self = context; + vm.runInNewContext(body, context, { filename, timeout: 1000 }); + assert.ok(context.SwcHelpersRegression, "Missing library exports"); + const result = context.SwcHelpersRegression.inspectRegExp("hello-hello"); + console.log(`__SWC_HELPERS__${JSON.stringify(result)}`); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); From 1e28634fb9b44c32c23af5245fa959c2fa2a1232 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Fri, 30 Oct 2026 16:56:06 +0800 Subject: [PATCH 2/2] test(pack): keep SWC helper regression in the test suite --- .github/workflows/pack-integration.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/pack-integration.yml b/.github/workflows/pack-integration.yml index 0e06e5ed6..3068b24b3 100644 --- a/.github/workflows/pack-integration.yml +++ b/.github/workflows/pack-integration.yml @@ -83,9 +83,6 @@ jobs: # Exercise the generated NAPI loader on the minimum supported Node major. NAPI_RS_ENFORCE_VERSION_CHECK=1 node -e 'require("./packages/pack/src/binding.js")' - # SWC-generated imports must resolve to compatible runtime helpers. - npm test --workspace @utoo/pack -- src/__test__/swcHelpers.test.ts - # Regression coverage for independent PostCSS and loader evaluator graphs sharing one # build-time runtime (utooland/utoo#3316). npm test --workspace shared-runtime-async-module