diff --git a/.changeset/four-planes-attend.md b/.changeset/four-planes-attend.md new file mode 100644 index 0000000000..f1f6013d2c --- /dev/null +++ b/.changeset/four-planes-attend.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +Fix source phase imports in bundled and non-bundled Workers + +Wrangler now preserves `import source` syntax when it runs esbuild, including module format detection and bundled deploy output. This fixes both `--no-bundle` and bundled deployments for Workers that import WebAssembly using source phase imports. diff --git a/.changeset/source-phase-miniflare.md b/.changeset/source-phase-miniflare.md new file mode 100644 index 0000000000..d7a199d15d --- /dev/null +++ b/.changeset/source-phase-miniflare.md @@ -0,0 +1,7 @@ +--- +"miniflare": patch +--- + +Fix source phase imports parsing in Miniflare + +Miniflare now uses the `acorn-import-phases` plugin to parse `import source` syntax when analyzing module dependencies. This fixes `ERR_MODULE_PARSE` errors when running Workers that use source phase imports for WebAssembly modules in local development. diff --git a/packages/miniflare/package.json b/packages/miniflare/package.json index d8f7d5fff6..ea2c83db6e 100644 --- a/packages/miniflare/package.json +++ b/packages/miniflare/package.json @@ -77,6 +77,7 @@ "@types/which": "^2.0.1", "@types/ws": "^8.5.7", "acorn": "8.14.0", + "acorn-import-phases": "^1.0.4", "acorn-walk": "8.3.2", "capnp-es": "catalog:default", "capnweb": "catalog:default", diff --git a/packages/miniflare/src/plugins/core/modules.ts b/packages/miniflare/src/plugins/core/modules.ts index dc91dc5d38..f68f096576 100644 --- a/packages/miniflare/src/plugins/core/modules.ts +++ b/packages/miniflare/src/plugins/core/modules.ts @@ -4,7 +4,8 @@ import { builtinModules } from "node:module"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { TextDecoder, TextEncoder } from "node:util"; -import { parse } from "acorn"; +import { Parser } from "acorn"; +import importPhases from "acorn-import-phases"; import { simple } from "acorn-walk"; import { dim } from "kleur/colors"; import { z } from "zod"; @@ -14,6 +15,9 @@ import { MatcherRegExps, testRegExps } from "../../workers"; import { getNodeCompat, NodeJSCompatMode } from "./node-compat"; import type estree from "estree"; +const ExtendedParser = Parser.extend(importPhases()); +const parse = ExtendedParser.parse.bind(ExtendedParser); + const SUGGEST_BUNDLE = "If you're trying to import an npm package, you'll need to bundle your Worker first."; const SUGGEST_NODE = diff --git a/packages/miniflare/test/plugins/core/modules.spec.ts b/packages/miniflare/test/plugins/core/modules.spec.ts index cfe8fb256a..1d9a4fa565 100644 --- a/packages/miniflare/test/plugins/core/modules.spec.ts +++ b/packages/miniflare/test/plugins/core/modules.spec.ts @@ -339,3 +339,44 @@ If you're trying to import an npm package, you'll need to bundle your Worker fir /^Unable to resolve "index\.mjs" dependency "node:assert": no matching module rules\.\nIf you're trying to import a Node\.js built-in module, or an npm package that uses Node\.js built-ins, you'll either need to:/ ); }); +test("Miniflare: parses source phase imports without error", async ({ + expect, +}) => { + const tmp = await useTmp(); + const wasmPath = path.join(tmp, "module.wasm"); + const workerPath = path.join(tmp, "index.mjs"); + + // Create a minimal wasm file + await fs.writeFile( + wasmPath, + Buffer.from([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]) + ); + + // Create a worker that uses source phase import syntax + await fs.writeFile( + workerPath, + `import source wasmModule from "./module.wasm"; +export default { + async fetch() { + return new Response("source phase import parsed successfully"); + } +};` + ); + + // Verify the worker can be loaded without parse errors + // Note: workerd doesn't actually support source phase imports at runtime, + // but we need to ensure the parser doesn't fail on the syntax + const mf = new Miniflare({ + modules: true, + modulesRoot: tmp, + modulesRules: [{ type: "CompiledWasm", include: ["**/*.wasm"] }], + compatibilityDate: "2023-08-01", + scriptPath: workerPath, + }); + useDispose(mf); + + // The worker should be able to load (even if the source phase import + // is not fully functional at runtime) + const res = await mf.dispatchFetch("http://localhost"); + expect(await res.text()).toBe("source phase import parsed successfully"); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4014f4b830..9808b15e44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2105,6 +2105,9 @@ importers: acorn: specifier: 8.14.0 version: 8.14.0 + acorn-import-phases: + specifier: ^1.0.4 + version: 1.0.4(acorn@8.14.0) acorn-walk: specifier: 8.3.2 version: 8.3.2 @@ -9052,6 +9055,12 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -19906,6 +19915,10 @@ snapshots: mime-types: 3.0.1 negotiator: 1.0.0 + acorn-import-phases@1.0.4(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0