From 11869008f69e5d14318de2bd24a38cc1a45c328c Mon Sep 17 00:00:00 2001
From: Sam Clegg <sbc@chromium.org>
Date: Wed, 19 Feb 2025 15:12:01 -0800
Subject: [PATCH] Fix for vite bundling with pthreads enabled

Replaces #23607
Fixes: #22394
---
 src/lib/libpthread.js | 51 ++++++++++++++++++++++++-------------------
 test/test_browser.py  |  3 ++-
 test/test_other.py    |  4 ++--
 3 files changed, 32 insertions(+), 26 deletions(-)

diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js
index a3c4cb4377d23..c009e99a3545e 100644
--- a/src/lib/libpthread.js
+++ b/src/lib/libpthread.js
@@ -29,6 +29,30 @@ globalThis.MAX_PTR = Number((2n ** 64n) - 1n);
 #else
 globalThis.MAX_PTR = (2 ** 32) - 1
 #endif
+// Use a macro to avoid duplicating pthread worker options.
+// We cannot use normal JS variable since the vite bundler requires that worker
+// options be inline.
+// See https://github.com/emscripten-core/emscripten/issues/22394
+globalThis.pthreadWorkerOptions = `{
+#if EXPORT_ES6
+        'type': 'module',
+#endif
+#if ENVIRONMENT_MAY_BE_NODE
+        // This is the way that we signal to the node worker that it is hosting
+        // a pthread.
+        'workerData': 'em-pthread',
+#endif
+#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
+        // This is the way that we signal to the Web Worker that it is hosting
+        // a pthread.
+#if ASSERTIONS
+        'name': 'em-pthread-' + PThread.nextWorkerID,
+#else
+        'name': 'em-pthread',
+#endif
+#endif
+}`;
+null
 }}}
 
 var LibraryPThread = {
@@ -394,25 +418,6 @@ var LibraryPThread = {
     // Creates a new web Worker and places it in the unused worker pool to wait for its use.
     allocateUnusedWorker() {
       var worker;
-      var workerOptions = {
-#if EXPORT_ES6
-        'type': 'module',
-#endif
-#if ENVIRONMENT_MAY_BE_NODE
-        // This is the way that we signal to the node worker that it is hosting
-        // a pthread.
-        'workerData': 'em-pthread',
-#endif
-#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
-        // This is the way that we signal to the Web Worker that it is hosting
-        // a pthread.
-#if ASSERTIONS
-        'name': 'em-pthread-' + PThread.nextWorkerID,
-#else
-        'name': 'em-pthread',
-#endif
-#endif
-      };
 #if EXPORT_ES6 && USE_ES6_IMPORT_META
       // If we're using module output, use bundler-friendly pattern.
 #if PTHREADS_DEBUG
@@ -427,14 +432,14 @@ var LibraryPThread = {
             createScriptURL: (ignored) => new URL("{{{ TARGET_JS_NAME }}}", import.meta.url)
           }
         );
-        worker = new Worker(p.createScriptURL('ignored'), workerOptions);
+        worker = new Worker(p.createScriptURL('ignored'), {{{ pthreadWorkerOptions }}});
       } else
 #endif
       // We need to generate the URL with import.meta.url as the base URL of the JS file
       // instead of just using new URL(import.meta.url) because bundler's only recognize
       // the first case in their bundling step. The latter ends up producing an invalid
       // URL to import from the server (e.g., for webpack the file:// path).
-      worker = new Worker(new URL('{{{ TARGET_JS_NAME }}}', import.meta.url), workerOptions);
+      worker = new Worker(new URL('{{{ TARGET_JS_NAME }}}', import.meta.url), {{{ pthreadWorkerOptions }}});
 #else
       var pthreadMainJs = _scriptName;
 #if expectToReceiveOnModule('mainScriptUrlOrBlob')
@@ -454,10 +459,10 @@ var LibraryPThread = {
       // Use Trusted Types compatible wrappers.
       if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) {
         var p = trustedTypes.createPolicy('emscripten#workerPolicy2', { createScriptURL: (ignored) => pthreadMainJs });
-        worker = new Worker(p.createScriptURL('ignored'), workerOptions);
+        worker = new Worker(p.createScriptURL('ignored'), {{{ pthreadWorkerOptions }}});
       } else
 #endif
-      worker = new Worker(pthreadMainJs, workerOptions);
+      worker = new Worker(pthreadMainJs, {{{ pthreadWorkerOptions }}});
 #endif // EXPORT_ES6 && USE_ES6_IMPORT_META
       PThread.unusedWorkers.push(worker);
     },
diff --git a/test/test_browser.py b/test/test_browser.py
index 20fa6eadc142c..d0d47ff0010a9 100644
--- a/test/test_browser.py
+++ b/test/test_browser.py
@@ -5534,10 +5534,11 @@ def test_webpack(self, es6):
     shutil.copy('webpack/src/hello.wasm', 'webpack/dist/')
     self.run_browser('webpack/dist/index.html', '/report_result?exit:0')
 
+  @also_with_threads
   def test_vite(self):
     shutil.copytree(test_file('vite'), 'vite')
     with common.chdir('vite'):
-      self.compile_btest('hello_world.c', ['-sEXPORT_ES6', '-sEXIT_RUNTIME', '-sMODULARIZE', '-o', 'hello.mjs'])
+      self.compile_btest('hello_world.c', ['-sEXPORT_ES6', '-sEXIT_RUNTIME', '-sMODULARIZE', '-sENVIRONMENT=web,worker', '-o', 'hello.mjs'])
       self.run_process(shared.get_npm_cmd('vite') + ['build'])
     self.run_browser('vite/dist/index.html', '/report_result?exit:0')
 
diff --git a/test/test_other.py b/test/test_other.py
index 5995a90881715..8f9d7049b8d6a 100644
--- a/test/test_other.py
+++ b/test/test_other.py
@@ -380,7 +380,7 @@ def test_emcc_output_worker_mjs(self, args):
                       test_file('hello_world.c')] + args)
     src = read_file('subdir/hello_world.mjs')
     self.assertContained("new URL('hello_world.wasm', import.meta.url)", src)
-    self.assertContained("new Worker(new URL('hello_world.mjs', import.meta.url), workerOptions)", src)
+    self.assertContained("new Worker(new URL('hello_world.mjs', import.meta.url), {", src)
     self.assertContained('export default Module;', src)
     self.assertContained('hello, world!', self.run_js('subdir/hello_world.mjs'))
 
@@ -391,7 +391,7 @@ def test_emcc_output_worker_mjs_single_file(self):
                       test_file('hello_world.c'), '-sSINGLE_FILE'])
     src = read_file('hello_world.mjs')
     self.assertNotContained("new URL('data:", src)
-    self.assertContained("new Worker(new URL('hello_world.mjs', import.meta.url), workerOptions)", src)
+    self.assertContained("new Worker(new URL('hello_world.mjs', import.meta.url), {", src)
     self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
 
   def test_emcc_output_mjs_closure(self):