Skip to content

Commit d2d6abf

Browse files
committed
Simplify _scriptName / scriptDirectory handling
- Avoid defining `_scriptName` outside the wrapper function in `EXPORT_ES6` mode and for Node.js CommonJS builds. - Simplify `scriptDirectory` resolution to `new URL('.', _scriptName).href` on the web.
1 parent fdb190d commit d2d6abf

File tree

7 files changed

+88
-57
lines changed

7 files changed

+88
-57
lines changed

src/closure-externs/modularize-externs.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// Due to the way MODULARIZE works, Closure is run on generated code that does not define _scriptName,
2-
// but only after MODULARIZE has finished, _scriptName is injected to the generated code.
3-
// Therefore it cannot be minified.
1+
// In MODULARIZE mode the JS code may be executed later, after `document.currentScript` is gone, so we store
2+
// it to `_scriptName` outside the wrapper function. Therefore, it cannot be minified.
3+
// In EXPORT_ES6 mode we use `import.meta.url` and for Node.js CommonJS builds we use `__filename`.
4+
45
/**
56
* @suppress {duplicate, undefinedVars}
67
*/

src/shell.js

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -139,24 +139,32 @@ var quit_ = (status, toThrow) => {
139139
throw toThrow;
140140
};
141141

142-
#if SHARED_MEMORY && !MODULARIZE
142+
#if EXPORT_ES6
143+
var _scriptName = import.meta.url;
144+
#else
145+
#if ENVIRONMENT_MAY_BE_WEB
146+
#if !MODULARIZE
143147
// In MODULARIZE mode _scriptName needs to be captured already at the very top of the page immediately when the page is parsed, so it is generated there
144148
// before the page load. In non-MODULARIZE modes generate it here.
145-
var _scriptName = (typeof document != 'undefined') ? document.currentScript?.src : undefined;
149+
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
150+
#endif // !MODULARIZE
151+
#elif ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_WORKER
152+
var _scriptName;
153+
#endif // ENVIRONMENT_MAY_BE_WEB
146154

