From 00b4c2cbaf155451244f4b57c4b83dbb4952bd93 Mon Sep 17 00:00:00 2001 From: Jonathan Stockdill Date: Sat, 18 Jul 2026 11:06:46 +0000 Subject: [PATCH] fix(cli): #1723 gate import.meta.url transform on js content-type so wasm skips acorn --- packages/cli/src/config/rollup.config.js | 11 ++- .../build.default.wasm-import-invalid.spec.js | 67 +++++++++++++ .../src/pages/index.html | 11 +++ .../src/scripts/add.wasm | Bin 0 -> 41 bytes .../src/scripts/main.js | 3 + ...build.default.wasm-import-meta-url.spec.js | 91 ++++++++++++++++++ .../src/pages/index.html | 11 +++ .../src/scripts/add.wasm | Bin 0 -> 41 bytes .../src/scripts/main.js | 4 + 9 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 packages/cli/test/cases/build.default.wasm-import-invalid/build.default.wasm-import-invalid.spec.js create mode 100644 packages/cli/test/cases/build.default.wasm-import-invalid/src/pages/index.html create mode 100644 packages/cli/test/cases/build.default.wasm-import-invalid/src/scripts/add.wasm create mode 100644 packages/cli/test/cases/build.default.wasm-import-invalid/src/scripts/main.js create mode 100644 packages/cli/test/cases/build.default.wasm-import-meta-url/build.default.wasm-import-meta-url.spec.js create mode 100644 packages/cli/test/cases/build.default.wasm-import-meta-url/src/pages/index.html create mode 100644 packages/cli/test/cases/build.default.wasm-import-meta-url/src/scripts/add.wasm create mode 100644 packages/cli/test/cases/build.default.wasm-import-meta-url/src/scripts/main.js diff --git a/packages/cli/src/config/rollup.config.js b/packages/cli/src/config/rollup.config.js index 78230a163..f3bd34e91 100644 --- a/packages/cli/src/config/rollup.config.js +++ b/packages/cli/src/config/rollup.config.js @@ -320,7 +320,12 @@ function greenwoodImportMetaUrl(compilation) { } } - if (!canTransform) { + // only parse resources that were actually served as JavaScript, otherwise binary resources + // (e.g. .wasm / .png / .woff2) get fed to acorn and crash the build with a cryptic SyntaxError + // https://github.com/ProjectEvergreen/greenwood/issues/1723 + const contentType = response.headers.get("content-type") || ""; + + if (!canTransform || !contentType.includes("text/javascript")) { return null; } @@ -363,7 +368,9 @@ function greenwoodImportMetaUrl(compilation) { if (plugin.shouldServe && (await plugin.shouldServe(url, request))) { const response = await plugin.serve(url, request); - if (response?.headers?.get("content-type") || "".indexOf("text/javascript") >= 0) { + // parenthesize so we test the content-type string contains "text/javascript" + // https://github.com/ProjectEvergreen/greenwood/issues/1723 + if ((response?.headers?.get("content-type") || "").indexOf("text/javascript") >= 0) { bundleExtensions = [...bundleExtensions, ...plugin.extensions]; } } diff --git a/packages/cli/test/cases/build.default.wasm-import-invalid/build.default.wasm-import-invalid.spec.js b/packages/cli/test/cases/build.default.wasm-import-invalid/build.default.wasm-import-invalid.spec.js new file mode 100644 index 000000000..38dbd2994 --- /dev/null +++ b/packages/cli/test/cases/build.default.wasm-import-invalid/build.default.wasm-import-invalid.spec.js @@ -0,0 +1,67 @@ +/* + * Use Case + * Run Greenwood build command with an unsupported direct `import ... from "./add.wasm"`. + * + * User Result + * The build should fail, but NOT with the misleading acorn `SyntaxError` thrown from inside the + * `greenwood-import-meta-url` Rollup plugin (which pointed users at internal plugin code when the + * wasm binary was fed to the JavaScript parser). A clear, Rollup-native parse error is acceptable. + * + * User Command + * greenwood build + * + * User Config + * None (Greenwood Default) + * + * User Workspace + * src/ + * pages/ + * index.html + * scripts/ + * add.wasm + * main.js + */ +import { expect } from "chai"; +import path from "node:path"; +import { getOutputTeardownFiles } from "../../../../../test/utils.js"; +import { Runner } from "gallinago"; +import { fileURLToPath } from "node:url"; + +describe("Build Greenwood With: ", function () { + const cliPath = path.join(process.cwd(), "packages/cli/src/bin.js"); + const outputPath = fileURLToPath(new URL(".", import.meta.url)); + let runner; + let buildError; + + before(function () { + this.context = { + publicDir: path.join(outputPath, "public"), + }; + runner = new Runner(); + }); + + describe("An unsupported direct import of a .wasm module", function () { + before(async function () { + await runner.setup(outputPath); + + try { + await runner.runCommand(cliPath, "build"); + } catch (error) { + buildError = `${error}`; + } + }); + + it("should fail the build", function () { + expect(buildError).to.not.equal(undefined); + }); + + // https://github.com/ProjectEvergreen/greenwood/issues/1723 + it("should not crash with the acorn SyntaxError from the greenwood-import-meta-url plugin", function () { + expect(buildError).to.not.contain("greenwood-import-meta-url"); + }); + }); + + after(async function () { + await runner.teardown(getOutputTeardownFiles(outputPath)); + }); +}); diff --git a/packages/cli/test/cases/build.default.wasm-import-invalid/src/pages/index.html b/packages/cli/test/cases/build.default.wasm-import-invalid/src/pages/index.html new file mode 100644 index 000000000..4e1599b21 --- /dev/null +++ b/packages/cli/test/cases/build.default.wasm-import-invalid/src/pages/index.html @@ -0,0 +1,11 @@ + + + + wasm direct import + + + +

