From 06fed35786eb2ec9796eded5e322da86e5a05645 Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Mon, 19 Oct 2026 00:02:09 -0400 Subject: [PATCH] fix: load Worker entries served over HTTP new Worker() resolved every specifier as a file and read it from disk, so a dev server URL (new URL('./x.worker.ts', import.meta.url) under HMR) failed with os error 123. HTTP entries now skip the disk read and the worker's thread imports them through the native module loader, so the entry's own imports resolve against the URL. A failed load is reported as a worker error. --- runtime/src/esm_http_test.rs | 18 ++++++++++++++++++ runtime/src/worker_support.rs | 10 ++++++++++ runtime/src/worker_threads.rs | 6 +++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/runtime/src/esm_http_test.rs b/runtime/src/esm_http_test.rs index 5fd9ac8..4580d1c 100644 --- a/runtime/src/esm_http_test.rs +++ b/runtime/src/esm_http_test.rs @@ -248,6 +248,24 @@ fn http_import_failures_reject_with_the_cause() { assert_eq!(invalid, "TypeError"); } +#[test] +fn worker_entry_served_over_http() { + let server = TestServer::start(); + let o = server.origin.clone(); + server.route("/ns/m/echo.worker.js", "text/javascript", "import { add } from './add.js';\nself.onmessage = (event) => self.postMessage(add(event.data));"); + server.route("/ns/m/add.js", "text/javascript", "export const add = (n) => n + 1;"); + let mut runtime = Runtime::new("."); + runtime.register_delegate_isolate_ptr(); + let run = |path: &str| { + format!( + "new Promise((resolve, reject) => {{ const worker = new Worker(new URL('{o}{path}')); worker.onmessage = (event) => {{ worker.terminate(); event.type === 'messageerror' ? reject(event.error) : resolve(event.data); }}; worker.postMessage(41); }})" + ) + }; + assert_eq!(await_js(&mut runtime, &run("/ns/m/echo.worker.js")), "ok:42"); + let missing = await_js(&mut runtime, &run("/ns/m/missing.worker.js")); + assert!(missing.starts_with("err:") && missing.contains("status=404"), "{missing}"); +} + #[test] fn native_websocket_round_trip() { let server = TestServer::start(); diff --git a/runtime/src/worker_support.rs b/runtime/src/worker_support.rs index da678d1..21065e0 100644 --- a/runtime/src/worker_support.rs +++ b/runtime/src/worker_support.rs @@ -26,6 +26,12 @@ pub fn install_worker_runtime(scope: &mut ContextScope) { return { source: input, filename: '[worker:eval]' }; } + // A dev server's URL (`new URL('./x.worker.ts', import.meta.url)` under HMR) isn't a + // file: the worker's thread imports it, so its own imports resolve against the URL. + if (/^https?:\/\//.test(input)) { + return { source: '', filename: input }; + } + if (typeof globalThis.__nsResolveModulePath !== 'function' || typeof globalThis.__nsReadTextFile !== 'function') { throw new Error('Worker file loading is unavailable: missing runtime host hooks'); } @@ -90,6 +96,10 @@ pub fn install_worker_runtime(scope: &mut ContextScope) { typeof globalThis.__nsWorkerPollMessagesBlocking === 'function' && typeof globalThis.__nsWorkerTerminate === 'function'; + if (!canUseThreadedHost && /^https?:\/\//.test(resolved.filename)) { + throw new Error('Loading a Worker from a URL needs the threaded worker host: ' + resolved.filename); + } + var channel = new MessageChannel(); var mainPort = channel.port1; var workerPort = channel.port2; diff --git a/runtime/src/worker_threads.rs b/runtime/src/worker_threads.rs index 8b536bc..ae28770 100644 --- a/runtime/src/worker_threads.rs +++ b/runtime/src/worker_threads.rs @@ -173,7 +173,11 @@ fn worker_bootstrap_script(source: &str, filename: &str) -> Result