147155
#if ENVIRONMENT_MAY_BE_NODE
148-
if (ENVIRONMENT_IS_NODE) {
149-
#if EXPORT_ES6
150-
_scriptName = typeof __filename != 'undefined' ? __filename : import.meta.url
151-
#else
156+
if (typeof __filename != 'undefined') { // Node
152157
_scriptName = __filename;
153-
#endif
154158
} else
155159
#endif // ENVIRONMENT_MAY_BE_NODE
160+
#if ENVIRONMENT_MAY_BE_WORKER
156161
if (ENVIRONMENT_IS_WORKER) {
157162
_scriptName = self.location.href;
158163
}
159-
#endif // SHARED_MEMORY && !MODULARIZE
164+
#elif ENVIRONMENT_MAY_BE_NODE
165+
/*no-op*/{}
166+
#endif // ENVIRONMENT_MAY_BE_WORKER
167+
#endif // EXPORT_ES6
160168

161169
// `/` should be present at the end if `scriptDirectory` is not empty
162170
var scriptDirectory = '';
@@ -197,10 +205,7 @@ if (ENVIRONMENT_IS_NODE) {
197205
var nodePath = require('path');
198206

199207
#if EXPORT_ES6
200-
// EXPORT_ES6 + ENVIRONMENT_IS_NODE always requires use of import.meta.url,
201-
// since there's no way getting the current absolute path of the module when
202-
// support for that is not available.
203-
if (!import.meta.url.startsWith('data:')) {
208+
if (import.meta.url.startsWith('file:')) {
204209
scriptDirectory = nodePath.dirname(require('url').fileURLToPath(import.meta.url)) + '/';
205210
}
206211
#else
@@ -335,28 +340,11 @@ if (ENVIRONMENT_IS_SHELL) {
335340
// ENVIRONMENT_IS_NODE.
336341
#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
337342
if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
338-
if (ENVIRONMENT_IS_WORKER) { // Check worker, not web, since window could be polyfilled
339-
scriptDirectory = self.location.href;
340-
} else if (typeof document != 'undefined' && document.currentScript) { // web
341-
scriptDirectory = document.currentScript.src;
342-
}
343-
#if MODULARIZE
344-
// When MODULARIZE, this JS may be executed later, after document.currentScript
345-
// is gone, so we saved it, and we use it here instead of any other info.
346-
if (_scriptName) {
347-
scriptDirectory = _scriptName;
348-
}
349-
#endif
350-
// blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them.
351-
// otherwise, slice off the final part of the url to find the script directory.
352-
// if scriptDirectory does not contain a slash, lastIndexOf will return -1,
353-
// and scriptDirectory will correctly be replaced with an empty string.
354-
// If scriptDirectory contains a query (starting with ?) or a fragment (starting with #),
355-
// they are removed because they could contain a slash.
356-
if (scriptDirectory.startsWith('blob:')) {
357-
scriptDirectory = '';
358-
} else {
359-
scriptDirectory = scriptDirectory.slice(0, scriptDirectory.replace(/[?#].*/, '').lastIndexOf('/')+1);
343+
try {
344+
scriptDirectory = new URL('.', _scriptName).href; // includes trailing slash
345+
} catch {
346+
// Must be a `blob:` or `data:` URL (e.g. `blob:http://site.com/etc/etc`), we cannot
347+
// infer anything from them.
360348
}
361349

362350
#if ENVIRONMENT && ASSERTIONS

src/shell_minimal.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ var ENVIRONMENT_IS_PTHREAD = ENVIRONMENT_IS_WORKER && self.name?.startsWith('em-
134134
#if !MODULARIZE
135135
// In MODULARIZE mode _scriptName needs to be captured already at the very top of the page immediately when the page is parsed, so it is generated there
136136
// before the page load. In non-MODULARIZE modes generate it here.
137-
var _scriptName = (typeof document != 'undefined') ? document.currentScript?.src : undefined;
137+
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
138138
#endif
139139

140140
#if ENVIRONMENT_MAY_BE_NODE
@@ -145,9 +145,11 @@ if (ENVIRONMENT_IS_NODE) {
145145
// Under node we set `workerData` to `em-pthread` to signal that the worker
146146
// is hosting a pthread.
147147
ENVIRONMENT_IS_PTHREAD = ENVIRONMENT_IS_WORKER && worker_threads['workerData'] == 'em-pthread'
148+
#if !EXPORT_ES6
148149
_scriptName = __filename;
149-
} else
150150
#endif
151+
} else
152+
#endif // ENVIRONMENT_MAY_BE_NODE
151153
if (ENVIRONMENT_IS_WORKER) {
152154
_scriptName = self.location.href;
153155
}

src/wasm_worker.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ if (ENVIRONMENT_IS_NODE) {
3434
Object.assign(global, {
3535
self: global,
3636
require,
37+
#if !EXPORT_ES6
38+
// `vm.runInThisContext` global scope lacks `__filename` and `__dirname`
3739
__filename,
3840
__dirname,
41+
#endif
3942
Worker: nodeWorkerThreads.Worker,
4043
importScripts: (f) => vm.runInThisContext(fs.readFileSync(f, 'utf8'), {filename: f}),
4144
postMessage: (msg) => parentPort.postMessage(msg),

test/test_other.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15895,3 +15895,48 @@ def test_mainScriptUrlOrBlob(self, es6):
1589515895
# Now create foo.[m]js and the program should run as expected.
1589615896
shutil.copy(outfile, ('foo.%s' % ext))
1589715897
self.assertContained('hello, world', self.run_js(outfile))
15898+
15899+
@parameterized({
15900+
'': ([],),
15901+
'node': (['-sENVIRONMENT=node'],),
15902+
})
15903+
def test_locate_file_abspath(self, args):
15904+
# Verify that `scriptDirectory` is an absolute path
15905+
create_file('pre.js', '''
15906+
Module['locateFile'] = (fileName, scriptDirectory) => {
15907+
assert(nodePath['isAbsolute'](scriptDirectory), `scriptDirectory (${scriptDirectory}) should be an absolute path`);
15908+
return scriptDirectory + fileName;
15909+
};
15910+
''')
15911+
self.do_runf('hello_world.c', 'hello, world!', emcc_args=['--pre-js', 'pre.js'] + args)
15912+
15913+
@parameterized({
15914+
'': ([],),
15915+
'node': (['-sENVIRONMENT=node'],),
15916+
})
15917+
def test_locate_file_abspath_esm(self, args):
15918+
# Verify that `scriptDirectory` is an absolute path when `EXPORT_ES6`
15919+
create_file('pre.js', '''
15920+
Module['locateFile'] = (fileName, scriptDirectory) => {
15921+
assert(nodePath['isAbsolute'](scriptDirectory), `scriptDirectory (${scriptDirectory}) should be an absolute path`);
15922+
return scriptDirectory + fileName;
15923+
};
15924+
''')
15925+
self.run_process([EMCC, '-o', 'hello_world.mjs',
15926+
'--pre-js', 'pre.js',
15927+
'--extern-post-js', test_file('modularize_post_js.js'),
15928+
test_file('hello_world.c')] + args)
15929+
self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
15930+
15931+
@node_pthreads
15932+
def test_locate_file_abspath_pthread(self):
15933+
# Verify that `scriptDirectory` is an absolute path when `ENVIRONMENT_IS_WORKER`
15934+
self.set_setting('PROXY_TO_PTHREAD')
15935+
self.set_setting('EXIT_RUNTIME')
15936+
create_file('pre.js', '''
15937+
Module['locateFile'] = (fileName, scriptDirectory) => {
15938+
assert(nodePath['isAbsolute'](scriptDirectory), `scriptDirectory (${scriptDirectory}) should be an absolute path`);
15939+
return scriptDirectory + fileName;
15940+
};
15941+
''')
15942+
self.do_runf('hello_world.c', 'hello, world!', emcc_args=['--pre-js', 'pre.js'])

tools/building.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ def closure_compiler(filename, advanced=True, extra_closure_args=None):
555555
# should not minify these symbol names.
556556
CLOSURE_EXTERNS = [path_from_root('src/closure-externs/closure-externs.js')]
557557

558-
if settings.MODULARIZE:
558+
if settings.MODULARIZE and settings.ENVIRONMENT_MAY_BE_WEB and not settings.EXPORT_ES6:
559559
CLOSURE_EXTERNS += [path_from_root('src/closure-externs/modularize-externs.js')]
560560

561561
if settings.USE_WEBGPU:

tools/link.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2459,30 +2459,22 @@ def modularize():
24592459
# document.currentScript, so a simple export declaration is enough.
24602460
src = f'var {settings.EXPORT_NAME} = {wrapper_function};'
24612461
else:
2462-
script_url_node = ''
2462+
script_url_web = ''
24632463
# When MODULARIZE this JS may be executed later,
24642464
# after document.currentScript is gone, so we save it.
2465-
# In EXPORT_ES6 + PTHREADS the 'thread' is actually an ES6 module
2466-
# webworker running in strict mode, so doesn't have access to 'document'.
2467-
# In this case use 'import.meta' instead.
2468-
if settings.EXPORT_ES6:
2469-
script_url = 'import.meta.url'
2470-
else:
2471-
script_url = "typeof document != 'undefined' ? document.currentScript?.src : undefined"
2472-
if settings.ENVIRONMENT_MAY_BE_NODE:
2473-
script_url_node = "if (typeof __filename != 'undefined') _scriptName = _scriptName || __filename;"
2465+
# In EXPORT_ES6 mode we can just use 'import.meta.url'.
2466+
if settings.ENVIRONMENT_MAY_BE_WEB and not settings.EXPORT_ES6:
2467+
script_url_web = "var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;"
24742468
src = '''\
24752469
var %(EXPORT_NAME)s = (() => {
2476-
var _scriptName = %(script_url)s;
2477-
%(script_url_node)s
2470+
%(script_url_web)s
24782471
return (%(wrapper_function)s);
24792472
})();
24802473
''' % {
2481-
'EXPORT_NAME': settings.EXPORT_NAME,
2482-
'script_url': script_url,
2483-
'script_url_node': script_url_node,
2484-
'wrapper_function': wrapper_function,
2485-
}
2474+
'EXPORT_NAME': settings.EXPORT_NAME,
2475+
'script_url_web': script_url_web,
2476+
'wrapper_function': wrapper_function,
2477+
}
24862478

24872479
if settings.SOURCE_PHASE_IMPORTS:
24882480
src = f"import source wasmModule from './{settings.WASM_BINARY_FILE}';\n\n" + src

0 commit comments

Comments
 (0)