Skip to content
This repository has been archived by the owner on Apr 16, 2020. It is now read-only.

esm: experimental wasm modules #46

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,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);
});
})
Expand Down
18 changes: 12 additions & 6 deletions lib/internal/modules/esm/create_dynamic_module.js
Original file line number Diff line number Diff line change
@@ -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} };
Expand All @@ -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);
Expand Down
11 changes: 10 additions & 1 deletion lib/internal/modules/esm/default_resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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');
Expand Down Expand Up @@ -44,6 +44,15 @@ const legacyExtensionFormatMap = {
'.node': 'commonjs'
};

if (experimentalWasmModules) {
Object.assign(extensionFormatMap, {
guybedford marked this conversation as resolved.
Show resolved Hide resolved
'.wasm': 'wasm'
});
Object.assign(legacyExtensionFormatMap, {
'.wasm': 'wasm'
});
}

if (experimentalJsonModules) {
// This is a total hack
Object.assign(extensionFormatMap, {
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
41 changes: 34 additions & 7 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
'use strict';

/* global WebAssembly */

const {
JSON,
Object,
SafeMap,
StringPrototype,
JSON
StringPrototype
} = primordials;

const { NativeModule } = require('internal/bootstrap/loaders');
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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);
});
}
Expand All @@ -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]);
});
});
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,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",
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions test/es-module/test-esm-wasm.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Flags: --experimental-modules --experimental-wasm-modules
/* eslint-disable node-core/required-modules */
import { add, addImported } from '../fixtures/es-modules/simple.wasm';
import { strictEqual } from 'assert';

strictEqual(add(10, 20), 30);

strictEqual(addImported(0), 42);
strictEqual(addImported(1), 43);
Binary file added test/fixtures/es-modules/simple.wasm
Binary file not shown.
3 changes: 3 additions & 0 deletions test/fixtures/es-modules/wasm-dep.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function jsFn () {
return 42;
}