Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/esm.md36
-rw-r--r--lib/internal/modules/cjs/loader.js2
-rw-r--r--lib/internal/modules/esm/create_dynamic_module.js18
-rw-r--r--lib/internal/modules/esm/default_resolve.js21
-rw-r--r--lib/internal/modules/esm/loader.js2
-rw-r--r--lib/internal/modules/esm/translators.js41
-rw-r--r--src/node_options.cc9
-rw-r--r--src/node_options.h1
-rw-r--r--test/es-module/test-esm-wasm.mjs15
-rw-r--r--test/fixtures/es-modules/simple.wasmbin0 -> 136 bytes
-rw-r--r--test/fixtures/es-modules/simple.wat23
-rw-r--r--test/fixtures/es-modules/wasm-dep.mjs13
12 files changed, 147 insertions, 34 deletions
diff --git a/doc/api/esm.md b/doc/api/esm.md
index 2d1d4085e73..60f9f6787db 100644
--- a/doc/api/esm.md
+++ b/doc/api/esm.md
@@ -441,6 +441,30 @@ node --experimental-modules index.mjs # fails
node --experimental-modules --experimental-json-modules index.mjs # works
```
+## Experimental Wasm Modules
+
+Importing Web Assembly modules is supported under the
+`--experimental-wasm-modules` flag, allowing any `.wasm` files to be
+imported as normal modules while also supporting their module imports.
+
+This integration is in line with the
+[ES Module Integration Proposal for Web Assembly][].
+
+For example, an `index.mjs` containing:
+
+```js
+import * as M from './module.wasm';
+console.log(M);
+```
+
+executed under:
+
+```bash
+node --experimental-modules --experimental-wasm-modules index.mjs
+```
+
+would provide the exports interface for the instantiation of `module.wasm`.
+
## Experimental Loader hooks
**Note: This API is currently being redesigned and will still change.**
@@ -484,11 +508,12 @@ module. This can be one of the following:
| `format` | Description |
| --- | --- |
-| `'module'` | Load a standard JavaScript module |
-| `'commonjs'` | Load a Node.js CommonJS module |
| `'builtin'` | Load a Node.js builtin module |
-| `'json'` | Load a JSON file |
+| `'commonjs'` | Load a Node.js CommonJS module |
| `'dynamic'` | Use a [dynamic instantiate hook][] |
+| `'json'` | Load a JSON file |
+| `'module'` | Load a standard JavaScript module |
+| `'wasm'` | Load a WebAssembly module |
For example, a dummy loader to load JavaScript restricted to browser resolution
rules with only JS file extension and Node.js builtin modules support could
@@ -585,8 +610,8 @@ format for that resolved URL given by the **ESM_FORMAT** routine.
The _"module"_ format is returned for an ECMAScript Module, while the
_"commonjs"_ format is used to indicate loading through the legacy
-CommonJS loader. Additional formats such as _"wasm"_ or _"addon"_ can be
-extended in future updates.
+CommonJS loader. Additional formats such as _"addon"_ can be extended in future
+updates.
In the following algorithms, all subroutine errors are propagated as errors
of these top-level routines.
@@ -739,5 +764,6 @@ success!
[ECMAScript-modules implementation]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
[WHATWG JSON modules]: https://github.com/whatwg/html/issues/4315
+[ES Module Integration Proposal for Web Assembly]: https://github.com/webassembly/esm-integration
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
[the official standard format]: https://tc39.github.io/ecma262/#sec-modules
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index 5d4ae26c02e..2798032fd96 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -658,7 +658,7 @@ Module.prototype.load = function(filename) {
url,
new ModuleJob(ESMLoader, url, async () => {
return createDynamicModule(
- ['default'], url, (reflect) => {
+ [], ['default'], url, (reflect) => {
reflect.exports.default.set(exports);
});
})
diff --git a/lib/internal/modules/esm/create_dynamic_module.js b/lib/internal/modules/esm/create_dynamic_module.js
index eea01bed31e..45f964d5ad8 100644
--- a/lib/internal/modules/esm/create_dynamic_module.js
+++ b/lib/internal/modules/esm/create_dynamic_module.js
@@ -1,14 +1,18 @@
'use strict';
-const { ArrayPrototype } = primordials;
+const { ArrayPrototype, JSON, Object } = primordials;
const debug = require('internal/util/debuglog').debuglog('esm');
-const createDynamicModule = (exports, url = '', evaluate) => {
+const createDynamicModule = (imports, exports, url = '', evaluate) => {
debug('creating ESM facade for %s with exports: %j', url, exports);
const names = ArrayPrototype.map(exports, (name) => `${name}`);
const source = `
+${ArrayPrototype.join(ArrayPrototype.map(imports, (impt, index) =>
+ `import * as $import_${index} from ${JSON.stringify(impt)};
+import.meta.imports[${JSON.stringify(impt)}] = $import_${index};`), '\n')
+}
${ArrayPrototype.join(ArrayPrototype.map(names, (name) =>
`let $${name};
export { $${name} as ${name} };
@@ -22,19 +26,21 @@ import.meta.done();
`;
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
const m = new ModuleWrap(source, `${url}`);
- m.link(() => 0);
- m.instantiate();
const readyfns = new Set();
const reflect = {
- namespace: m.namespace(),
- exports: {},
+ exports: Object.create(null),
onReady: (cb) => { readyfns.add(cb); },
};
+ if (imports.length)
+ reflect.imports = Object.create(null);
+
callbackMap.set(m, {
initializeImportMeta: (meta, wrap) => {
meta.exports = reflect.exports;
+ if (reflect.imports)
+ meta.imports = reflect.imports;
meta.done = () => {
evaluate(reflect);
reflect.onReady = (cb) => cb(reflect);
diff --git a/lib/internal/modules/esm/default_resolve.js b/lib/internal/modules/esm/default_resolve.js
index a83cf9c6756..67b8db716c5 100644
--- a/lib/internal/modules/esm/default_resolve.js
+++ b/lib/internal/modules/esm/default_resolve.js
@@ -10,17 +10,14 @@ const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalJsonModules = getOptionValue('--experimental-json-modules');
const typeFlag = getOptionValue('--input-type');
-
+const experimentalWasmModules = getOptionValue('--experimental-wasm-modules');
const { resolve: moduleWrapResolve,
getPackageType } = internalBinding('module_wrap');
const { pathToFileURL, fileURLToPath } = require('internal/url');
const { ERR_INPUT_TYPE_NOT_ALLOWED,
ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
-const {
- Object,
- SafeMap
-} = primordials;
+const { SafeMap } = primordials;
const realpathCache = new SafeMap();
@@ -44,15 +41,11 @@ const legacyExtensionFormatMap = {
'.node': 'commonjs'
};
-if (experimentalJsonModules) {
- // This is a total hack
- Object.assign(extensionFormatMap, {
- '.json': 'json'
- });
- Object.assign(legacyExtensionFormatMap, {
- '.json': 'json'
- });
-}
+if (experimentalWasmModules)
+ extensionFormatMap['.wasm'] = legacyExtensionFormatMap['.wasm'] = 'wasm';
+
+if (experimentalJsonModules)
+ extensionFormatMap['.json'] = legacyExtensionFormatMap['.json'] = 'json';
function resolve(specifier, parentURL) {
if (NativeModule.canBeRequiredByUsers(specifier)) {
diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js
index f752550d12c..0ea1e6f4e56 100644
--- a/lib/internal/modules/esm/loader.js
+++ b/lib/internal/modules/esm/loader.js
@@ -153,7 +153,7 @@ class Loader {
loaderInstance = async (url) => {
debug(`Translating dynamic ${url}`);
const { exports, execute } = await this._dynamicInstantiate(url);
- return createDynamicModule(exports, url, (reflect) => {
+ return createDynamicModule([], exports, url, (reflect) => {
debug(`Loading dynamic ${url}`);
execute(reflect.exports);
});
diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js
index 72350fb2b2c..4ca9be4d622 100644
--- a/lib/internal/modules/esm/translators.js
+++ b/lib/internal/modules/esm/translators.js
@@ -1,9 +1,12 @@
'use strict';
+/* global WebAssembly */
+
const {
+ JSON,
+ Object,
SafeMap,
- StringPrototype,
- JSON
+ StringPrototype
} = primordials;
const { NativeModule } = require('internal/bootstrap/loaders');
@@ -72,11 +75,11 @@ translators.set('commonjs', async function commonjsStrategy(url, isMain) {
];
if (module && module.loaded) {
const exports = module.exports;
- return createDynamicModule(['default'], url, (reflect) => {
+ return createDynamicModule([], ['default'], url, (reflect) => {
reflect.exports.default.set(exports);
});
}
- return createDynamicModule(['default'], url, () => {
+ return createDynamicModule([], ['default'], url, () => {
debug(`Loading CJSModule ${url}`);
// We don't care about the return val of _load here because Module#load
// will handle it for us by checking the loader registry and filling the
@@ -97,7 +100,7 @@ translators.set('builtin', async function builtinStrategy(url) {
}
module.compileForPublicLoader(true);
return createDynamicModule(
- [...module.exportKeys, 'default'], url, (reflect) => {
+ [], [...module.exportKeys, 'default'], url, (reflect) => {
debug(`Loading BuiltinModule ${url}`);
module.reflect = reflect;
for (const key of module.exportKeys)
@@ -116,7 +119,7 @@ translators.set('json', async function jsonStrategy(url) {
let module = CJSModule._cache[modulePath];
if (module && module.loaded) {
const exports = module.exports;
- return createDynamicModule(['default'], url, (reflect) => {
+ return createDynamicModule([], ['default'], url, (reflect) => {
reflect.exports.default.set(exports);
});
}
@@ -136,8 +139,32 @@ translators.set('json', async function jsonStrategy(url) {
throw err;
}
CJSModule._cache[modulePath] = module;
- return createDynamicModule(['default'], url, (reflect) => {
+ return createDynamicModule([], ['default'], url, (reflect) => {
debug(`Parsing JSONModule ${url}`);
reflect.exports.default.set(module.exports);
});
});
+
+// Strategy for loading a wasm module
+translators.set('wasm', async function(url) {
+ const pathname = fileURLToPath(url);
+ const buffer = await readFileAsync(pathname);
+ debug(`Translating WASMModule ${url}`);
+ let compiled;
+ try {
+ compiled = await WebAssembly.compile(buffer);
+ } catch (err) {
+ err.message = pathname + ': ' + err.message;
+ throw err;
+ }
+
+ const imports =
+ WebAssembly.Module.imports(compiled).map(({ module }) => module);
+ const exports = WebAssembly.Module.exports(compiled).map(({ name }) => name);
+
+ return createDynamicModule(imports, exports, url, (reflect) => {
+ const { exports } = new WebAssembly.Instance(compiled, reflect.imports);
+ for (const expt of Object.keys(exports))
+ reflect.exports[expt].set(exports[expt]);
+ });
+});
diff --git a/src/node_options.cc b/src/node_options.cc
index a36666c3e0f..12e0cb09d08 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -122,6 +122,11 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
"--experimental-modules be enabled");
}
+ if (experimental_wasm_modules && !experimental_modules) {
+ errors->push_back("--experimental-wasm-modules requires "
+ "--experimental-modules be enabled");
+ }
+
if (!es_module_specifier_resolution.empty()) {
if (!experimental_modules) {
errors->push_back("--es-module-specifier-resolution requires "
@@ -274,6 +279,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"experimental ES Module support and caching modules",
&EnvironmentOptions::experimental_modules,
kAllowedInEnvironment);
+ AddOption("--experimental-wasm-modules",
+ "experimental ES Module support for webassembly modules",
+ &EnvironmentOptions::experimental_wasm_modules,
+ kAllowedInEnvironment);
AddOption("--experimental-policy",
"use the specified file as a "
"security policy",
diff --git a/src/node_options.h b/src/node_options.h
index db564ddb3d3..b0a1844df58 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -94,6 +94,7 @@ class EnvironmentOptions : public Options {
bool experimental_json_modules = false;
bool experimental_modules = false;
std::string es_module_specifier_resolution;
+ bool experimental_wasm_modules = false;
std::string module_type;
std::string experimental_policy;
bool experimental_repl_await = false;
diff --git a/test/es-module/test-esm-wasm.mjs b/test/es-module/test-esm-wasm.mjs
new file mode 100644
index 00000000000..bcfce797a9c
--- /dev/null
+++ b/test/es-module/test-esm-wasm.mjs
@@ -0,0 +1,15 @@
+// Flags: --experimental-modules --experimental-wasm-modules
+import '../common/index.mjs';
+import { add, addImported } from '../fixtures/es-modules/simple.wasm';
+import { state } from '../fixtures/es-modules/wasm-dep.mjs';
+import { strictEqual } from 'assert';
+
+strictEqual(state, 'WASM Start Executed');
+
+strictEqual(add(10, 20), 30);
+
+strictEqual(addImported(0), 42);
+
+strictEqual(state, 'WASM JS Function Executed');
+
+strictEqual(addImported(1), 43);
diff --git a/test/fixtures/es-modules/simple.wasm b/test/fixtures/es-modules/simple.wasm
new file mode 100644
index 00000000000..9e035904b2e
--- /dev/null
+++ b/test/fixtures/es-modules/simple.wasm
Binary files differ
diff --git a/test/fixtures/es-modules/simple.wat b/test/fixtures/es-modules/simple.wat
new file mode 100644
index 00000000000..8c8746aad27
--- /dev/null
+++ b/test/fixtures/es-modules/simple.wat
@@ -0,0 +1,23 @@
+;; Compiled using the WebAssembly Tootkit (https://github.com/WebAssembly/wabt)
+;; $ wat2wasm simple.wat -o simple.wasm
+
+(module
+ (import "./wasm-dep.mjs" "jsFn" (func $jsFn (result i32)))
+ (import "./wasm-dep.mjs" "jsInitFn" (func $jsInitFn))
+ (export "add" (func $add))
+ (export "addImported" (func $addImported))
+ (start $startFn)
+ (func $startFn
+ call $jsInitFn
+ )
+ (func $add (param $a i32) (param $b i32) (result i32)
+ local.get $a
+ local.get $b
+ i32.add
+ )
+ (func $addImported (param $a i32) (result i32)
+ local.get $a
+ call $jsFn
+ i32.add
+ )
+)
diff --git a/test/fixtures/es-modules/wasm-dep.mjs b/test/fixtures/es-modules/wasm-dep.mjs
new file mode 100644
index 00000000000..243e1b17d26
--- /dev/null
+++ b/test/fixtures/es-modules/wasm-dep.mjs
@@ -0,0 +1,13 @@
+import { strictEqual } from 'assert';
+
+export function jsFn () {
+ state = 'WASM JS Function Executed';
+ return 42;
+}
+
+export let state = 'JS Function Executed';
+
+export function jsInitFn () {
+ strictEqual(state, 'JS Function Executed');
+ state = 'WASM Start Executed';
+} \ No newline at end of file