Skip to content

Commit 49ab23a

Browse files
authored
[Pthreads] Fix worker.js in ES6 module environments (emscripten-core#21041)
This file can be an ES6 module if a package.json file indicates that all files in the directory are. We need to apply the same tricks as we apply to the main JS file in that case, with createRequire etc. We also need to emit the suffix .worker.mjs and not .js, or else node will error. Followup to emscripten-core#20939 and fixes the package.json discussion after that PR landed.
1 parent a997567 commit 49ab23a

File tree

4 files changed

+59
-18
lines changed

4 files changed

+59
-18
lines changed

ChangeLog.md

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.52 (in development)
2222
-----------------------
23+
- Building with `pthreads+EXPORT_ES6` will now emit the worker file as
24+
`NAME.worker.mjs` rather than `.js`. This is a necessary breaking change to
25+
resolve other `pthreads+EXPORT_ES6` issues in Node.js (because Node.js is
26+
affected by the suffix in some cases). (#21041)
2327
- Include paths added by ports (e.g. `-sUSE_SDL=2`) now use `-isystem` rather
2428
then `-I`. This means that files in user-specified include directories will
2529
now take precedence over port includes. (#21014)

src/worker.js

+16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions
1818
if (ENVIRONMENT_IS_NODE) {
1919
// Create as web-worker-like an environment as we can.
2020

21+
// See the parallel code in shell.js, but here we don't need the condition on
22+
// multi-environment builds, as we do not have the need to interact with the
23+
// modularization logic as shell.js must (see link.py:node_es6_imports and
24+
// how that is used in link.py).
25+
#if EXPORT_ES6
26+
const { createRequire } = await import('module');
27+
/** @suppress{duplicate} */
28+
var require = createRequire(import.meta.url);
29+
#endif
30+
2131
var nodeWorkerThreads = require('worker_threads');
2232

2333
var parentPort = nodeWorkerThreads.parentPort;
@@ -32,7 +42,13 @@ if (ENVIRONMENT_IS_NODE) {
3242
require,
3343
Module,
3444
location: {
45+
// __filename is undefined in ES6 modules, and import.meta.url only in ES6
46+
// modules.
47+
#if EXPORT_ES6
48+
href: typeof __filename !== 'undefined' ? __filename : import.meta.url
49+
#else
3550
href: __filename
51+
#endif
3652
},
3753
Worker: nodeWorkerThreads.Worker,
3854
importScripts: (f) => vm.runInThisContext(fs.readFileSync(f, 'utf8'), {filename: f}),

test/test_other.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -343,10 +343,10 @@ def test_emcc_output_worker_mjs(self, args):
343343
test_file('hello_world.c')] + args)
344344
src = read_file('subdir/hello_world.mjs')
345345
self.assertContained("new URL('hello_world.wasm', import.meta.url)", src)
346-
self.assertContained("new Worker(new URL('hello_world.worker.js', import.meta.url), {type: 'module'})", src)
346+
self.assertContained("new Worker(new URL('hello_world.worker.mjs', import.meta.url), {type: 'module'})", src)
347347
self.assertContained("new Worker(pthreadMainJs, {type: 'module'})", src)
348348
self.assertContained('export default Module;', src)
349-
src = read_file('subdir/hello_world.worker.js')
349+
src = read_file('subdir/hello_world.worker.mjs')
350350
self.assertContained("import('./hello_world.mjs')", src)
351351
self.assertContained('hello, world!', self.run_js('subdir/hello_world.mjs'))
352352

@@ -358,7 +358,7 @@ def test_emcc_output_worker_mjs_single_file(self):
358358
test_file('hello_world.c'), '-sSINGLE_FILE'])
359359
src = read_file('hello_world.mjs')
360360
self.assertNotContained("new URL('data:", src)
361-
self.assertContained("new Worker(new URL('hello_world.worker.js', import.meta.url), {type: 'module'})", src)
361+
self.assertContained("new Worker(new URL('hello_world.worker.mjs', import.meta.url), {type: 'module'})", src)
362362
self.assertContained("new Worker(pthreadMainJs, {type: 'module'})", src)
363363
self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
364364

@@ -400,11 +400,16 @@ def test_export_es6_allows_export_in_post_js(self):
400400
src = read_file('a.out.js')
401401
self.assertContained('export{doNothing};', src)
402402

403+
@parameterized({
404+
'': (False,),
405+
'package_json': (True,),
406+
})
403407
@parameterized({
404408
'': ([],),
405-
'pthreads': (['-pthread'],),
409+
# load a worker before startup to check ES6 modules there as well
410+
'pthreads': (['-pthread', '-sPTHREAD_POOL_SIZE=1'],),
406411
})
407-
def test_export_es6(self, args):
412+
def test_export_es6(self, args, package_json):
408413
self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_ES6',
409414
'-o', 'hello.mjs'] + args)
410415
# In ES6 mode we use MODULARIZE, so we must instantiate an instance of the
@@ -413,6 +418,12 @@ def test_export_es6(self, args):
413418
import Hello from "./hello.mjs";
414419
Hello();
415420
''')
421+
422+
if package_json:
423+
# This makes node load all files in the directory as ES6 modules,
424+
# including the worker.js file.
425+
create_file('package.json', '{"type":"module"}')
426+
416427
self.assertContained('hello, world!', self.run_js('runner.mjs'))
417428

418429
def test_emcc_out_file(self):

tools/link.py

+23-13
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,10 @@ def do_split_module(wasm_file, options):
513513
building.run_binaryen_command('wasm-split', wasm_file + '.orig', outfile=wasm_file, args=args)
514514

515515

516+
def get_worker_js_suffix():
517+
return '.worker.mjs' if settings.EXPORT_ES6 else '.worker.js'
518+
519+
516520
def setup_pthreads(target):
517521
if settings.RELOCATABLE:
518522
# phtreads + dyanmic linking has certain limitations
@@ -569,7 +573,7 @@ def setup_pthreads(target):
569573
building.user_requested_exports.update(worker_imports)
570574

571575
# set location of worker.js
572-
settings.PTHREAD_WORKER_FILE = unsuffixed_basename(target) + '.worker.js'
576+
settings.PTHREAD_WORKER_FILE = unsuffixed_basename(target) + get_worker_js_suffix()
573577

574578
if settings.MINIMAL_RUNTIME:
575579
building.user_requested_exports.add('exit')
@@ -1999,12 +2003,27 @@ def phase_memory_initializer(memfile):
19992003
final_js += '.mem.js'
20002004

20012005

2006+
# Unmangle previously mangled `import.meta` and `await import` references in
2007+
# both main code and libraries.
2008+
# See also: `preprocess` in parseTools.js.
2009+
def fix_es6_import_statements(js_file):
2010+
if not settings.EXPORT_ES6 or not settings.USE_ES6_IMPORT_META:
2011+
return
2012+
2013+
src = read_file(js_file)
2014+
write_file(js_file, src
2015+
.replace('EMSCRIPTEN$IMPORT$META', 'import.meta')
2016+
.replace('EMSCRIPTEN$AWAIT$IMPORT', 'await import'))
2017+
2018+
20022019
def create_worker_file(input_file, target_dir, output_file):
20032020
output_file = os.path.join(target_dir, output_file)
20042021
input_file = utils.path_from_root(input_file)
20052022
contents = shared.read_and_preprocess(input_file, expand_macros=True)
20062023
write_file(output_file, contents)
20072024

2025+
fix_es6_import_statements(output_file)
2026+
20082027
# Minify the worker JS file, if JS minification is enabled.
20092028
if settings.MINIFY_WHITESPACE:
20102029
contents = building.acorn_optimizer(output_file, ['minifyWhitespace'], return_output=True)
@@ -2045,17 +2064,8 @@ def phase_final_emitting(options, state, target, wasm_target, memfile):
20452064
# mode)
20462065
final_js = building.closure_compiler(final_js, advanced=False, extra_closure_args=options.closure_args)
20472066

2048-
# Unmangle previously mangled `import.meta` and `await import` references in
2049-
# both main code and libraries.
2050-
# See also: `preprocess` in parseTools.js.
2051-
if settings.EXPORT_ES6 and settings.USE_ES6_IMPORT_META:
2052-
src = read_file(final_js)
2053-
final_js += '.esmeta.js'
2054-
write_file(final_js, src
2055-
.replace('EMSCRIPTEN$IMPORT$META', 'import.meta')
2056-
.replace('EMSCRIPTEN$AWAIT$IMPORT', 'await import'))
2057-
shared.get_temp_files().note(final_js)
2058-
save_intermediate('es6-module')
2067+
fix_es6_import_statements(final_js)
2068+
save_intermediate('es6-module')
20592069

20602070
# Apply pre and postjs files
20612071
if options.extern_pre_js or options.extern_post_js:
@@ -2600,7 +2610,7 @@ def generate_worker_js(target, js_target, target_basename):
26002610
proxy_worker_filename = get_subresource_location(js_target)
26012611
else:
26022612
# compiler output goes in .worker.js file
2603-
move_file(js_target, shared.replace_suffix(js_target, '.worker.js'))
2613+
move_file(js_target, shared.replace_suffix(js_target, get_worker_js_suffix()))
26042614
worker_target_basename = target_basename + '.worker'
26052615
proxy_worker_filename = (settings.PROXY_TO_WORKER_FILENAME or worker_target_basename) + '.js'
26062616

0 commit comments

Comments
 (0)