From d568e2229c14100cd1630869aee008b4391be3bf Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Mon, 10 Aug 2026 21:50:09 +0100 Subject: [PATCH] Add module fallback redirect reproduction --- samples/module-fallback-redirect/README.md | 45 +++++++++++ samples/module-fallback-redirect/config.capnp | 19 +++++ samples/module-fallback-redirect/fallback.mjs | 76 +++++++++++++++++++ samples/module-fallback-redirect/worker.js | 15 ++++ 4 files changed, 155 insertions(+) create mode 100644 samples/module-fallback-redirect/README.md create mode 100644 samples/module-fallback-redirect/config.capnp create mode 100644 samples/module-fallback-redirect/fallback.mjs create mode 100644 samples/module-fallback-redirect/worker.js diff --git a/samples/module-fallback-redirect/README.md b/samples/module-fallback-redirect/README.md new file mode 100644 index 00000000000..1ebf366ed3c --- /dev/null +++ b/samples/module-fallback-redirect/README.md @@ -0,0 +1,45 @@ +# Module Fallback Redirect Reproduction + +This reproduces a V2 module fallback failure involving a redirected module that +performs a dynamic import. It does not use Vite or Workers SDK. + +Start the fallback service from the repository root: + +```sh +node samples/module-fallback-redirect/fallback.mjs +``` + +In another terminal, start Workerd: + +```sh +npx workerd@latest serve samples/module-fallback-redirect/config.capnp --experimental +``` + +Then invoke the Worker: + +```sh +curl http://127.0.0.1:8080 +``` + +The Worker imports two bare dependencies. The fallback service redirects both +to canonical `file:///project/node_modules/...` URLs. The `/project` path is a +logical, portable stand-in for a resolved project path; this reproduction does +not read the host filesystem. + +- `static-test-dependency` statically imports `./value.mjs`. +- `dynamic-test-dependency` dynamically imports `./value.mjs` when `load()` is + called. + +If both imports succeeded, Workerd would log: + +```text +static import: loaded +dynamic import: loaded +``` + +Instead, the static import succeeds, but the dynamic import returns HTTP 500 +with: + +```text +Referring module not found in the registry: file:///project/node_modules/dynamic-test-dependency/index.mjs +``` diff --git a/samples/module-fallback-redirect/config.capnp b/samples/module-fallback-redirect/config.capnp new file mode 100644 index 00000000000..bbc136ef49e --- /dev/null +++ b/samples/module-fallback-redirect/config.capnp @@ -0,0 +1,19 @@ +using Workerd = import "/workerd/workerd.capnp"; + +const config :Workerd.Config = ( + services = [ + (name = "main", worker = .worker), + ], + sockets = [ + (name = "http", address = "127.0.0.1:8080", http = (), service = "main"), + ], +); + +const worker :Workerd.Worker = ( + modules = [ + (name = "worker.js", esModule = embed "worker.js"), + ], + compatibilityDate = "2026-08-03", + compatibilityFlags = ["new_module_registry"], + moduleFallback = "127.0.0.1:8888", +); diff --git a/samples/module-fallback-redirect/fallback.mjs b/samples/module-fallback-redirect/fallback.mjs new file mode 100644 index 00000000000..2780518d918 --- /dev/null +++ b/samples/module-fallback-redirect/fallback.mjs @@ -0,0 +1,76 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { createServer } from 'node:http'; + +function sendJson(response, value) { + response.writeHead(200, { 'content-type': 'application/json' }); + response.end(JSON.stringify(value)); +} + +createServer(async (request, response) => { + let body = ''; + for await (const chunk of request) { + body += chunk; + } + + const resolution = JSON.parse(body); + console.log(resolution); + + switch (resolution.specifier) { + case 'file:///bundle/static-test-dependency': + response.writeHead(301, { + location: + 'file:///project/node_modules/static-test-dependency/index.mjs', + }); + response.end(); + return; + case 'file:///project/node_modules/static-test-dependency/index.mjs': + sendJson(response, { + esModule: [ + 'import { value } from "./value.mjs";', + 'export { value };', + ].join('\n'), + }); + return; + case 'file:///bundle/value.mjs': + response.writeHead(301, { + location: + 'file:///project/node_modules/static-test-dependency/value.mjs', + }); + response.end(); + return; + case 'file:///project/node_modules/static-test-dependency/value.mjs': + sendJson(response, { + esModule: 'export const value = "loaded";', + }); + return; + case 'file:///bundle/dynamic-test-dependency': + response.writeHead(301, { + location: + 'file:///project/node_modules/dynamic-test-dependency/index.mjs', + }); + response.end(); + return; + case 'file:///project/node_modules/dynamic-test-dependency/index.mjs': + sendJson(response, { + esModule: [ + 'export async function load() {', + ' return await import("./value.mjs");', + '}', + ].join('\n'), + }); + return; + case 'file:///project/node_modules/dynamic-test-dependency/value.mjs': + sendJson(response, { + esModule: 'export const value = "loaded";', + }); + return; + default: + response.writeHead(404); + response.end(); + } +}).listen(8888, '127.0.0.1', () => { + console.log('Fallback service listening on http://127.0.0.1:8888'); +}); diff --git a/samples/module-fallback-redirect/worker.js b/samples/module-fallback-redirect/worker.js new file mode 100644 index 00000000000..90be0c51efa --- /dev/null +++ b/samples/module-fallback-redirect/worker.js @@ -0,0 +1,15 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { load } from 'dynamic-test-dependency'; +import { value as staticValue } from 'static-test-dependency'; + +export default { + async fetch() { + console.log('static import:', staticValue); + const dynamicModule = await load(); + console.log('dynamic import:', dynamicModule.value); + return new Response('ok'); + }, +};