Add support for -sEXPORT_ES6/*.mjs on Node.js#17915
Add support for -sEXPORT_ES6/*.mjs on Node.js#17915RReverser merged 3 commits intoemscripten-core:mainfrom
-sEXPORT_ES6/*.mjs on Node.js#17915Conversation
840e1b5 to
a385c4a
Compare
8a2e576 to
bc619d3
Compare
EXPORT_ES6 is tested on Node.js-sEXPORT_ES6/*.mjs on Node.js
|
This should be ready for review, but I'm unsure how to proceed due to this Lines 205 to 208 in bc619d3 Also, perhaps we should swap all Line 3596 in bc619d3 But AFAIK that requires a lot more changes, and I'm not sure it's worth it. For example, should we then eagerly import the crypto module too? Lines 2229 to 2238 in f9d26b0 /cc @curiousdannii the creator of issue #11792. |
|
cc @brendandahl who might know this ES6 stuff? I'm afraid I don't know much about it myself... |
src/closure-externs/node-externs.js
Outdated
| /** | ||
| * @param {URL|string} url | ||
| */ | ||
| var fileURLToPath = function(url) {}; |
There was a problem hiding this comment.
According to the docs seem to exist on the url module, rather than at the top level..?
There was a problem hiding this comment.
They are, but they're imported into the module's global scope (see emcc.py).
There was a problem hiding this comment.
But shouldn't closure be able to see that then? It seems odd to me that we should need to declare these in this global location like this? Perhaps a local annotation where they are declared/imported?
There was a problem hiding this comment.
It does seem odd. Maybe closure doesn't understand await import('url')?
There was a problem hiding this comment.
Indeed, it's a bit odd that this is needed. I think this was necessary when targeting both web/node as well as node only. That is:
if (typeof process == 'object' &&
typeof process.versions == 'object' &&
typeof process.versions.node == 'string') {
const { createRequire } = await import('module');
var { pathToFileURL, fileURLToPath } = await import('url');
var require = createRequire(import.meta.url);
}(-sENVIRONMENT=web,node)
import { createRequire } from 'module';
import { pathToFileURL, fileURLToPath } from 'url';
const require = createRequire(import.meta.url);(-sENVIRONMENT=node)
Perhaps closure is run on an indeterminate file instead of the final JS file with these imports? Let me verify this.
There was a problem hiding this comment.
It seems that Closure indeed has some limitations with dynamic import expressions.
https://github.com/google/closure-compiler/wiki/JS-Modules#dynamic-import-expressions
I moved these externals to the correct file with commit 68377b6 and removed the need of this URL import() with commit bae5bf5.
| '': (True, [],), | ||
| 'no_import_meta': (False, ['-sUSE_ES6_IMPORT_META=0'],), | ||
| }) | ||
| @node_pthreads |
There was a problem hiding this comment.
The test links with -pthread, so Node.js before version 16 needs the --experimental-wasm-bulk-memory --experimental-wasm-threads flags to successfully execute the subdir/hello_world.mjs script.
|
This is a clever temporary solution, but I'm pretty sure it wouldn't help when the code is bundled, so it's not a full solution to #11792. Substantial parts of the JS output will need to be redesigned in order to be async, so as I've said before, before #11792 can be properly solved it will need direction from the core Emscripten team. |
Are you ok with landing this as a temporary fix? It seems like have some test coverage is better than none. |
|
I haven't tested it myself, but the idea is a good one, and it would definitely be good to get some more test coverage. |
Looks and works how'd I'd expect. |
Indeed, I think some bundlers will still fail on
Is there a design document of how this would look like? I suppose Emscripten needs to change this emit: var Module = (() => {
var _scriptDir = import.meta.url;
return (
function(Module) {
Module = Module || {};
...
return Module.ready
})();
export default Module;to: var Module = (() => {
var _scriptDir = import.meta.url;
return (
async function(Module) {
Module = Module || {};
...
return Module.ready
})();
export default Module;(i.e., second function needs To allow |
|
I resolved the above mentioned |
Actually... maybe best to do that as part of this PR? Then it wouldn't depend on top-level await, which is a relatively new feature in browsers (definitely newer than |
Ah, I think you're right. Let me fix that. I assumed that guarding the top-level await import for Node.js would prevent browsers from failing, but this probably didn't work for Node.js before v14.8 either. |
|
... should be fixed with commit e8f83e9. |
src/shell.js
Outdated
| // EXPORT_ES6 + ENVIRONMENT_IS_NODE always requires use of import.meta.url, | ||
| // since there's no way getting the current absolute path of the module when | ||
| // support for that is not available. | ||
| scriptDirectory = nodePath.dirname(nodeUrl.fileURLToPath(import.meta.url)); |
There was a problem hiding this comment.
This one I'm less sure about, but also worth checking if we can get away with just keeping the URL here.
There was a problem hiding this comment.
Indeed, I wasn't sure about this. I removed the fileURLToPath construction with commit cff5945, however, that may cause the locateFile function to return file:// strings on Node.js.
Lines 152 to 161 in 84a6341
Let's see if the CI likes that, though locally it still passes the
other.test_emcc_output_mjs* other.test_export_es6* tests.
There was a problem hiding this comment.
however, that may cause the
locateFilefunction to returnfile://strings on Node.js.
Now I'm really curious, if file:// strings are not accepted directly, then how would same strings returned from locateFile work...
There was a problem hiding this comment.
Now I'm really curious, if
file://strings are not accepted directly, then how would same strings returned fromlocateFilework...
IIUC, file:// strings are accepted after that isFileURI(filename) ? new URL(filename) ... change in read_, which will probably always be called on Node.js when reading files. But maybe it lacks some test coverage for this.
There was a problem hiding this comment.
Oh ok, that makes sense. (well, at least if nothing else uses the result of locateFile)
There was a problem hiding this comment.
There was a problem hiding this comment.
Now you're in a danger zone :) But if it works, great!
This allows us to run on `node`. 🥳 emscripten-core/emscripten#17915
|
I do have a small nit, though, which is that the imports are still not prefixed with |
I've created #18235 for this. |
|
I think I'd rather err on the side of compatibility with more tooling for now. |
Updating emsdk to v3.1.27 was necessary for emscripten-core/emscripten#17915, which allows using the same WASM build for both browser and nodejs envs.
Updating emsdk to v3.1.27 was necessary for emscripten-core/emscripten#17915, which allows using the same WASM build for both browser and nodejs envs.
Updating emsdk to v3.1.27 was necessary for emscripten-core/emscripten#17915, which allows using the same WASM build for both browser and nodejs envs.
Updating emsdk to v3.1.27 was necessary for emscripten-core/emscripten#17915, which allows using the same WASM build for both browser and nodejs envs.
Updating emsdk to v3.1.27 was necessary for emscripten-core/emscripten#17915, which allows using the same WASM build for both browser and nodejs envs.
Updating emsdk to v3.1.27 was necessary for emscripten-core/emscripten#17915, which allows using the same WASM build for both browser and nodejs envs.
Updating emsdk to v3.1.27 was necessary for emscripten-core/emscripten#17915, which allows using the same WASM build for both browser and nodejs envs.
Updating emsdk to v3.1.27 was necessary for emscripten-core/emscripten#17915, which allows using the same WASM build for both browser and nodejs envs.
* `EXPORT_ES6` and `ENVIRONMENT=*node*` requires `USE_ES6_IMPORT_META` to be set. This changed in emscripten-core/emscripten#17915. * `_malloc` and `_free` must be declared as exported functions to avoid removal by the compiler. * Remove [`WASM_ASYNC_COMPILATION=0`](https://emsettings.surma.technology/#WASM_ASYNC_COMPILATION). The current code was broken by emscripten-core/emscripten#12650. Previously, `WASM_ASYNC_COMPILATION=0` would return `Promise<Module>`, but that PR made it return `Module` directly. (Took ages to find the cause of this!) Removing the flag to keep it async avoids a breaking change in this library. Node.js v14.8.0 and later support top-level await, so async isn't that ugly. Alternatively, the Node.js version could easily be changed to load synchronously. Node.js has no limit on the size of synchronously loaded WebAssembly modules, unlike Chromium.
* `EXPORT_ES6` and `ENVIRONMENT=*node*` requires `USE_ES6_IMPORT_META` to be set. This changed in emscripten-core/emscripten#17915. * `_malloc` and `_free` must be declared as exported functions to avoid removal by the compiler. * Remove [`WASM_ASYNC_COMPILATION=0`](https://emsettings.surma.technology/#WASM_ASYNC_COMPILATION). The current code was broken by emscripten-core/emscripten#12650. Previously, `WASM_ASYNC_COMPILATION=0` would return `Promise<Module>`, but that PR made it return `Module` directly. (Took ages to find the cause of this!) Removing the flag to keep it async avoids a breaking change in this library. Node.js v14.8.0 and later support top-level await, so async isn't that ugly. Alternatively, the Node.js version could easily be changed to load synchronously. Node.js has no limit on the size of synchronously loaded WebAssembly modules, unlike Chromium.
|
Hello this is a neat new feature but I don't see any documentation of it anywhere. |
This allows us to run on `node`. 🥳 emscripten-core/emscripten#17915
This allows us to run on `node`. 🥳 emscripten-core/emscripten#17915
As described in #11792,
require()and__dirnamedoesn't exist inan ES6 module. Emscripten uses this to import built-in core Node.js
modules. For example, the
node:fsmodule is used for synchronouslyimporting the
*.wasmbinary, when not linking with-sSINGLE_FILE.To work around this, ES6 modules on Node.js may import
createRequire()fromnode:moduleto construct therequire()function, allowing modules to be imported in a CommonJS manner.
Emscripten targets a variety of environments, which can be
categorized as:
-sENVIRONMENT=*is not specified at link time.For use case (1), this commit ensures that an
asyncfunction isemitted, allowing Node.js modules to be dynamically imported. This is
necessary given that static import declarations cannot be put in
conditionals. Inside the module, for Node.js only, it's using the
above-mentioned
createRequire()-construction.For use case (2), when only Node.js is targeted, a static import
declaration utilize the same
createRequire()-construction.For both use cases,
-sUSE_ES6_IMPORT_META=0is not allowed, whenNode.js is among the targets, since it is not possible to mimic
__dirnamewhenimport.metasupport is not available.This commit does not change anything for use case (2), when only the
web is targeted (
-sENVIRONMENT=web).Resolves: #11792.
Resolves: #17797.