Repository: denoland/deno
Pull request: fix(ext/node): worker_threads handles basic `require` calls

This is not a full-fledged and fully correct `require`/CJS support
for `node:worker_threads`, but unlocks certain scenarios that
were not working at all previously.

Changed files:
filename: ext/node/polyfills/02_init.js
patch: @@ -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.

filename: ext/node/polyfills/worker_threads.ts
patch: @@ -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,
+  FunctionPrototypeCall,
+  JSONParse,
+  JSONStringify,
   ObjectHasOwn,
+  ObjectPrototypeIsPrototypeOf,
   PromiseResolve,
-  FunctionPrototypeCall,
+  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,

filename: runtime/js/99_main.js
patch: @@ -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 {

filename: tests/unit_node/worker_threads_test.ts
patch: @@ -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<void>();
+  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;
+});