From 9a0d6434a171a00685ba1903491c1af1d293df5a Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Fri, 20 Mar 2026 20:42:30 -0400 Subject: [PATCH 1/4] [wrangler] Fix no-bundle source phase imports --- .changeset/four-planes-attend.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/four-planes-attend.md diff --git a/.changeset/four-planes-attend.md b/.changeset/four-planes-attend.md new file mode 100644 index 0000000000..5c7a19d14a --- /dev/null +++ b/.changeset/four-planes-attend.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +Fix source phase imports in non-bundled Workers + +Wrangler now preserves `import source` syntax when it runs esbuild in non-bundling paths such as module format detection. This fixes `--no-bundle` deployments for Workers that import WebAssembly using source phase imports. From b416df87b7cd68ecae9c21f6d9ca527e88b56334 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Fri, 20 Mar 2026 20:49:03 -0400 Subject: [PATCH 2/4] [wrangler] Support source phase imports --- .changeset/four-planes-attend.md | 4 ++-- packages/wrangler/src/__tests__/deploy/formats.test.ts | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.changeset/four-planes-attend.md b/.changeset/four-planes-attend.md index 5c7a19d14a..f1f6013d2c 100644 --- a/.changeset/four-planes-attend.md +++ b/.changeset/four-planes-attend.md @@ -2,6 +2,6 @@ "wrangler": patch --- -Fix source phase imports in non-bundled Workers +Fix source phase imports in bundled and non-bundled Workers -Wrangler now preserves `import source` syntax when it runs esbuild in non-bundling paths such as module format detection. This fixes `--no-bundle` deployments for Workers that import WebAssembly using source phase imports. +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/packages/wrangler/src/__tests__/deploy/formats.test.ts b/packages/wrangler/src/__tests__/deploy/formats.test.ts index 0c14853598..7f61fc79dd 100644 --- a/packages/wrangler/src/__tests__/deploy/formats.test.ts +++ b/packages/wrangler/src/__tests__/deploy/formats.test.ts @@ -54,7 +54,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("@cloudflare/cli/command"); +vi.mock("../../autoconfig/c3-vendor/command"); describe("deploy", () => { mockAccountId(); @@ -963,7 +963,9 @@ export default { expect(fs.existsSync("some-dir/index.js")).toBe(true); expect(fs.existsSync("some-dir/index.js.map")).toBe(true); - expect(fs.readFileSync("some-dir/index.js", "utf8")).toContain( + expect( + fs.readFileSync("some-dir/index.js", "utf8") + ).toContain( 'import source hello from "./d025a03cd31e98e96fb5bd5bce87f9bca4e8ce2c-hello.wasm";' ); expect( @@ -1147,7 +1149,9 @@ export default { import source hello from "./d025a03cd31e98e96fb5bd5bce87f9bca4e8ce2c-hello.wasm"; ` ); - expect(fs.readFileSync("some-dir/worker.bundle", "utf8")).toContain( + expect( + fs.readFileSync("some-dir/worker.bundle", "utf8") + ).toContain( 'Content-Disposition: form-data; name="./d025a03cd31e98e96fb5bd5bce87f9bca4e8ce2c-hello.wasm"; filename="./d025a03cd31e98e96fb5bd5bce87f9bca4e8ce2c-hello.wasm"' ); }); From f406687335a27e5e20b935d035c0c95bb6c43b88 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 23 Mar 2026 11:31:35 -0700 Subject: [PATCH 3/4] fix formatting --- packages/wrangler/src/__tests__/deploy/formats.test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/wrangler/src/__tests__/deploy/formats.test.ts b/packages/wrangler/src/__tests__/deploy/formats.test.ts index 7f61fc79dd..1779c29902 100644 --- a/packages/wrangler/src/__tests__/deploy/formats.test.ts +++ b/packages/wrangler/src/__tests__/deploy/formats.test.ts @@ -963,9 +963,7 @@ export default { expect(fs.existsSync("some-dir/index.js")).toBe(true); expect(fs.existsSync("some-dir/index.js.map")).toBe(true); - expect( - fs.readFileSync("some-dir/index.js", "utf8") - ).toContain( + expect(fs.readFileSync("some-dir/index.js", "utf8")).toContain( 'import source hello from "./d025a03cd31e98e96fb5bd5bce87f9bca4e8ce2c-hello.wasm";' ); expect( @@ -1149,9 +1147,7 @@ export default { import source hello from "./d025a03cd31e98e96fb5bd5bce87f9bca4e8ce2c-hello.wasm"; ` ); - expect( - fs.readFileSync("some-dir/worker.bundle", "utf8") - ).toContain( + expect(fs.readFileSync("some-dir/worker.bundle", "utf8")).toContain( 'Content-Disposition: form-data; name="./d025a03cd31e98e96fb5bd5bce87f9bca4e8ce2c-hello.wasm"; filename="./d025a03cd31e98e96fb5bd5bce87f9bca4e8ce2c-hello.wasm"' ); }); From 4f37431a3e2ab62058f85fe4d1f319dacc35d39a Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 1 Apr 2026 18:12:37 -0700 Subject: [PATCH 4/4] [miniflare] Add support for parsing source phase imports Fixes module parsing to support the TC39 source phase imports proposal (`import source wasm from './module.wasm'`), which is required for WebAssembly ESM integration. Follow-up to #12996 which added wrangler support. Part of #12995. Changes: - Add acorn-import-phases dependency to parse import source syntax - Extend acorn Parser with the import phases plugin in modules.ts - Add test for source phase imports parsing --- .changeset/source-phase-miniflare.md | 7 ++++ packages/miniflare/package.json | 1 + .../miniflare/src/plugins/core/modules.ts | 6 ++- .../test/plugins/core/modules.spec.ts | 41 +++++++++++++++++++ .../src/__tests__/deploy/formats.test.ts | 2 +- pnpm-lock.yaml | 13 ++++++ 6 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 .changeset/source-phase-miniflare.md 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/packages/wrangler/src/__tests__/deploy/formats.test.ts b/packages/wrangler/src/__tests__/deploy/formats.test.ts index 1779c29902..0c14853598 100644 --- a/packages/wrangler/src/__tests__/deploy/formats.test.ts +++ b/packages/wrangler/src/__tests__/deploy/formats.test.ts @@ -54,7 +54,7 @@ vi.mock("../../package-manager", async (importOriginal) => ({ vi.mock("../../autoconfig/run"); vi.mock("../../autoconfig/frameworks/utils/packages"); -vi.mock("../../autoconfig/c3-vendor/command"); +vi.mock("@cloudflare/cli/command"); describe("deploy", () => { mockAccountId(); 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