diff --git a/ext/node/polyfills/02_init.js b/ext/node/polyfills/02_init.js index cf57507dc7..ef777b985a 100644 --- a/ext/node/polyfills/02_init.js +++ b/ext/node/polyfills/02_init.js @@ -19,6 +19,7 @@ function initialize(args) { maybeWorkerMetadata, nodeDebug, warmup = false, + moduleSpecifier = null, } = args; if (!warmup) { if (initialized) { @@ -41,6 +42,7 @@ function initialize(args) { runningOnMainThread, workerId, maybeWorkerMetadata, + moduleSpecifier, ); internals.__setupChildProcessIpcChannel(); // `Deno[Deno.internal].requireImpl` will be unreachable after this line. diff --git a/ext/node/polyfills/worker_threads.ts b/ext/node/polyfills/worker_threads.ts index 4e398f8823..46e4a641ce 100644 --- a/ext/node/polyfills/worker_threads.ts +++ b/ext/node/polyfills/worker_threads.ts @@ -29,22 +29,26 @@ import { EventEmitter } from "node:events"; import { BroadcastChannel } from "ext:deno_broadcast_channel/01_broadcast_channel.js"; import { untransferableSymbol } from "ext:deno_node/internal_binding/util.ts"; import process from "node:process"; +import { createRequire } from "node:module"; -const { JSONParse, JSONStringify, ObjectPrototypeIsPrototypeOf } = primordials; const { + encodeURIComponent, Error, - ObjectHasOwn, - PromiseResolve, FunctionPrototypeCall, + JSONParse, + JSONStringify, + ObjectHasOwn, + ObjectPrototypeIsPrototypeOf, + PromiseResolve, + SafeMap, SafeSet, + SafeWeakMap, + StringPrototypeStartsWith, + StringPrototypeTrim, Symbol, SymbolFor, SymbolIterator, - StringPrototypeTrim, - SafeWeakMap, - SafeMap, TypeError, - encodeURIComponent, } = primordials; const debugWorkerThreads = false; @@ -363,6 +367,7 @@ internals.__initWorkerThreads = ( runningOnMainThread: boolean, workerId, maybeWorkerMetadata, + moduleSpecifier, ) => { isMainThread = runningOnMainThread; @@ -377,6 +382,17 @@ internals.__initWorkerThreads = ( defaultExport.resourceLimits = resourceLimits; if (!isMainThread) { + // TODO(bartlomieju): this is a really hacky way to provide + // require in worker_threads - this should be rewritten to use proper + // CJS/ESM loading + if (moduleSpecifier) { + globalThis.require = createRequire( + StringPrototypeStartsWith(moduleSpecifier, "data:") + ? `${Deno.cwd()}/[worker eval]` + : moduleSpecifier, + ); + } + const listeners = new SafeWeakMap< // deno-lint-ignore no-explicit-any (...args: any[]) => void, diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index db4281da70..cc66529d81 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -1089,6 +1089,7 @@ function bootstrapWorkerRuntime( } // Not available in workers + const moduleSpecifier = finalDenoNs.mainModule; delete finalDenoNs.mainModule; if (!ArrayPrototypeIncludes(unstableFeatures, unstableIds.unsafeProto)) { @@ -1121,6 +1122,7 @@ function bootstrapWorkerRuntime( workerId, maybeWorkerMetadata: workerMetadata, nodeDebug, + moduleSpecifier: workerType === "node" ? moduleSpecifier : null, }); } } else { diff --git a/tests/unit_node/worker_threads_test.ts b/tests/unit_node/worker_threads_test.ts index c994b91eb1..6e80f4663f 100644 --- a/tests/unit_node/worker_threads_test.ts +++ b/tests/unit_node/worker_threads_test.ts @@ -864,3 +864,30 @@ Deno.test("[node/worker_threads] Worker runs async ops correctly", async () => { await recvMessage.promise; }); + +Deno.test("[node/worker_threads] Worker works with CJS require", async () => { + const recvMessage = Promise.withResolvers(); + const worker = new workerThreads.Worker( + ` + const assert = require("assert"); + require("worker_threads").parentPort.on("message", ({ port }) => { + assert(port instanceof MessagePort); + + port.postMessage("Hello from worker"); + }); + `, + { eval: true }, + ); + + const channel = new workerThreads.MessageChannel(); + worker.postMessage({ port: channel.port2 }, [channel.port2]); + channel.port1.on("message", (msg) => { + assertEquals(msg, "Hello from worker"); + channel.port1.close(); + channel.port2.close(); + worker.terminate(); + recvMessage.resolve(); + }); + + await recvMessage.promise; +});