wasm

+ + + diff --git a/packages/cli/test/cases/build.default.wasm-import-invalid/src/scripts/add.wasm b/packages/cli/test/cases/build.default.wasm-import-invalid/src/scripts/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/packages/cli/test/cases/build.default.wasm-import-invalid/src/scripts/main.js b/packages/cli/test/cases/build.default.wasm-import-invalid/src/scripts/main.js new file mode 100644 index 000000000..a3b6f8354 --- /dev/null +++ b/packages/cli/test/cases/build.default.wasm-import-invalid/src/scripts/main.js @@ -0,0 +1,3 @@ +import * as wasm from "./add.wasm"; + +document.getElementById("out").textContent = `add(2,3)=${wasm.add(2, 3)}`; diff --git a/packages/cli/test/cases/build.default.wasm-import-meta-url/build.default.wasm-import-meta-url.spec.js b/packages/cli/test/cases/build.default.wasm-import-meta-url/build.default.wasm-import-meta-url.spec.js new file mode 100644 index 000000000..408cf9d14 --- /dev/null +++ b/packages/cli/test/cases/build.default.wasm-import-meta-url/build.default.wasm-import-meta-url.spec.js @@ -0,0 +1,91 @@ +/* + * Use Case + * Run Greenwood build command referencing a .wasm asset via the supported + * `new URL("./add.wasm", import.meta.url)` pattern. + * + * User Result + * Should generate a Greenwood build that emits the .wasm as a content-hashed, + * byte-identical asset, without crashing the JavaScript parser on the binary. + * + * User Command + * greenwood build + * + * User Config + * None (Greenwood Default) + * + * User Workspace + * src/ + * pages/ + * index.html + * scripts/ + * add.wasm + * main.js + */ +import { expect } from "chai"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { runSmokeTest } from "../../../../../test/smoke-test.js"; +import { getOutputTeardownFiles } from "../../../../../test/utils.js"; +import { Runner } from "gallinago"; +import { fileURLToPath } from "node:url"; + +// https://github.com/ProjectEvergreen/greenwood/issues/1723 +describe("Build Greenwood With: ", function () { + const LABEL = "Referencing a .wasm asset with new URL and import.meta.url"; + const cliPath = path.join(process.cwd(), "packages/cli/src/bin.js"); + const outputPath = fileURLToPath(new URL(".", import.meta.url)); + let runner; + + before(function () { + this.context = { + publicDir: path.join(outputPath, "public"), + }; + runner = new Runner(); + }); + + describe(LABEL, function () { + before(async function () { + await runner.setup(outputPath); + await runner.runCommand(cliPath, "build"); + }); + + runSmokeTest(["public"], LABEL); + + describe("WASM asset output", function () { + let wasmFiles; + + before(async function () { + wasmFiles = await Array.fromAsync( + fs.glob("add.*.wasm", { cwd: new URL("./public/", import.meta.url) }), + ); + }); + + it("should emit exactly one content-hashed .wasm asset", function () { + expect(wasmFiles.length).to.equal(1); + }); + + it("should emit the .wasm asset byte-identical to the source", async function () { + const emitted = await fs.readFile(new URL(`./public/${wasmFiles[0]}`, import.meta.url)); + const source = await fs.readFile(new URL("./src/scripts/add.wasm", import.meta.url)); + + expect(emitted.equals(source)).to.equal(true); + }); + + it("should reference the content-hashed .wasm filename from the bundled script", async function () { + const scripts = await Array.fromAsync( + fs.glob("main.*.js", { cwd: new URL("./public/", import.meta.url) }), + ); + const contents = await fs.readFile( + new URL(`./public/${scripts[0]}`, import.meta.url), + "utf-8", + ); + + expect(contents).to.contain(wasmFiles[0]); + }); + }); + }); + + after(async function () { + await runner.teardown(getOutputTeardownFiles(outputPath)); + }); +}); diff --git a/packages/cli/test/cases/build.default.wasm-import-meta-url/src/pages/index.html b/packages/cli/test/cases/build.default.wasm-import-meta-url/src/pages/index.html new file mode 100644 index 000000000..8ec2f1ff0 --- /dev/null +++ b/packages/cli/test/cases/build.default.wasm-import-meta-url/src/pages/index.html @@ -0,0 +1,11 @@ + + + + wasm import.meta.url + + + +

wasm

+ + + diff --git a/packages/cli/test/cases/build.default.wasm-import-meta-url/src/scripts/add.wasm b/packages/cli/test/cases/build.default.wasm-import-meta-url/src/scripts/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/packages/cli/test/cases/build.default.wasm-import-meta-url/src/scripts/main.js b/packages/cli/test/cases/build.default.wasm-import-meta-url/src/scripts/main.js new file mode 100644 index 000000000..7043f6a63 --- /dev/null +++ b/packages/cli/test/cases/build.default.wasm-import-meta-url/src/scripts/main.js @@ -0,0 +1,4 @@ +const wasmUrl = new URL("./add.wasm", import.meta.url); +const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmUrl)); + +document.getElementById("out").textContent = `add(2,3)=${instance.exports.add(2, 3)}`;