Skip to content

Commit 2430b4f

Browse files
committed
Add support for -sEXPORT_ES6/*.mjs on Node.js
See: #11792.
1 parent f9d26b0 commit 2430b4f

File tree

6 files changed

+90
-29
lines changed

6 files changed

+90
-29
lines changed

emcc.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3582,6 +3582,35 @@ def preprocess_wasm2js_script():
35823582
write_file(final_js, js)
35833583

35843584

3585+
def node_es6_imports():
3586+
if not settings.EXPORT_ES6 or not shared.target_environment_may_be('node'):
3587+
return ''
3588+
3589+
if settings.USE_ES6_IMPORT_META:
3590+
import_script_url = 'import.meta.url'
3591+
else:
3592+
import_script_url = "pathToFileURL('.')"
3593+
3594+
# Avoid Node specific imports in browser.
3595+
# TODO: Swap all `require()`'s with `import()`'s?
3596+
if shared.target_environment_may_be('web'):
3597+
return '''
3598+
if (typeof process == 'object' &&
3599+
typeof process.versions == 'object' &&
3600+
typeof process.versions.node == 'string') {
3601+
const { createRequire } = await import('module');
3602+
var { pathToFileURL, fileURLToPath } = await import('url');
3603+
var require = createRequire(%s);
3604+
}
3605+
''' % import_script_url
3606+
else:
3607+
return '''
3608+
import { createRequire } from 'module';
3609+
import { pathToFileURL, fileURLToPath } from 'url';
3610+
const require = createRequire(%s);
3611+
''' % import_script_url
3612+
3613+
35853614
def modularize():
35863615
global final_js
35873616
logger.debug(f'Modularizing, assigning to var {settings.EXPORT_NAME}')
@@ -3612,24 +3641,25 @@ def modularize():
36123641
# document.currentScript, so a simple export declaration is enough.
36133642
src = 'var %s=%s' % (settings.EXPORT_NAME, src)
36143643
else:
3615-
script_url_node = ""
3644+
script_url_node = ''
36163645
# When MODULARIZE this JS may be executed later,
36173646
# after document.currentScript is gone, so we save it.
36183647
# In EXPORT_ES6 + USE_PTHREADS the 'thread' is actually an ES6 module webworker running in strict mode,
36193648
# so doesn't have access to 'document'. In this case use 'import.meta' instead.
36203649
if settings.EXPORT_ES6 and settings.USE_ES6_IMPORT_META:
3621-
script_url = "import.meta.url"
3650+
script_url = 'import.meta.url'
36223651
else:
36233652
script_url = "typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined"
36243653
if shared.target_environment_may_be('node'):
36253654
script_url_node = "if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;"
3626-
src = '''
3655+
src = '''%(node_imports)s
36273656
var %(EXPORT_NAME)s = (() => {
36283657
var _scriptDir = %(script_url)s;
36293658
%(script_url_node)s
36303659
return (%(src)s);
36313660
})();
36323661
''' % {
3662+
'node_imports': node_es6_imports(),
36333663
'EXPORT_NAME': settings.EXPORT_NAME,
36343664
'script_url': script_url,
36353665
'script_url_node': script_url_node,

src/closure-externs/node-externs.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,13 @@ Buffer.prototype.slice = function(start, end) {};
111111
* @nosideeffects
112112
*/
113113
Buffer.prototype.toString = function(encoding, start, end) {};
114+
115+
/**
116+
* @param {string} path
117+
*/
118+
var pathToFileURL = function(path) {};
119+
120+
/**
121+
* @param {URL|string} url
122+
*/
123+
var fileURLToPath = function(url) {};

src/node_shell_read.js

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,6 @@
44
* SPDX-License-Identifier: MIT
55
*/
66

