Repository: vercel/next.js
Pull request: [turbopack] Fix the stubbed out implementation of `signature`  so that it works in a web worker.

## What
Fix the stubbed out runtime hook for react HMR to correctly support both the `register` and `signature` APIs, by moving the stubs into the runtime directly.  This way TSC can tell us we failed to uphold the function signature.

## Why

The `swc` react transform looks for certain naming conventions and function signatures to register top level items for HMR.  However, if a module is loaded in a web worker the react hooks are not installed.  We could potentially try to avoid running the react transform on modules loaded in workers but as a dev-only transform this might actually be slower (for shared modules).  So instead we just supply an alternate fake implementation.

Closes PACK-4669

Changed files:
filename: turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/dev-base.ts
patch: @@ -129,15 +129,6 @@ const getOrInstantiateModuleFromParent: GetOrInstantiateModuleFromParent<
   })
 }
 
-function getDevWorkerBlobURL(chunks: ChunkPath[]) {
-  return getWorkerBlobURL(
-    chunks,
-    `// noop fns to prevent runtime errors during initialization
-self.$RefreshReg$ = function() {};
-self.$RefreshSig$ = function() {};`
-  )
-}
-
 // @ts-ignore Defined in `runtime-base.ts`
 function instantiateModule(id: ModuleId, source: SourceInfo): Module {
   const moduleFactory = moduleFactories[id]
@@ -229,7 +220,7 @@ function instantiateModule(id: ModuleId, source: SourceInfo): Module {
           U: relativeURL,
           k: refresh,
           R: createResolvePathFromModule(r),
-          b: getDevWorkerBlobURL,
+          b: getWorkerBlobURL,
           z: requireStub,
         })
       )
