Skip to content

Commit ffcd91b

Browse files
authored
Move modularization code into JS file. NFC (#24533)
Its much easier to generate JS code using out JS template system than emitting directly from python. Split out from #23261
1 parent 4c98511 commit ffcd91b

10 files changed

+155
-136
lines changed

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default [{
2929
'src/lib/',
3030
'src/runtime_*.js',
3131
'src/shell*.js',
32+
'src/modularize.js',
3233
'src/preamble*.js',
3334
'src/postlibrary.js',
3435
'src/postamble*.js',

src/modularize.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @license
3+
* Copyright 2025 The Emscripten Authors
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
// This code implements the `-sMODULARIZE` settings by taking the generated
8+
// JS program code (INNER_JS_CODE) and wrapping it in a factory function.
9+
10+
#if SOURCE_PHASE_IMPORTS
11+
import source wasmModule from './{settings.WASM_BINARY_FILE}';
12+
#endif
13+
14+
#if ENVIRONMENT_MAY_BE_WEB && !EXPORT_ES6 && !(MINIMAL_RUNTIME && !PTHREADS)
15+
// Single threaded MINIMAL_RUNTIME programs do not need access to
16+
// document.currentScript, so a simple export declaration is enough.
17+
var {{{ EXPORT_NAME }}} = (() => {
18+
// When MODULARIZE this JS may be executed later,
19+
// after document.currentScript is gone, so we save it.
20+
// In EXPORT_ES6 mode we can just use 'import.meta.url'.
21+
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
22+
return {{{ asyncIf(WASM_ASYNC_COMPILATION || (EXPORT_ES6 && ENVIRONMENT_MAY_BE_NODE)) }}}function(moduleArg = {}) {
23+
var moduleRtn;
24+
25+
<<< INNER_JS_CODE >>>
26+
27+
return moduleRtn;
28+
};
29+
})();
30+
#else
31+
// When targetting node and ES6 we use `await import ..` in the generated code
32+
// so the outer function needs to be marked as async.
33+
{{{ asyncIf(WASM_ASYNC_COMPILATION || (EXPORT_ES6 && ENVIRONMENT_MAY_BE_NODE)) }}}function {{{ EXPORT_NAME }}}(moduleArg = {}) {
34+
var moduleRtn;
35+
36+
<<< INNER_JS_CODE >>>
37+
38+
return moduleRtn;
39+
}
40+
#endif
41+
42+
// Export using a UMD style export, or ES6 exports if selected
43+
#if EXPORT_ES6
44+
export default {{{ EXPORT_NAME }}};
45+
#elif !MINIMAL_RUNTIME
46+
if (typeof exports === 'object' && typeof module === 'object') {
47+
module.exports = {{{ EXPORT_NAME }}};
48+
// This default export looks redundant, but it allows TS to import this
49+
// commonjs style module.
50+
module.exports.default = {{{ EXPORT_NAME }}};
51+
} else if (typeof define === 'function' && define['amd'])
52+
define([], () => {{{ EXPORT_NAME }}});
53+
#endif
54+
55+
#if PTHREADS
56+
57+
// Create code for detecting if we are running in a pthread.
58+
// Normally this detection is done when the module is itself run but
59+
// when running in MODULARIZE mode we need use this to know if we should
60+
// run the module constructor on startup (true only for pthreads).
61+
#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
62+
var isPthread = globalThis.self?.name?.startsWith('em-pthread');
63+
#if ENVIRONMENT_MAY_BE_NODE
64+
// In order to support both web and node we also need to detect node here.
65+
var isNode = {{{ nodeDetectionCode() }}};
66+
if (isNode) isPthread = {{{ nodePthreadDetection() }}}
67+
#endif
68+
#else ENVIRONMENT_MAY_BE_NODE
69+
var isPthread = {{{ nodePthreadDetection() }}}
70+
// When running as a pthread, construct a new instance on startup
71+
#endif
72+
73+
#if MODULARIZE == 'instance'
74+
isPthread && init();
75+
#else
76+
isPthread && {{{ EXPORT_NAME }}}();
77+
#endif
78+
79+
#endif // PTHREADS
80+
81+
#if WASM_WORKERS
82+
83+
// Same as above for for WASM_WORKERS
84+
// Normally this detection is done when the module is itself run but
85+
// when running in MODULARIZE mode we need use this to know if we should
86+
// run the module constructor on startup (true only for pthreads).
87+
#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
88+
var isWW = globalThis.self?.name == 'em-ww';
89+
// In order to support both web and node we also need to detect node here.
90+
#if ENVIRONMENT_MAY_BE_NODE
91+
#if !PTHREADS
92+
var isNode = {{{ nodeDetectionCode() }}};
93+
#endif
94+
if (isNode) isWW = {{{ nodeWWDetection() }}};
95+
#endif
96+
#elif ENVIRONMENT_MAY_BE_NODE
97+
var isWW = {{{ nodeWWDetection() }}};
98+
#endif
99+
100+
#if AUDIO_WORKLET
101+
isWW ||= typeof AudioWorkletGlobalScope !== 'undefined';
102+
// When running as a wasm worker, construct a new instance on startup
103+
#endif
104+
105+
#if MODULARIZE == 'instance'
106+
isWW && init();
107+
#else
108+
isWW && {{{ EXPORT_NAME }}}();
109+
#endif
110+
111+
#endif // WASM_WORKERS

src/parseTools.mjs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,26 @@ function nodeDetectionCode() {
10971097
return "typeof process == 'object' && process.versions?.node && process.type != 'renderer'";
10981098
}
10991099

1100+
function nodePthreadDetection() {
1101+
// Under node we detect that we are running in a pthread by checking the
1102+
// workerData property.
1103+
if (EXPORT_ES6) {
1104+
return "(await import('worker_threads')).workerData === 'em-pthread'";
1105+
} else {
1106+
return "require('worker_threads').workerData === 'em-pthread'";
1107+
}
1108+
}
1109+
1110+
function nodeWWDetection() {
1111+
// Under node we detect that we are running in a wasm worker by checking the
1112+
// workerData property.
1113+
if (EXPORT_ES6) {
1114+
return "(await import('worker_threads')).workerData === 'em-ww'";
1115+
} else {
1116+
return "require('worker_threads').workerData === 'em-ww'";
1117+
}
1118+
}
1119+
11001120
addToCompileTimeContext({
11011121
ATEXITS,
11021122
ATPRERUNS,
@@ -1169,4 +1189,6 @@ addToCompileTimeContext({
11691189
storeException,
11701190
to64,
11711191
toIndexType,
1192+
nodePthreadDetection,
1193+
nodeWWDetection,
11721194
});

test/code_size/hello_webgl2_wasm.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.html": 454,
33
"a.html.gz": 328,
4-
"a.js": 4391,
5-
"a.js.gz": 2255,
4+
"a.js": 4386,
5+
"a.js.gz": 2252,
66
"a.wasm": 8286,
77
"a.wasm.gz": 5617,
8-
"total": 13131,
9-
"total_gz": 8200
8+
"total": 13126,
9+
"total_gz": 8197
1010
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"a.html": 346,
33
"a.html.gz": 262,
4-
"a.js": 18083,
5-
"a.js.gz": 9785,
6-
"total": 18429,
7-
"total_gz": 10047
4+
"a.js": 18078,
5+
"a.js.gz": 9781,
6+
"total": 18424,
7+
"total_gz": 10043
88
}

test/code_size/hello_webgl_wasm.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.html": 454,
33
"a.html.gz": 328,
4-
"a.js": 3929,
5-
"a.js.gz": 2096,
4+
"a.js": 3924,
5+
"a.js.gz": 2092,
66
"a.wasm": 8286,
77
"a.wasm.gz": 5617,
8-
"total": 12669,
9-
"total_gz": 8041
8+
"total": 12664,
9+
"total_gz": 8037
1010
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"a.html": 346,
33
"a.html.gz": 262,
4-
"a.js": 17610,
5-
"a.js.gz": 9616,
6-
"total": 17956,
7-
"total_gz": 9878
4+
"a.js": 17605,
5+
"a.js.gz": 9614,
6+
"total": 17951,
7+
"total_gz": 9876
88
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1236
1+
1234
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2556
1+
2551

tools/link.py

Lines changed: 3 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,126 +2436,11 @@ def phase_binaryen(target, options, wasm_target):
24362436
write_file(final_js, js)
24372437

24382438

2439-
def node_pthread_detection():
2440-
# Under node we detect that we are running in a pthread by checking the
2441-
# workerData property.
2442-
if settings.EXPORT_ES6:
2443-
return "(await import('worker_threads')).workerData === 'em-pthread';\n"
2444-
else:
2445-
return "require('worker_threads').workerData === 'em-pthread'\n"
2446-
2447-
2448-
def node_ww_detection():
2449-
# Under node we detect that we are running in a wasm worker by checking the
2450-
# workerData property.
2451-
if settings.EXPORT_ES6:
2452-
return "(await import('worker_threads')).workerData === 'em-ww';\n"
2453-
else:
2454-
return "require('worker_threads').workerData === 'em-ww'\n"
2455-
2456-
24572439
def modularize():
24582440
global final_js
2459-
logger.debug(f'Modularizing, assigning to var {settings.EXPORT_NAME}')
2460-
generated_js = read_file(final_js)
2461-
2462-
# When targetting node and ES6 we use `await import ..` in the generated code
2463-
# so the outer function needs to be marked as async.
2464-
if settings.WASM_ASYNC_COMPILATION or (settings.EXPORT_ES6 and settings.ENVIRONMENT_MAY_BE_NODE):
2465-
maybe_async = 'async '
2466-
else:
2467-
maybe_async = ''
2468-
2469-
wrapper_function = '''
2470-
%(maybe_async)sfunction(moduleArg = {}) {
2471-
var moduleRtn;
2472-
2473-
%(generated_js)s
2474-
2475-
return moduleRtn;
2476-
}
2477-
''' % {
2478-
'maybe_async': maybe_async,
2479-
'generated_js': generated_js,
2480-
}
2481-
2482-
# In MODULARIZE mode this JS may be executed later, after
2483-
# document.currentScript is gone, so we need to capture it on load using a
2484-
# closure. In EXPORT_ES6 mode we can just use 'import.meta.url'.
2485-
capture_currentScript = settings.ENVIRONMENT_MAY_BE_WEB and not settings.EXPORT_ES6
2486-
# Single threaded MINIMAL_RUNTIME programs do not need access to
2487-
# document.currentScript, so a simple export declaration is enough.
2488-
if settings.MINIMAL_RUNTIME and not settings.PTHREADS:
2489-
capture_currentScript = False
2490-
2491-
if not capture_currentScript:
2492-
src = f'var {settings.EXPORT_NAME} = {wrapper_function};'
2493-
else:
2494-
src = f'''\
2495-
var {settings.EXPORT_NAME} = (() => {{
2496-
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
2497-
return ({wrapper_function});
2498-
}})();
2499-
'''
2500-
2501-
if settings.SOURCE_PHASE_IMPORTS:
2502-
src = f"import source wasmModule from './{settings.WASM_BINARY_FILE}';\n\n" + src
2503-
2504-
# Export using a UMD style export, or ES6 exports if selected
2505-
if settings.EXPORT_ES6:
2506-
src += 'export default %s;\n' % settings.EXPORT_NAME
2507-
elif not settings.MINIMAL_RUNTIME:
2508-
src += '''\
2509-
if (typeof exports === 'object' && typeof module === 'object') {
2510-
module.exports = %(EXPORT_NAME)s;
2511-
// This default export looks redundant, but it allows TS to import this
2512-
// commonjs style module.
2513-
module.exports.default = %(EXPORT_NAME)s;
2514-
} else if (typeof define === 'function' && define['amd'])
2515-
define([], () => %(EXPORT_NAME)s);
2516-
''' % {'EXPORT_NAME': settings.EXPORT_NAME}
2517-
2518-
if settings.PTHREADS:
2519-
# Create code for detecting if we are running in a pthread.
2520-
# Normally this detection is done when the module is itself run but
2521-
# when running in MODULARIZE mode we need use this to know if we should
2522-
# run the module constructor on startup (true only for pthreads).
2523-
if settings.ENVIRONMENT_MAY_BE_WEB or settings.ENVIRONMENT_MAY_BE_WORKER:
2524-
src += "var isPthread = globalThis.self?.name?.startsWith('em-pthread');\n"
2525-
# In order to support both web and node we also need to detect node here.
2526-
if settings.ENVIRONMENT_MAY_BE_NODE:
2527-
src += f'var isNode = {node_detection_code()};\n'
2528-
src += f'if (isNode) isPthread = {node_pthread_detection()}\n'
2529-
elif settings.ENVIRONMENT_MAY_BE_NODE:
2530-
src += f'var isPthread = {node_pthread_detection()}\n'
2531-
src += '// When running as a pthread, construct a new instance on startup\n'
2532-
if settings.MODULARIZE == 'instance':
2533-
src += 'isPthread && init();\n'
2534-
else:
2535-
src += 'isPthread && %s();\n' % settings.EXPORT_NAME
2536-
2537-
if settings.WASM_WORKERS:
2538-
# Same as above for for WASM_WORKERS
2539-
# Normally this detection is done when the module is itself run but
2540-
# when running in MODULARIZE mode we need use this to know if we should
2541-
# run the module constructor on startup (true only for pthreads).
2542-
if settings.ENVIRONMENT_MAY_BE_WEB or settings.ENVIRONMENT_MAY_BE_WORKER:
2543-
src += "var isWW = globalThis.self?.name == 'em-ww';\n"
2544-
# In order to support both web and node we also need to detect node here.
2545-
if settings.ENVIRONMENT_MAY_BE_NODE:
2546-
if not settings.PTHREADS:
2547-
src += f'var isNode = {node_detection_code()};\n'
2548-
src += f'if (isNode) isWW = {node_ww_detection()}\n'
2549-
elif settings.ENVIRONMENT_MAY_BE_NODE:
2550-
src += f'var isWW = {node_ww_detection()}\n'
2551-
if settings.AUDIO_WORKLET:
2552-
src += "isWW ||= typeof AudioWorkletGlobalScope !== 'undefined';\n"
2553-
src += '// When running as a wasm worker, construct a new instance on startup\n'
2554-
if settings.MODULARIZE == 'instance':
2555-
src += 'isWW && init();\n'
2556-
else:
2557-
src += 'isWW && %s();\n' % settings.EXPORT_NAME
2558-
2441+
logger.debug(f'Modularizing, creating factory function called `{settings.EXPORT_NAME}`')
2442+
src = building.read_and_preprocess(utils.path_from_root('src/modularize.js'), expand_macros=True)
2443+
src = do_replace(src, '<<< INNER_JS_CODE >>>', read_file(final_js))
25592444
final_js += '.modular.js'
25602445
write_file(final_js, src)
25612446
shared.get_temp_files().note(final_js)

0 commit comments

Comments
 (0)