7-
// These modules will usually be used on Node.js. Load them eagerly to avoid
8-
// the complexity of lazy-loading. However, for now we must guard on require()
9-
// actually existing: if the JS is put in a .mjs file (ES6 module) and run on
10-
// node, then we'll detect node as the environment and get here, but require()
11-
// does not exist (since ES6 modules should use |import|). If the code actually
12-
// uses the node filesystem then it will crash, of course, but in the case of
13-
// code that never uses it we don't want to crash here, so the guarding if lets
14-
// such code work properly. See discussion in
15-
// https://github.com/emscripten-core/emscripten/pull/17851
16-
var fs, nodePath;
17-
if (typeof require === 'function') {
18-
fs = require('fs');
19-
nodePath = require('path');
20-
}
21-
227
read_ = (filename, binary) => {
238
#if SUPPORT_BASE64_EMBEDDING
249
var ret = tryParseAsDataURI(filename);

src/preamble.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,13 @@ if (Module['locateFile']) {
649649
#if EXPORT_ES6 && USE_ES6_IMPORT_META && !SINGLE_FILE // in single-file mode, repeating WASM_BINARY_FILE would emit the contents again
650650
} else {
651651
// Use bundler-friendly `new URL(..., import.meta.url)` pattern; works in browsers too.
652-
wasmBinaryFile = new URL('{{{ WASM_BINARY_FILE }}}', import.meta.url).toString();
652+
wasmBinaryFile = new URL('{{{ WASM_BINARY_FILE }}}', import.meta.url);
653+
#if ENVIRONMENT_MAY_BE_NODE
654+
if (ENVIRONMENT_IS_NODE) {
655+
wasmBinaryFile = fileURLToPath(wasmBinaryFile);
656+
} else
657+
#endif
658+
wasmBinaryFile = wasmBinaryFile.href;
653659
}
654660
#endif
655661

src/shell.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,25 @@ if (ENVIRONMENT_IS_NODE) {
193193
if (typeof process == 'undefined' || !process.release || process.release.name !== 'node') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)');
194194
#endif
195195
#endif
196+
// These modules will usually be used on Node.js. Load them eagerly to avoid
197+
// the complexity of lazy-loading.
198+
var fs = require('fs');
199+
var nodePath = require('path');
200+
196201
if (ENVIRONMENT_IS_WORKER) {
197-
scriptDirectory = require('path').dirname(scriptDirectory) + '/';
202+
scriptDirectory = nodePath.dirname(scriptDirectory);
198203
} else {
199-
scriptDirectory = __dirname + '/';
204+
#if EXPORT_ES6
205+
// FIXME: How about the -sUSE_ES6_IMPORT_META=0 case? Is there a way of
206+
// getting the current absolute path of the module when support for
207+
// `import.meta.url` is not available?
208+
scriptDirectory = nodePath.dirname(fileURLToPath(import.meta.url));
209+
#else
210+
scriptDirectory = __dirname;
211+
#endif
200212
}
213+
// Ensure trailing slash
214+
scriptDirectory += '/';
201215

202216
#include "node_shell_read.js"
203217

test/test_other.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -237,41 +237,57 @@ def test_emcc_generate_config(self):
237237
self.assertContained('LLVM_ROOT', config_contents)
238238
os.remove(config_path)
239239

240-
def test_emcc_output_mjs(self):
241-
self.run_process([EMCC, '-o', 'hello_world.mjs', test_file('hello_world.c')])
242-
output = read_file('hello_world.mjs')
243-
self.assertContained('export default Module;', output)
244-
# TODO(sbc): Test that this is actually runnable. We currently don't have
245-
# any tests for EXPORT_ES6 but once we do this should be enabled.
246-
# self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
240+
@parameterized({
241+
'': ([],),
242+
'node': (['-sENVIRONMENT=node'],),
243+
})
244+
def test_emcc_output_mjs(self, args):
245+
create_file('extern-post.js', 'await Module();')
246+
self.run_process([EMCC, '-o', 'hello_world.mjs',
247+
'--extern-post-js', 'extern-post.js',
248+
test_file('hello_world.c')] + args)
249+
src = read_file('hello_world.mjs')
250+
self.assertContained('export default Module;', src)
251+
self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
247252

248253
@parameterized({
249254
'': (True, [],),
250255
'no_import_meta': (False, ['-sUSE_ES6_IMPORT_META=0'],),
251256
})
257+
@node_pthreads
252258
def test_emcc_output_worker_mjs(self, has_import_meta, args):
259+
create_file('extern-post.js', 'await Module();')
253260
os.mkdir('subdir')
254261
self.run_process([EMCC, '-o', 'subdir/hello_world.mjs', '-pthread', '-O1',
262+
'--extern-post-js', 'extern-post.js',
255263
test_file('hello_world.c')] + args)
256264
src = read_file('subdir/hello_world.mjs')
257265
self.assertContainedIf("new URL('hello_world.wasm', import.meta.url)", src, condition=has_import_meta)
258266
self.assertContainedIf("new Worker(new URL('hello_world.worker.js', import.meta.url))", src, condition=has_import_meta)
259267
self.assertContained('export default Module;', src)
260268
src = read_file('subdir/hello_world.worker.js')
261269
self.assertContained('import("./hello_world.mjs")', src)
270+
self.assertContained('hello, world!', self.run_js('subdir/hello_world.mjs'))
262271

272+
@node_pthreads
263273
def test_emcc_output_worker_mjs_single_file(self):
274+
create_file('extern-post.js', 'await Module();')
264275
self.run_process([EMCC, '-o', 'hello_world.mjs', '-pthread',
276+
'--extern-post-js', 'extern-post.js',
265277
test_file('hello_world.c'), '-sSINGLE_FILE'])
266278
src = read_file('hello_world.mjs')
267279
self.assertNotContained("new URL('data:", src)
268280
self.assertContained("new Worker(new URL('hello_world.worker.js', import.meta.url))", src)
281+
self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
269282

270283
def test_emcc_output_mjs_closure(self):
284+
create_file('extern-post.js', 'await Module();')
271285
self.run_process([EMCC, '-o', 'hello_world.mjs',
272-
test_file('hello_world.c'), '--closure=1'])
286+
'--extern-post-js', 'extern-post.js',
287+
test_file('hello_world.c'), '--closure=1'])
273288
src = read_file('hello_world.mjs')
274289
self.assertContained('new URL("hello_world.wasm", import.meta.url)', src)
290+
self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
275291

276292
def test_export_es6_implies_modularize(self):
277293
self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_ES6'])

0 commit comments

Comments
 (0)