@@ -257,22 +248,28 @@ function runModuleExecutionHooks(
   module: Module,
   executeModule: (ctx: RefreshContext) => void
 ) {
-  const cleanupReactRefreshIntercept =
-    typeof globalThis.$RefreshInterceptModuleExecution$ === 'function'
-      ? globalThis.$RefreshInterceptModuleExecution$(module.id)
-      : () => {}
-
-  try {
+  if (typeof globalThis.$RefreshInterceptModuleExecution$ === 'function') {
+    const cleanupReactRefreshIntercept =
+      globalThis.$RefreshInterceptModuleExecution$(module.id)
+    try {
+      executeModule({
+        register: globalThis.$RefreshReg$,
+        signature: globalThis.$RefreshSig$,
+        registerExports: registerExportsAndSetupBoundaryForReactRefresh,
+      })
+    } finally {
+      // Always cleanup the intercept, even if module execution failed.
+      cleanupReactRefreshIntercept()
+    }
+  } else {
+    // If the react refresh hooks are not installed we need to bind dummy functions.
+    // This is expected when running in a Web Worker.  It is also common in some of
+    // our test environments.
     executeModule({
-      register: globalThis.$RefreshReg$,
-      signature: globalThis.$RefreshSig$,
-      registerExports: registerExportsAndSetupBoundaryForReactRefresh,
+      register: (type, id) => {},
+      signature: () => (type) => {},
+      registerExports: (module, helpers) => {},
     })
-  } catch (e) {
-    throw e
-  } finally {
-    // Always cleanup the intercept, even if module execution failed.
-    cleanupReactRefreshIntercept()
   }
 }
 

filename: turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts
patch: @@ -255,15 +255,12 @@ function resolveAbsolutePath(modulePath?: string): string {
 /**
  * Returns a blob URL for the worker.
  * @param chunks list of chunks to load
- * @param beforeLoad code to run before code is loaded. Used in dev for HMR setup.
  */
-function getWorkerBlobURL(
-  chunks: ChunkPath[],
-  beforeLoad: string | undefined
-): string {
+function getWorkerBlobURL(chunks: ChunkPath[]): string {
+  // It is important to reverse the array so when bootstrapping we can infer what chunk is being
+  // evaluated by poping urls off of this array.  See `getPathFromScript`
   let bootstrap = `self.TURBOPACK_WORKER_LOCATION = ${JSON.stringify(location.origin)};
 self.TURBOPACK_NEXT_CHUNK_URLS = ${JSON.stringify(chunks.reverse().map(getChunkRelativeUrl), null, 2)};
-${beforeLoad || ''}
 importScripts(...self.TURBOPACK_NEXT_CHUNK_URLS.map(c => self.TURBOPACK_WORKER_LOCATION + c).reverse());`
   let blob = new Blob([bootstrap], { type: 'text/javascript' })
   return URL.createObjectURL(blob)

filename: turbopack/crates/turbopack-tests/tests/snapshot/runtime/default_dev_runtime/output/b1abf_turbopack-tests_tests_snapshot_runtime_default_dev_runtime_input_index_75df6705.js
patch: @@ -496,11 +496,11 @@ async function loadChunkPath(source, chunkPath) {
 /**
  * Returns a blob URL for the worker.
  * @param chunks list of chunks to load
- * @param beforeLoad code to run before code is loaded. Used in dev for HMR setup.
- */ function getWorkerBlobURL(chunks, beforeLoad) {
+ */ function getWorkerBlobURL(chunks) {
+    // It is important to reverse the array so when bootstrapping we can infer what chunk is being
+    // evaluated by poping urls off of this array.  See `getPathFromScript`
     let bootstrap = `self.TURBOPACK_WORKER_LOCATION = ${JSON.stringify(location.origin)};
 self.TURBOPACK_NEXT_CHUNK_URLS = ${JSON.stringify(chunks.reverse().map(getChunkRelativeUrl), null, 2)};
-${beforeLoad || ''}
 importScripts(...self.TURBOPACK_NEXT_CHUNK_URLS.map(c => self.TURBOPACK_WORKER_LOCATION + c).reverse());`;
     let blob = new Blob([
         bootstrap
@@ -660,11 +660,6 @@ const getOrInstantiateModuleFromParent = (id, sourceModule)=>{
         parentId: sourceModule.id
     });
 };
-function getDevWorkerBlobURL(chunks) {
-    return getWorkerBlobURL(chunks, `// noop fns to prevent runtime errors during initialization
-self.$RefreshReg$ = function() {};
-self.$RefreshSig$ = function() {};`);
-}
 // @ts-ignore Defined in `runtime-base.ts`
 function instantiateModule(id, source) {
     const moduleFactory = moduleFactories[id];
@@ -751,7 +746,7 @@ function instantiateModule(id, source) {
                 U: relativeURL,
                 k: refresh,
                 R: createResolvePathFromModule(r),
-                b: getDevWorkerBlobURL,
+                b: getWorkerBlobURL,
                 z: requireStub
             }));
         });
@@ -771,18 +766,27 @@ function instantiateModule(id, source) {
  * Next.js' React Refresh runtime hooks into to add module context to the
  * refresh registry.
  */ function runModuleExecutionHooks(module, executeModule) {
-    const cleanupReactRefreshIntercept = typeof globalThis.$RefreshInterceptModuleExecution$ === 'function' ? globalThis.$RefreshInterceptModuleExecution$(module.id) : ()=>{};
-    try {
+    if (typeof globalThis.$RefreshInterceptModuleExecution$ === 'function') {
+        const cleanupReactRefreshIntercept = globalThis.$RefreshInterceptModuleExecution$(module.id);
+        try {
+            executeModule({
+                register: globalThis.$RefreshReg$,
+                signature: globalThis.$RefreshSig$,
+                registerExports: registerExportsAndSetupBoundaryForReactRefresh
+            });
+        } finally{
+            // Always cleanup the intercept, even if module execution failed.
+            cleanupReactRefreshIntercept();
+        }
+    } else {
+        // If the react refresh hooks are not installed we need to bind dummy functions.
+        // This is expected when running in a Web Worker.  It is also common in some of
+        // our test environments.
         executeModule({
-            register: globalThis.$RefreshReg$,
-            signature: globalThis.$RefreshSig$,
-            registerExports: registerExportsAndSetupBoundaryForReactRefresh
+            register: (type, id)=>{},
+            signature: ()=>(type)=>{},
+            registerExports: (module, helpers)=>{}
         });
-    } catch (e) {
-        throw e;
-    } finally{
-        // Always cleanup the intercept, even if module execution failed.
-        cleanupReactRefreshIntercept();
     }
 }
 /**