mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
esm: support source phase imports for WebAssembly
PR-URL: https://github.com/nodejs/node/pull/56919 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
This commit is contained in:
parent
ac5afbc83a
commit
db4dcc05ac
|
|
@ -2736,6 +2736,17 @@ The source map could not be parsed because it does not exist, or is corrupt.
|
|||
|
||||
A file imported from a source map was not found.
|
||||
|
||||
<a id="ERR_SOURCE_PHASE_NOT_DEFINED"></a>
|
||||
|
||||
### `ERR_SOURCE_PHASE_NOT_DEFINED`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
The provided module import does not provide a source phase imports representation for source phase
|
||||
import syntax `import source x from 'x'` or `import.source(x)`.
|
||||
|
||||
<a id="ERR_SQLITE_ERROR"></a>
|
||||
|
||||
### `ERR_SQLITE_ERROR`
|
||||
|
|
|
|||
|
|
@ -669,17 +669,19 @@ imported from the same path.
|
|||
|
||||
> Stability: 1 - Experimental
|
||||
|
||||
Importing WebAssembly 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.
|
||||
Importing both WebAssembly module instances and WebAssembly source phase
|
||||
imports are supported under the `--experimental-wasm-modules` flag.
|
||||
|
||||
This integration is in line with the
|
||||
Both of these integrations are in line with the
|
||||
[ES Module Integration Proposal for WebAssembly][].
|
||||
|
||||
For example, an `index.mjs` containing:
|
||||
Instance imports allow any `.wasm` files to be imported as normal modules,
|
||||
supporting their module imports in turn.
|
||||
|
||||
For example, an `index.js` containing:
|
||||
|
||||
```js
|
||||
import * as M from './module.wasm';
|
||||
import * as M from './library.wasm';
|
||||
console.log(M);
|
||||
```
|
||||
|
||||
|
|
@ -689,7 +691,35 @@ executed under:
|
|||
node --experimental-wasm-modules index.mjs
|
||||
```
|
||||
|
||||
would provide the exports interface for the instantiation of `module.wasm`.
|
||||
would provide the exports interface for the instantiation of `library.wasm`.
|
||||
|
||||
### Wasm Source Phase Imports
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
The [Source Phase Imports][] proposal allows the `import source` keyword
|
||||
combination to import a `WebAssembly.Module` object directly, instead of getting
|
||||
a module instance already instantiated with its dependencies.
|
||||
|
||||
This is useful when needing custom instantiations for Wasm, while still
|
||||
resolving and loading it through the ES module integration.
|
||||
|
||||
For example, to create multiple instances of a module, or to pass custom imports
|
||||
into a new instance of `library.wasm`:
|
||||
|
||||
```js
|
||||
import source libraryModule from './library.wasm';
|
||||
|
||||
const instance1 = await WebAssembly.instantiate(libraryModule, {
|
||||
custom: import1,
|
||||
});
|
||||
|
||||
const instance2 = await WebAssembly.instantiate(libraryModule, {
|
||||
custom: import2,
|
||||
});
|
||||
```
|
||||
|
||||
<i id="esm_experimental_top_level_await"></i>
|
||||
|
||||
|
|
@ -1126,6 +1156,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][].
|
|||
[Loading ECMAScript modules using `require()`]: modules.md#loading-ecmascript-modules-using-require
|
||||
[Module customization hooks]: module.md#customization-hooks
|
||||
[Node.js Module Resolution And Loading Algorithm]: #resolution-algorithm-specification
|
||||
[Source Phase Imports]: https://github.com/tc39/proposal-source-phase-imports
|
||||
[Terminology]: #terminology
|
||||
[URL]: https://url.spec.whatwg.org/
|
||||
[`"exports"`]: packages.md#exports
|
||||
|
|
|
|||
|
|
@ -1908,6 +1908,7 @@ has the following signature:
|
|||
* `importAttributes` {Object} The `"with"` value passed to the
|
||||
[`optionsExpression`][] optional parameter, or an empty object if no value was
|
||||
provided.
|
||||
* `phase` {string} The phase of the dynamic import (`"source"` or `"evaluation"`).
|
||||
* Returns: {Module Namespace Object|vm.Module} Returning a `vm.Module` is
|
||||
recommended in order to take advantage of error tracking, and to avoid issues
|
||||
with namespaces that contain `then` function exports.
|
||||
|
|
|
|||
|
|
@ -1508,7 +1508,7 @@ function loadESMFromCJS(mod, filename, format, source) {
|
|||
if (isMain) {
|
||||
require('internal/modules/run_main').runEntryPointWithESMLoader((cascadedLoader) => {
|
||||
const mainURL = pathToFileURL(filename).href;
|
||||
return cascadedLoader.import(mainURL, undefined, { __proto__: null }, true);
|
||||
return cascadedLoader.import(mainURL, undefined, { __proto__: null }, undefined, true);
|
||||
});
|
||||
// ESM won't be accessible via process.mainModule.
|
||||
setOwnProperty(process, 'mainModule', undefined);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const {
|
|||
forceDefaultLoader,
|
||||
} = require('internal/modules/esm/utils');
|
||||
const { kImplicitTypeAttribute } = require('internal/modules/esm/assert');
|
||||
const { ModuleWrap, kEvaluating, kEvaluated } = internalBinding('module_wrap');
|
||||
const { ModuleWrap, kEvaluating, kEvaluated, kEvaluationPhase, kSourcePhase } = internalBinding('module_wrap');
|
||||
const {
|
||||
urlToFilename,
|
||||
} = require('internal/modules/helpers');
|
||||
|
|
@ -236,8 +236,7 @@ class ModuleLoader {
|
|||
async executeModuleJob(url, wrap, isEntryPoint = false) {
|
||||
const { ModuleJob } = require('internal/modules/esm/module_job');
|
||||
const module = await onImport.tracePromise(async () => {
|
||||
const job = new ModuleJob(
|
||||
this, url, undefined, wrap, false, false);
|
||||
const job = new ModuleJob(this, url, undefined, wrap, kEvaluationPhase, false, false);
|
||||
this.loadCache.set(url, undefined, job);
|
||||
const { module } = await job.run(isEntryPoint);
|
||||
return module;
|
||||
|
|
@ -273,11 +272,12 @@ class ModuleLoader {
|
|||
* @param {string} [parentURL] The URL of the module where the module request is initiated.
|
||||
* It's undefined if it's from the root module.
|
||||
* @param {ImportAttributes} importAttributes Attributes from the import statement or expression.
|
||||
* @param {number} phase Import phase.
|
||||
* @returns {Promise<ModuleJobBase>}
|
||||
*/
|
||||
async getModuleJobForImport(specifier, parentURL, importAttributes) {
|
||||
async getModuleJobForImport(specifier, parentURL, importAttributes, phase) {
|
||||
const resolveResult = await this.resolve(specifier, parentURL, importAttributes);
|
||||
return this.#getJobFromResolveResult(resolveResult, parentURL, importAttributes, false);
|
||||
return this.#getJobFromResolveResult(resolveResult, parentURL, importAttributes, phase, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -287,11 +287,12 @@ class ModuleLoader {
|
|||
* @param {string} specifier See {@link getModuleJobForImport}
|
||||
* @param {string} [parentURL] See {@link getModuleJobForImport}
|
||||
* @param {ImportAttributes} importAttributes See {@link getModuleJobForImport}
|
||||
* @param {number} phase Import phase.
|
||||
* @returns {Promise<ModuleJobBase>}
|
||||
*/
|
||||
getModuleJobForRequireInImportedCJS(specifier, parentURL, importAttributes) {
|
||||
getModuleJobForRequireInImportedCJS(specifier, parentURL, importAttributes, phase) {
|
||||
const resolveResult = this.resolveSync(specifier, parentURL, importAttributes);
|
||||
return this.#getJobFromResolveResult(resolveResult, parentURL, importAttributes, true);
|
||||
return this.#getJobFromResolveResult(resolveResult, parentURL, importAttributes, phase, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -300,16 +301,21 @@ class ModuleLoader {
|
|||
* @param {{ format: string, url: string }} resolveResult Resolved module request.
|
||||
* @param {string} [parentURL] See {@link getModuleJobForImport}
|
||||
* @param {ImportAttributes} importAttributes See {@link getModuleJobForImport}
|
||||
* @param {number} phase Import phase.
|
||||
* @param {boolean} isForRequireInImportedCJS Whether this is done for require() in imported CJS.
|
||||
* @returns {ModuleJobBase}
|
||||
*/
|
||||
#getJobFromResolveResult(resolveResult, parentURL, importAttributes, isForRequireInImportedCJS = false) {
|
||||
#getJobFromResolveResult(resolveResult, parentURL, importAttributes, phase,
|
||||
isForRequireInImportedCJS = false) {
|
||||
const { url, format } = resolveResult;
|
||||
const resolvedImportAttributes = resolveResult.importAttributes ?? importAttributes;
|
||||
let job = this.loadCache.get(url, resolvedImportAttributes.type);
|
||||
|
||||
if (job === undefined) {
|
||||
job = this.#createModuleJob(url, resolvedImportAttributes, parentURL, format, isForRequireInImportedCJS);
|
||||
job = this.#createModuleJob(url, resolvedImportAttributes, phase, parentURL, format,
|
||||
isForRequireInImportedCJS);
|
||||
} else {
|
||||
job.ensurePhase(phase);
|
||||
}
|
||||
|
||||
return job;
|
||||
|
|
@ -377,7 +383,7 @@ class ModuleLoader {
|
|||
const inspectBrk = (isMain && getOptionValue('--inspect-brk'));
|
||||
|
||||
const { ModuleJobSync } = require('internal/modules/esm/module_job');
|
||||
job = new ModuleJobSync(this, url, kEmptyObject, wrap, isMain, inspectBrk);
|
||||
job = new ModuleJobSync(this, url, kEmptyObject, wrap, kEvaluationPhase, isMain, inspectBrk);
|
||||
this.loadCache.set(url, kImplicitTypeAttribute, job);
|
||||
mod[kRequiredModuleSymbol] = job.module;
|
||||
return { wrap: job.module, namespace: job.runSync(parent).namespace };
|
||||
|
|
@ -389,9 +395,10 @@ class ModuleLoader {
|
|||
* @param {string} specifier Specifier of the the imported module.
|
||||
* @param {string} parentURL Where the import comes from.
|
||||
* @param {object} importAttributes import attributes from the import statement.
|
||||
* @param {number} phase The import phase.
|
||||
* @returns {ModuleJobBase}
|
||||
*/
|
||||
getModuleJobForRequire(specifier, parentURL, importAttributes) {
|
||||
getModuleJobForRequire(specifier, parentURL, importAttributes, phase) {
|
||||
const parsed = URLParse(specifier);
|
||||
if (parsed != null) {
|
||||
const protocol = parsed.protocol;
|
||||
|
|
@ -422,6 +429,7 @@ class ModuleLoader {
|
|||
}
|
||||
throw new ERR_REQUIRE_CYCLE_MODULE(message);
|
||||
}
|
||||
job.ensurePhase(phase);
|
||||
// Otherwise the module could be imported before but the evaluation may be already
|
||||
// completed (e.g. the require call is lazy) so it's okay. We will return the
|
||||
// module now and check asynchronicity of the entire graph later, after the
|
||||
|
|
@ -463,7 +471,7 @@ class ModuleLoader {
|
|||
|
||||
const inspectBrk = (isMain && getOptionValue('--inspect-brk'));
|
||||
const { ModuleJobSync } = require('internal/modules/esm/module_job');
|
||||
job = new ModuleJobSync(this, url, importAttributes, wrap, isMain, inspectBrk);
|
||||
job = new ModuleJobSync(this, url, importAttributes, wrap, phase, isMain, inspectBrk);
|
||||
|
||||
this.loadCache.set(url, importAttributes.type, job);
|
||||
return job;
|
||||
|
|
@ -543,13 +551,14 @@ class ModuleLoader {
|
|||
* by the time this returns. Otherwise it may still have pending module requests.
|
||||
* @param {string} url The URL that was resolved for this module.
|
||||
* @param {ImportAttributes} importAttributes See {@link getModuleJobForImport}
|
||||
* @param {number} phase Import phase.
|
||||
* @param {string} [parentURL] See {@link getModuleJobForImport}
|
||||
* @param {string} [format] The format hint possibly returned by the `resolve` hook
|
||||
* @param {boolean} isForRequireInImportedCJS Whether this module job is created for require()
|
||||
* in imported CJS.
|
||||
* @returns {ModuleJobBase} The (possibly pending) module job
|
||||
*/
|
||||
#createModuleJob(url, importAttributes, parentURL, format, isForRequireInImportedCJS) {
|
||||
#createModuleJob(url, importAttributes, phase, parentURL, format, isForRequireInImportedCJS) {
|
||||
const context = { format, importAttributes };
|
||||
|
||||
const isMain = parentURL === undefined;
|
||||
|
|
@ -575,6 +584,7 @@ class ModuleLoader {
|
|||
url,
|
||||
importAttributes,
|
||||
moduleOrModulePromise,
|
||||
phase,
|
||||
isMain,
|
||||
inspectBrk,
|
||||
isForRequireInImportedCJS,
|
||||
|
|
@ -592,11 +602,18 @@ class ModuleLoader {
|
|||
* @param {string} parentURL Path of the parent importing the module.
|
||||
* @param {Record<string, string>} importAttributes Validations for the
|
||||
* module import.
|
||||
* @param {number} [phase] The phase of the import.
|
||||
* @param {boolean} [isEntryPoint] Whether this is the realm-level entry point.
|
||||
* @returns {Promise<ModuleExports>}
|
||||
*/
|
||||
async import(specifier, parentURL, importAttributes, isEntryPoint = false) {
|
||||
async import(specifier, parentURL, importAttributes, phase = kEvaluationPhase, isEntryPoint = false) {
|
||||
return onImport.tracePromise(async () => {
|
||||
const moduleJob = await this.getModuleJobForImport(specifier, parentURL, importAttributes);
|
||||
const moduleJob = await this.getModuleJobForImport(specifier, parentURL, importAttributes,
|
||||
phase);
|
||||
if (phase === kSourcePhase) {
|
||||
const module = await moduleJob.modulePromise;
|
||||
return module.getModuleSourceObject();
|
||||
}
|
||||
const { module } = await moduleJob.run(isEntryPoint);
|
||||
return module.getNamespace();
|
||||
}, {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
|
|||
debug = fn;
|
||||
});
|
||||
|
||||
const { ModuleWrap, kInstantiated } = internalBinding('module_wrap');
|
||||
const { ModuleWrap, kInstantiated, kEvaluationPhase } = internalBinding('module_wrap');
|
||||
const {
|
||||
privateSymbols: {
|
||||
entry_point_module_private_symbol,
|
||||
|
|
@ -59,8 +59,10 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
|
|||
);
|
||||
|
||||
class ModuleJobBase {
|
||||
constructor(url, importAttributes, isMain, inspectBrk) {
|
||||
constructor(url, importAttributes, phase, isMain, inspectBrk) {
|
||||
assert(typeof phase === 'number');
|
||||
this.importAttributes = importAttributes;
|
||||
this.phase = phase;
|
||||
this.isMain = isMain;
|
||||
this.inspectBrk = inspectBrk;
|
||||
|
||||
|
|
@ -78,14 +80,15 @@ class ModuleJob extends ModuleJobBase {
|
|||
* @param {string} url URL of the module to be wrapped in ModuleJob.
|
||||
* @param {ImportAttributes} importAttributes Import attributes from the import statement.
|
||||
* @param {ModuleWrap|Promise<ModuleWrap>} moduleOrModulePromise Translated ModuleWrap for the module.
|
||||
* @param {number} phase The phase to load the module to.
|
||||
* @param {boolean} isMain Whether the module is the entry point.
|
||||
* @param {boolean} inspectBrk Whether this module should be evaluated with the
|
||||
* first line paused in the debugger (because --inspect-brk is passed).
|
||||
* @param {boolean} isForRequireInImportedCJS Whether this is created for require() in imported CJS.
|
||||
*/
|
||||
constructor(loader, url, importAttributes = { __proto__: null },
|
||||
moduleOrModulePromise, isMain, inspectBrk, isForRequireInImportedCJS = false) {
|
||||
super(url, importAttributes, isMain, inspectBrk);
|
||||
constructor(loader, url, importAttributes = { __proto__: null }, moduleOrModulePromise,
|
||||
phase = kEvaluationPhase, isMain, inspectBrk, isForRequireInImportedCJS = false) {
|
||||
super(url, importAttributes, phase, isMain, inspectBrk);
|
||||
this.#loader = loader;
|
||||
|
||||
// Expose the promise to the ModuleWrap directly for linking below.
|
||||
|
|
@ -97,22 +100,37 @@ class ModuleJob extends ModuleJobBase {
|
|||
this.modulePromise = moduleOrModulePromise;
|
||||
}
|
||||
|
||||
// Promise for the list of all dependencyJobs.
|
||||
this.linked = this._link();
|
||||
// This promise is awaited later anyway, so silence
|
||||
// 'unhandled rejection' warnings.
|
||||
PromisePrototypeThen(this.linked, undefined, noop);
|
||||
if (this.phase === kEvaluationPhase) {
|
||||
// Promise for the list of all dependencyJobs.
|
||||
this.linked = this.#link();
|
||||
// This promise is awaited later anyway, so silence
|
||||
// 'unhandled rejection' warnings.
|
||||
PromisePrototypeThen(this.linked, undefined, noop);
|
||||
}
|
||||
|
||||
// instantiated == deep dependency jobs wrappers are instantiated,
|
||||
// and module wrapper is instantiated.
|
||||
this.instantiated = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that this ModuleJob is moving towards the required phase
|
||||
* (does not necessarily mean it is ready at that phase - run does that)
|
||||
* @param {number} phase
|
||||
*/
|
||||
ensurePhase(phase) {
|
||||
if (this.phase < phase) {
|
||||
this.phase = phase;
|
||||
this.linked = this.#link();
|
||||
PromisePrototypeThen(this.linked, undefined, noop);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates the module requests and links with the loader.
|
||||
* @returns {Promise<ModuleJob[]>} Dependency module jobs.
|
||||
*/
|
||||
async _link() {
|
||||
async #link() {
|
||||
this.module = await this.modulePromise;
|
||||
assert(this.module instanceof ModuleWrap);
|
||||
|
||||
|
|
@ -123,23 +141,33 @@ class ModuleJob extends ModuleJobBase {
|
|||
// these `link` callbacks depending on each other.
|
||||
// Create an ArrayLike to avoid calling into userspace with `.then`
|
||||
// when returned from the async function.
|
||||
const dependencyJobs = Array(moduleRequests.length);
|
||||
ObjectSetPrototypeOf(dependencyJobs, null);
|
||||
const evaluationDepJobs = Array(moduleRequests.length);
|
||||
ObjectSetPrototypeOf(evaluationDepJobs, null);
|
||||
|
||||
// Specifiers should be aligned with the moduleRequests array in order.
|
||||
const specifiers = Array(moduleRequests.length);
|
||||
const modulePromises = Array(moduleRequests.length);
|
||||
// Track each loop for whether it is an evaluation phase or source phase request.
|
||||
let isEvaluation;
|
||||
// Iterate with index to avoid calling into userspace with `Symbol.iterator`.
|
||||
for (let idx = 0; idx < moduleRequests.length; idx++) {
|
||||
const { specifier, attributes } = moduleRequests[idx];
|
||||
for (
|
||||
let idx = 0, eidx = 0;
|
||||
// Use the let-scoped eidx value to update the executionDepJobs length at the end of the loop.
|
||||
idx < moduleRequests.length || (evaluationDepJobs.length = eidx, false);
|
||||
idx++, eidx += isEvaluation
|
||||
) {
|
||||
const { specifier, phase, attributes } = moduleRequests[idx];
|
||||
isEvaluation = phase === kEvaluationPhase;
|
||||
// TODO(joyeecheung): resolve all requests first, then load them in another
|
||||
// loop so that hooks can pre-fetch sources off-thread.
|
||||
const dependencyJobPromise = this.#loader.getModuleJobForImport(
|
||||
specifier, this.url, attributes,
|
||||
specifier, this.url, attributes, phase,
|
||||
);
|
||||
const modulePromise = PromisePrototypeThen(dependencyJobPromise, (job) => {
|
||||
debug(`async link() ${this.url} -> ${specifier}`, job);
|
||||
dependencyJobs[idx] = job;
|
||||
if (phase === kEvaluationPhase) {
|
||||
evaluationDepJobs[eidx] = job;
|
||||
}
|
||||
return job.modulePromise;
|
||||
});
|
||||
modulePromises[idx] = modulePromise;
|
||||
|
|
@ -149,17 +177,17 @@ class ModuleJob extends ModuleJobBase {
|
|||
const modules = await SafePromiseAllReturnArrayLike(modulePromises);
|
||||
this.module.link(specifiers, modules);
|
||||
|
||||
return dependencyJobs;
|
||||
return evaluationDepJobs;
|
||||
}
|
||||
|
||||
instantiate() {
|
||||
#instantiate() {
|
||||
if (this.instantiated === undefined) {
|
||||
this.instantiated = this._instantiate();
|
||||
this.instantiated = this.#_instantiate();
|
||||
}
|
||||
return this.instantiated;
|
||||
}
|
||||
|
||||
async _instantiate() {
|
||||
async #_instantiate() {
|
||||
const jobsInGraph = new SafeSet();
|
||||
const addJobsToDependencyGraph = async (moduleJob) => {
|
||||
debug(`async addJobsToDependencyGraph() ${this.url}`, moduleJob);
|
||||
|
|
@ -247,6 +275,7 @@ class ModuleJob extends ModuleJobBase {
|
|||
}
|
||||
|
||||
runSync() {
|
||||
assert(this.phase === kEvaluationPhase);
|
||||
assert(this.module instanceof ModuleWrap);
|
||||
if (this.instantiated !== undefined) {
|
||||
return { __proto__: null, module: this.module };
|
||||
|
|
@ -262,7 +291,8 @@ class ModuleJob extends ModuleJobBase {
|
|||
}
|
||||
|
||||
async run(isEntryPoint = false) {
|
||||
await this.instantiate();
|
||||
assert(this.phase === kEvaluationPhase);
|
||||
await this.#instantiate();
|
||||
if (isEntryPoint) {
|
||||
globalThis[entry_point_module_private_symbol] = this.module;
|
||||
}
|
||||
|
|
@ -317,40 +347,64 @@ class ModuleJobSync extends ModuleJobBase {
|
|||
* @param {string} url URL of the module to be wrapped in ModuleJob.
|
||||
* @param {ImportAttributes} importAttributes Import attributes from the import statement.
|
||||
* @param {ModuleWrap} moduleWrap Translated ModuleWrap for the module.
|
||||
* @param {number} phase The phase to load the module to.
|
||||
* @param {boolean} isMain Whether the module is the entry point.
|
||||
* @param {boolean} inspectBrk Whether this module should be evaluated with the
|
||||
* first line paused in the debugger (because --inspect-brk is passed).
|
||||
*/
|
||||
constructor(loader, url, importAttributes, moduleWrap, isMain, inspectBrk) {
|
||||
super(url, importAttributes, isMain, inspectBrk, true);
|
||||
constructor(loader, url, importAttributes, moduleWrap, phase = kEvaluationPhase, isMain,
|
||||
inspectBrk) {
|
||||
super(url, importAttributes, phase, isMain, inspectBrk, true);
|
||||
|
||||
this.#loader = loader;
|
||||
this.module = moduleWrap;
|
||||
|
||||
assert(this.module instanceof ModuleWrap);
|
||||
this.linked = undefined;
|
||||
this.type = importAttributes.type;
|
||||
if (phase === kEvaluationPhase) {
|
||||
this.#link();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that this ModuleJob is at the required phase
|
||||
* @param {number} phase
|
||||
*/
|
||||
ensurePhase(phase) {
|
||||
if (this.phase < phase) {
|
||||
this.phase = phase;
|
||||
this.#link();
|
||||
}
|
||||
}
|
||||
|
||||
#link() {
|
||||
// Store itself into the cache first before linking in case there are circular
|
||||
// references in the linking.
|
||||
loader.loadCache.set(url, importAttributes.type, this);
|
||||
|
||||
this.#loader.loadCache.set(this.url, this.type, this);
|
||||
try {
|
||||
const moduleRequests = this.module.getModuleRequests();
|
||||
// Specifiers should be aligned with the moduleRequests array in order.
|
||||
const specifiers = Array(moduleRequests.length);
|
||||
const modules = Array(moduleRequests.length);
|
||||
const jobs = Array(moduleRequests.length);
|
||||
const evaluationDepJobs = Array(moduleRequests.length);
|
||||
let j = 0;
|
||||
for (let i = 0; i < moduleRequests.length; ++i) {
|
||||
const { specifier, attributes } = moduleRequests[i];
|
||||
const job = this.#loader.getModuleJobForRequire(specifier, url, attributes);
|
||||
const { specifier, attributes, phase } = moduleRequests[i];
|
||||
const job = this.#loader.getModuleJobForRequire(specifier, this.url, attributes, phase);
|
||||
specifiers[i] = specifier;
|
||||
modules[i] = job.module;
|
||||
jobs[i] = job;
|
||||
if (phase === kEvaluationPhase) {
|
||||
evaluationDepJobs[j++] = job;
|
||||
}
|
||||
}
|
||||
evaluationDepJobs.length = j;
|
||||
this.module.link(specifiers, modules);
|
||||
this.linked = jobs;
|
||||
this.linked = evaluationDepJobs;
|
||||
} finally {
|
||||
// Restore it - if it succeeds, we'll reset in the caller; Otherwise it's
|
||||
// not cached and if the error is caught, subsequent attempt would still fail.
|
||||
loader.loadCache.delete(url, importAttributes.type);
|
||||
this.#loader.loadCache.delete(this.url, this.type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -359,6 +413,7 @@ class ModuleJobSync extends ModuleJobBase {
|
|||
}
|
||||
|
||||
async run() {
|
||||
assert(this.phase === kEvaluationPhase);
|
||||
// This path is hit by a require'd module that is imported again.
|
||||
const status = this.module.getStatus();
|
||||
if (status > kInstantiated) {
|
||||
|
|
@ -382,6 +437,7 @@ class ModuleJobSync extends ModuleJobBase {
|
|||
}
|
||||
|
||||
runSync(parent) {
|
||||
assert(this.phase === kEvaluationPhase);
|
||||
// TODO(joyeecheung): add the error decoration logic from the async instantiate.
|
||||
this.module.async = this.module.instantiateSync();
|
||||
// If --experimental-print-required-tla is true, proceeds to evaluation even
|
||||
|
|
|
|||
|
|
@ -506,12 +506,16 @@ translators.set('wasm', async function(url, source) {
|
|||
|
||||
const createDynamicModule = require(
|
||||
'internal/modules/esm/create_dynamic_module');
|
||||
return createDynamicModule(imports, exports, url, (reflect) => {
|
||||
const { module } = createDynamicModule(imports, exports, url, (reflect) => {
|
||||
const { exports } = new WebAssembly.Instance(compiled, reflect.imports);
|
||||
for (const expt of ObjectKeys(exports)) {
|
||||
reflect.exports[expt].set(exports[expt]);
|
||||
}
|
||||
}).module;
|
||||
});
|
||||
// WebAssembly modules support source phase imports, to import the compiled module
|
||||
// separate from the linked instance.
|
||||
module.setModuleSourceObject(compiled);
|
||||
return module;
|
||||
});
|
||||
|
||||
// Strategy for loading a addon
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ const {
|
|||
vm_dynamic_import_no_callback,
|
||||
} = internalBinding('symbols');
|
||||
|
||||
const { ModuleWrap } = internalBinding('module_wrap');
|
||||
const {
|
||||
ModuleWrap,
|
||||
setImportModuleDynamicallyCallback,
|
||||
setInitializeImportMetaObjectCallback,
|
||||
} = internalBinding('module_wrap');
|
||||
const {
|
||||
maybeCacheSourceMap,
|
||||
} = require('internal/source_map/source_map_cache');
|
||||
|
|
@ -39,10 +43,6 @@ const {
|
|||
emitExperimentalWarning,
|
||||
getCWDURL,
|
||||
} = require('internal/util');
|
||||
const {
|
||||
setImportModuleDynamicallyCallback,
|
||||
setInitializeImportMetaObjectCallback,
|
||||
} = internalBinding('module_wrap');
|
||||
const assert = require('internal/assert');
|
||||
const {
|
||||
normalizeReferrerURL,
|
||||
|
|
@ -112,6 +112,7 @@ function getConditionsSet(conditions) {
|
|||
* @param {string} specifier
|
||||
* @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
|
||||
* @param {Record<string, string>} attributes
|
||||
* @param {number} phase
|
||||
* @returns {Promise<ModuleNamespaceObject>}
|
||||
*/
|
||||
|
||||
|
|
@ -212,58 +213,62 @@ function initializeImportMetaObject(symbol, meta, wrap) {
|
|||
/**
|
||||
* Proxy the dynamic import handling to the default loader for source text modules.
|
||||
* @param {string} specifier - The module specifier string.
|
||||
* @param {number} phase - The module import phase.
|
||||
* @param {Record<string, string>} attributes - The import attributes object.
|
||||
* @param {string|null|undefined} referrerName - name of the referrer.
|
||||
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
|
||||
*/
|
||||
function defaultImportModuleDynamicallyForModule(specifier, attributes, referrerName) {
|
||||
function defaultImportModuleDynamicallyForModule(specifier, phase, attributes, referrerName) {
|
||||
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
|
||||
return cascadedLoader.import(specifier, referrerName, attributes);
|
||||
return cascadedLoader.import(specifier, referrerName, attributes, phase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy the dynamic import to the default loader for classic scripts.
|
||||
* @param {string} specifier - The module specifier string.
|
||||
* @param {number} phase - The module import phase.
|
||||
* @param {Record<string, string>} attributes - The import attributes object.
|
||||
* @param {string|null|undefined} referrerName - name of the referrer.
|
||||
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
|
||||
*/
|
||||
function defaultImportModuleDynamicallyForScript(specifier, attributes, referrerName) {
|
||||
function defaultImportModuleDynamicallyForScript(specifier, phase, attributes, referrerName) {
|
||||
const parentURL = normalizeReferrerURL(referrerName);
|
||||
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
|
||||
return cascadedLoader.import(specifier, parentURL, attributes);
|
||||
return cascadedLoader.import(specifier, parentURL, attributes, phase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously imports a module dynamically using a callback function. The native callback.
|
||||
* @param {symbol} referrerSymbol - Referrer symbol of the registered script, function, module, or contextified object.
|
||||
* @param {string} specifier - The module specifier string.
|
||||
* @param {number} phase - The module import phase.
|
||||
* @param {Record<string, string>} attributes - The import attributes object.
|
||||
* @param {string|null|undefined} referrerName - name of the referrer.
|
||||
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
|
||||
* @throws {ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING} - If the callback function is missing.
|
||||
*/
|
||||
async function importModuleDynamicallyCallback(referrerSymbol, specifier, attributes, referrerName) {
|
||||
async function importModuleDynamicallyCallback(referrerSymbol, specifier, phase, attributes,
|
||||
referrerName) {
|
||||
// For user-provided vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, emit the warning
|
||||
// and fall back to the default loader.
|
||||
if (referrerSymbol === vm_dynamic_import_main_context_default) {
|
||||
emitExperimentalWarning('vm.USE_MAIN_CONTEXT_DEFAULT_LOADER');
|
||||
return defaultImportModuleDynamicallyForScript(specifier, attributes, referrerName);
|
||||
return defaultImportModuleDynamicallyForScript(specifier, phase, attributes, referrerName);
|
||||
}
|
||||
// For script compiled internally that should use the default loader to handle dynamic
|
||||
// import, proxy the request to the default loader without the warning.
|
||||
if (referrerSymbol === vm_dynamic_import_default_internal) {
|
||||
return defaultImportModuleDynamicallyForScript(specifier, attributes, referrerName);
|
||||
return defaultImportModuleDynamicallyForScript(specifier, phase, attributes, referrerName);
|
||||
}
|
||||
// For SourceTextModules compiled internally, proxy the request to the default loader.
|
||||
if (referrerSymbol === source_text_module_default_hdo) {
|
||||
return defaultImportModuleDynamicallyForModule(specifier, attributes, referrerName);
|
||||
return defaultImportModuleDynamicallyForModule(specifier, phase, attributes, referrerName);
|
||||
}
|
||||
|
||||
if (moduleRegistries.has(referrerSymbol)) {
|
||||
const { importModuleDynamically, callbackReferrer } = moduleRegistries.get(referrerSymbol);
|
||||
if (importModuleDynamically !== undefined) {
|
||||
return importModuleDynamically(specifier, callbackReferrer, attributes);
|
||||
return importModuleDynamically(specifier, callbackReferrer, attributes, phase);
|
||||
}
|
||||
}
|
||||
if (referrerSymbol === vm_dynamic_import_missing_flag) {
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ function executeUserEntryPoint(main = process.argv[1]) {
|
|||
runEntryPointWithESMLoader((cascadedLoader) => {
|
||||
// Note that if the graph contains unsettled TLA, this may never resolve
|
||||
// even after the event loop stops running.
|
||||
return cascadedLoader.import(mainURL, undefined, { __proto__: null }, true);
|
||||
return cascadedLoader.import(mainURL, undefined, { __proto__: null }, undefined, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ const {
|
|||
} = require('internal/errors');
|
||||
const { pathToFileURL } = require('internal/url');
|
||||
const { exitCodes: { kGenericUserError } } = internalBinding('errors');
|
||||
const {
|
||||
kSourcePhase,
|
||||
kEvaluationPhase,
|
||||
} = internalBinding('module_wrap');
|
||||
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
|
||||
|
||||
const {
|
||||
|
|
@ -379,9 +383,10 @@ function parseAndEvalCommonjsTypeScript(name, source, breakFirstLine, print, sho
|
|||
*/
|
||||
function compileScript(name, body, baseUrl) {
|
||||
const hostDefinedOptionId = Symbol(name);
|
||||
async function importModuleDynamically(specifier, _, importAttributes) {
|
||||
async function importModuleDynamically(specifier, _, importAttributes, phase) {
|
||||
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
|
||||
return cascadedLoader.import(specifier, baseUrl, importAttributes);
|
||||
return cascadedLoader.import(specifier, baseUrl, importAttributes,
|
||||
phase === 'source' ? kSourcePhase : kEvaluationPhase);
|
||||
}
|
||||
return makeContextifyScript(
|
||||
body, // code
|
||||
|
|
|
|||
|
|
@ -31,6 +31,15 @@ const {
|
|||
},
|
||||
} = internalBinding('util');
|
||||
|
||||
/**
|
||||
* @callback VmImportModuleDynamicallyCallback
|
||||
* @param {string} specifier
|
||||
* @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
|
||||
* @param {Record<string, string>} attributes
|
||||
* @param {string} phase
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if the given object is a context object.
|
||||
* @param {object} object - The object to check.
|
||||
|
|
@ -42,10 +51,10 @@ function isContext(object) {
|
|||
|
||||
/**
|
||||
* Retrieves the host-defined option ID based on the provided importModuleDynamically and hint.
|
||||
* @param {import('internal/modules/esm/utils').ImportModuleDynamicallyCallback | undefined} importModuleDynamically -
|
||||
* @param {VmImportModuleDynamicallyCallback | undefined} importModuleDynamically -
|
||||
* The importModuleDynamically function or undefined.
|
||||
* @param {string} hint - The hint for the option ID.
|
||||
* @returns {symbol | import('internal/modules/esm/utils').ImportModuleDynamicallyCallback} - The host-defined option
|
||||
* @returns {symbol | VmImportModuleDynamicallyCallback} - The host-defined option
|
||||
* ID.
|
||||
*/
|
||||
function getHostDefinedOptionId(importModuleDynamically, hint) {
|
||||
|
|
@ -82,7 +91,7 @@ function getHostDefinedOptionId(importModuleDynamically, hint) {
|
|||
/**
|
||||
* Registers a dynamically imported module for customization.
|
||||
* @param {string} referrer - The path of the referrer module.
|
||||
* @param {import('internal/modules/esm/utils').ImportModuleDynamicallyCallback} importModuleDynamically - The
|
||||
* @param {VmImportModuleDynamicallyCallback} importModuleDynamically - The
|
||||
* dynamically imported module function to be registered.
|
||||
*/
|
||||
function registerImportModuleDynamically(referrer, importModuleDynamically) {
|
||||
|
|
@ -115,7 +124,7 @@ function registerImportModuleDynamically(referrer, importModuleDynamically) {
|
|||
* @param {object[]} [contextExtensions=[]] - An array of context extensions to use for the compiled function.
|
||||
* @param {string[]} [params] - An optional array of parameter names for the compiled function.
|
||||
* @param {symbol} hostDefinedOptionId - A symbol referenced by the field `host_defined_option_symbol`.
|
||||
* @param {import('internal/modules/esm/utils').ImportModuleDynamicallyCallback} [importModuleDynamically] -
|
||||
* @param {VmImportModuleDynamicallyCallback} [importModuleDynamically] -
|
||||
* A function to use for dynamically importing modules.
|
||||
* @returns {object} An object containing the compiled function and any associated data.
|
||||
* @throws {TypeError} If any of the arguments are of the wrong type.
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ const {
|
|||
kEvaluating,
|
||||
kEvaluated,
|
||||
kErrored,
|
||||
kSourcePhase,
|
||||
} = binding;
|
||||
|
||||
const STATUS_MAP = {
|
||||
|
|
@ -431,10 +432,26 @@ class SyntheticModule extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback ImportModuleDynamicallyCallback
|
||||
* @param {string} specifier
|
||||
* @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
|
||||
* @param {Record<string, string>} attributes
|
||||
* @param {number} phase
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {import('internal/vm').VmImportModuleDynamicallyCallback} importModuleDynamically
|
||||
* @returns {ImportModuleDynamicallyCallback}
|
||||
*/
|
||||
function importModuleDynamicallyWrap(importModuleDynamically) {
|
||||
const importModuleDynamicallyWrapper = async (...args) => {
|
||||
const m = await ReflectApply(importModuleDynamically, this, args);
|
||||
const importModuleDynamicallyWrapper = async (specifier, referrer, attributes, phase) => {
|
||||
const phaseString = phase === kSourcePhase ? 'source' : 'evaluation';
|
||||
const m = await ReflectApply(importModuleDynamically, this, [specifier, referrer, attributes,
|
||||
phaseString]);
|
||||
if (isModuleNamespaceObject(m)) {
|
||||
if (phase === kSourcePhase) throw new ERR_VM_MODULE_NOT_MODULE();
|
||||
return m;
|
||||
}
|
||||
if (!isModule(m)) {
|
||||
|
|
@ -443,6 +460,8 @@ function importModuleDynamicallyWrap(importModuleDynamically) {
|
|||
if (m.status === 'errored') {
|
||||
throw m.error;
|
||||
}
|
||||
if (phase === kSourcePhase)
|
||||
return m[kWrap].getModuleSourceObject();
|
||||
return m.namespace;
|
||||
};
|
||||
return importModuleDynamicallyWrapper;
|
||||
|
|
|
|||
|
|
@ -457,9 +457,11 @@ function REPLServer(prompt,
|
|||
} catch {
|
||||
// Continue regardless of error.
|
||||
}
|
||||
async function importModuleDynamically(specifier, _, importAttributes) {
|
||||
async function importModuleDynamically(specifier, _, importAttributes, phase) {
|
||||
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
|
||||
return cascadedLoader.import(specifier, parentURL, importAttributes);
|
||||
return cascadedLoader.import(specifier, parentURL, importAttributes,
|
||||
phase === 'evaluation' ? cascadedLoader.kEvaluationPhase :
|
||||
cascadedLoader.kSourcePhase);
|
||||
}
|
||||
// `experimentalREPLAwait` is set to true by default.
|
||||
// Shall be false in case `--no-experimental-repl-await` flag is used.
|
||||
|
|
|
|||
|
|
@ -295,6 +295,7 @@
|
|||
V(pathname_string, "pathname") \
|
||||
V(pending_handle_string, "pendingHandle") \
|
||||
V(permission_string, "permission") \
|
||||
V(phase_string, "phase") \
|
||||
V(pid_string, "pid") \
|
||||
V(ping_rtt_string, "pingRTT") \
|
||||
V(pipe_source_string, "pipeSource") \
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ using v8::MemorySpan;
|
|||
using v8::Message;
|
||||
using v8::MicrotaskQueue;
|
||||
using v8::Module;
|
||||
using v8::ModuleImportPhase;
|
||||
using v8::ModuleRequest;
|
||||
using v8::Name;
|
||||
using v8::Null;
|
||||
|
|
@ -73,6 +74,8 @@ ModuleWrap::ModuleWrap(Realm* realm,
|
|||
|
||||
object->SetInternalField(kModuleSlot, module);
|
||||
object->SetInternalField(kURLSlot, url);
|
||||
object->SetInternalField(kModuleSourceObjectSlot,
|
||||
v8::Undefined(realm->isolate()));
|
||||
object->SetInternalField(kSyntheticEvaluationStepsSlot,
|
||||
synthetic_evaluation_step);
|
||||
object->SetInternalField(kContextObjectSlot, context_object);
|
||||
|
|
@ -410,6 +413,16 @@ MaybeLocal<Module> ModuleWrap::CompileSourceTextModule(
|
|||
return scope.Escape(module);
|
||||
}
|
||||
|
||||
ModulePhase to_phase_constant(ModuleImportPhase phase) {
|
||||
switch (phase) {
|
||||
case ModuleImportPhase::kEvaluation:
|
||||
return kEvaluationPhase;
|
||||
case ModuleImportPhase::kSource:
|
||||
return kSourcePhase;
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
static Local<Object> createImportAttributesContainer(
|
||||
Realm* realm,
|
||||
Isolate* isolate,
|
||||
|
|
@ -445,14 +458,17 @@ static Local<Array> createModuleRequestsContainer(
|
|||
Local<FixedArray> raw_attributes = module_request->GetImportAttributes();
|
||||
Local<Object> attributes =
|
||||
createImportAttributesContainer(realm, isolate, raw_attributes, 3);
|
||||
ModuleImportPhase phase = module_request->GetPhase();
|
||||
|
||||
Local<Name> names[] = {
|
||||
realm->isolate_data()->specifier_string(),
|
||||
realm->isolate_data()->attributes_string(),
|
||||
realm->isolate_data()->phase_string(),
|
||||
};
|
||||
Local<Value> values[] = {
|
||||
specifier,
|
||||
attributes,
|
||||
Integer::New(isolate, to_phase_constant(phase)),
|
||||
};
|
||||
DCHECK_EQ(arraysize(names), arraysize(values));
|
||||
|
||||
|
|
@ -525,7 +541,8 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
|
|||
Local<Context> context = obj->context();
|
||||
Local<Module> module = obj->module_.Get(isolate);
|
||||
TryCatchScope try_catch(realm->env());
|
||||
USE(module->InstantiateModule(context, ResolveModuleCallback));
|
||||
USE(module->InstantiateModule(
|
||||
context, ResolveModuleCallback, ResolveSourceCallback));
|
||||
|
||||
// clear resolve cache on instantiate
|
||||
obj->resolve_cache_.clear();
|
||||
|
|
@ -631,7 +648,8 @@ void ModuleWrap::InstantiateSync(const FunctionCallbackInfo<Value>& args) {
|
|||
|
||||
{
|
||||
TryCatchScope try_catch(env);
|
||||
USE(module->InstantiateModule(context, ResolveModuleCallback));
|
||||
USE(module->InstantiateModule(
|
||||
context, ResolveModuleCallback, ResolveSourceCallback));
|
||||
|
||||
// clear resolve cache on instantiate
|
||||
obj->resolve_cache_.clear();
|
||||
|
|
@ -777,6 +795,40 @@ void ModuleWrap::GetNamespace(const FunctionCallbackInfo<Value>& args) {
|
|||
args.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
void ModuleWrap::SetModuleSourceObject(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
ModuleWrap* obj;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
CHECK(args[0]->IsObject());
|
||||
CHECK(obj->object()
|
||||
->GetInternalField(kModuleSourceObjectSlot)
|
||||
.As<Value>()
|
||||
->IsUndefined());
|
||||
|
||||
obj->object()->SetInternalField(kModuleSourceObjectSlot, args[0]);
|
||||
}
|
||||
|
||||
void ModuleWrap::GetModuleSourceObject(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
ModuleWrap* obj;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||
|
||||
CHECK_EQ(args.Length(), 0);
|
||||
Local<Value> module_source_object =
|
||||
obj->object()->GetInternalField(kModuleSourceObjectSlot).As<Value>();
|
||||
|
||||
if (module_source_object->IsUndefined()) {
|
||||
Local<String> url = obj->object()->GetInternalField(kURLSlot).As<String>();
|
||||
THROW_ERR_SOURCE_PHASE_NOT_DEFINED(isolate, url);
|
||||
return;
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(module_source_object);
|
||||
}
|
||||
|
||||
void ModuleWrap::GetStatus(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
ModuleWrap* obj;
|
||||
|
|
@ -837,11 +889,63 @@ MaybeLocal<Module> ModuleWrap::ResolveModuleCallback(
|
|||
return module->module_.Get(isolate);
|
||||
}
|
||||
|
||||
static MaybeLocal<Promise> ImportModuleDynamically(
|
||||
MaybeLocal<Object> ModuleWrap::ResolveSourceCallback(
|
||||
Local<Context> context,
|
||||
Local<String> specifier,
|
||||
Local<FixedArray> import_attributes,
|
||||
Local<Module> referrer) {
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
if (env == nullptr) {
|
||||
THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate);
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
|
||||
Utf8Value specifier_utf8(isolate, specifier);
|
||||
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
|
||||
|
||||
ModuleWrap* dependent = GetFromModule(env, referrer);
|
||||
if (dependent == nullptr) {
|
||||
THROW_ERR_VM_MODULE_LINK_FAILURE(
|
||||
env, "request for '%s' is from invalid module", specifier_std);
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
|
||||
if (dependent->resolve_cache_.count(specifier_std) != 1) {
|
||||
THROW_ERR_VM_MODULE_LINK_FAILURE(
|
||||
env, "request for '%s' is not in cache", specifier_std);
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
|
||||
Local<Object> module_object =
|
||||
dependent->resolve_cache_[specifier_std].Get(isolate);
|
||||
if (module_object.IsEmpty() || !module_object->IsObject()) {
|
||||
THROW_ERR_VM_MODULE_LINK_FAILURE(
|
||||
env, "request for '%s' did not return an object", specifier_std);
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
|
||||
ModuleWrap* module;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Object>());
|
||||
|
||||
Local<Value> module_source_object =
|
||||
module->object()->GetInternalField(kModuleSourceObjectSlot).As<Value>();
|
||||
if (module_source_object->IsUndefined()) {
|
||||
Local<String> url =
|
||||
module->object()->GetInternalField(kURLSlot).As<String>();
|
||||
THROW_ERR_SOURCE_PHASE_NOT_DEFINED(isolate, url);
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
CHECK(module_source_object->IsObject());
|
||||
return module_source_object.As<Object>();
|
||||
}
|
||||
|
||||
static MaybeLocal<Promise> ImportModuleDynamicallyWithPhase(
|
||||
Local<Context> context,
|
||||
Local<Data> host_defined_options,
|
||||
Local<Value> resource_name,
|
||||
Local<String> specifier,
|
||||
ModuleImportPhase phase,
|
||||
Local<FixedArray> import_attributes) {
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
|
|
@ -879,6 +983,7 @@ static MaybeLocal<Promise> ImportModuleDynamically(
|
|||
Local<Value> import_args[] = {
|
||||
id,
|
||||
Local<Value>(specifier),
|
||||
Integer::New(isolate, to_phase_constant(phase)),
|
||||
attributes,
|
||||
resource_name,
|
||||
};
|
||||
|
|
@ -896,6 +1001,20 @@ static MaybeLocal<Promise> ImportModuleDynamically(
|
|||
return MaybeLocal<Promise>();
|
||||
}
|
||||
|
||||
static MaybeLocal<Promise> ImportModuleDynamically(
|
||||
Local<Context> context,
|
||||
Local<Data> host_defined_options,
|
||||
Local<Value> resource_name,
|
||||
Local<String> specifier,
|
||||
Local<FixedArray> import_attributes) {
|
||||
return ImportModuleDynamicallyWithPhase(context,
|
||||
host_defined_options,
|
||||
resource_name,
|
||||
specifier,
|
||||
ModuleImportPhase::kEvaluation,
|
||||
import_attributes);
|
||||
}
|
||||
|
||||
void ModuleWrap::SetImportModuleDynamicallyCallback(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
|
|
@ -908,6 +1027,10 @@ void ModuleWrap::SetImportModuleDynamicallyCallback(
|
|||
realm->set_host_import_module_dynamically_callback(import_callback);
|
||||
|
||||
isolate->SetHostImportModuleDynamicallyCallback(ImportModuleDynamically);
|
||||
// TODO(guybedford): Enable this once
|
||||
// https://github.com/nodejs/node/pull/56842 lands.
|
||||
// isolate->SetHostImportModuleWithPhaseDynamicallyCallback(
|
||||
// ImportModuleDynamicallyWithPhase);
|
||||
}
|
||||
|
||||
void ModuleWrap::HostInitializeImportMetaObjectCallback(
|
||||
|
|
@ -1132,6 +1255,8 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
|
|||
SetProtoMethod(isolate, tpl, "instantiate", Instantiate);
|
||||
SetProtoMethod(isolate, tpl, "evaluate", Evaluate);
|
||||
SetProtoMethod(isolate, tpl, "setExport", SetSyntheticExport);
|
||||
SetProtoMethod(isolate, tpl, "setModuleSourceObject", SetModuleSourceObject);
|
||||
SetProtoMethod(isolate, tpl, "getModuleSourceObject", GetModuleSourceObject);
|
||||
SetProtoMethodNoSideEffect(
|
||||
isolate, tpl, "createCachedData", CreateCachedData);
|
||||
SetProtoMethodNoSideEffect(isolate, tpl, "getNamespace", GetNamespace);
|
||||
|
|
@ -1160,18 +1285,21 @@ void ModuleWrap::CreatePerContextProperties(Local<Object> target,
|
|||
void* priv) {
|
||||
Realm* realm = Realm::GetCurrent(context);
|
||||
Isolate* isolate = realm->isolate();
|
||||
#define V(name) \
|
||||
#define V(enum_type, name) \
|
||||
target \
|
||||
->Set(context, \
|
||||
FIXED_ONE_BYTE_STRING(isolate, #name), \
|
||||
Integer::New(isolate, Module::Status::name)) \
|
||||
Integer::New(isolate, enum_type::name)) \
|
||||
.FromJust()
|
||||
V(kUninstantiated);
|
||||
V(kInstantiating);
|
||||
V(kInstantiated);
|
||||
V(kEvaluating);
|
||||
V(kEvaluated);
|
||||
V(kErrored);
|
||||
V(Module::Status, kUninstantiated);
|
||||
V(Module::Status, kInstantiating);
|
||||
V(Module::Status, kInstantiated);
|
||||
V(Module::Status, kEvaluating);
|
||||
V(Module::Status, kEvaluated);
|
||||
V(Module::Status, kErrored);
|
||||
|
||||
V(ModulePhase, kEvaluationPhase);
|
||||
V(ModulePhase, kSourcePhase);
|
||||
#undef V
|
||||
}
|
||||
|
||||
|
|
@ -1187,6 +1315,8 @@ void ModuleWrap::RegisterExternalReferences(
|
|||
registry->Register(Instantiate);
|
||||
registry->Register(Evaluate);
|
||||
registry->Register(SetSyntheticExport);
|
||||
registry->Register(SetModuleSourceObject);
|
||||
registry->Register(GetModuleSourceObject);
|
||||
registry->Register(CreateCachedData);
|
||||
registry->Register(GetNamespace);
|
||||
registry->Register(GetStatus);
|
||||
|
|
|
|||
|
|
@ -33,11 +33,17 @@ enum HostDefinedOptions : int {
|
|||
kLength = 9,
|
||||
};
|
||||
|
||||
enum ModulePhase : int {
|
||||
kSourcePhase = 1,
|
||||
kEvaluationPhase = 2,
|
||||
};
|
||||
|
||||
class ModuleWrap : public BaseObject {
|
||||
public:
|
||||
enum InternalFields {
|
||||
kModuleSlot = BaseObject::kInternalFieldCount,
|
||||
kURLSlot,
|
||||
kModuleSourceObjectSlot,
|
||||
kSyntheticEvaluationStepsSlot,
|
||||
kContextObjectSlot, // Object whose creation context is the target Context
|
||||
kInternalFieldCount
|
||||
|
|
@ -106,6 +112,10 @@ class ModuleWrap : public BaseObject {
|
|||
static void InstantiateSync(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void EvaluateSync(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void GetNamespaceSync(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void SetModuleSourceObject(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void GetModuleSourceObject(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void Link(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Instantiate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
|
@ -129,6 +139,11 @@ class ModuleWrap : public BaseObject {
|
|||
v8::Local<v8::String> specifier,
|
||||
v8::Local<v8::FixedArray> import_attributes,
|
||||
v8::Local<v8::Module> referrer);
|
||||
static v8::MaybeLocal<v8::Object> ResolveSourceCallback(
|
||||
v8::Local<v8::Context> context,
|
||||
v8::Local<v8::String> specifier,
|
||||
v8::Local<v8::FixedArray> import_attributes,
|
||||
v8::Local<v8::Module> referrer);
|
||||
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
|
||||
|
||||
v8::Global<v8::Module> module_;
|
||||
|
|
|
|||
|
|
@ -770,6 +770,10 @@ static ExitCode ProcessGlobalArgsInternal(std::vector<std::string>* args,
|
|||
env_opts->abort_on_uncaught_exception = true;
|
||||
}
|
||||
|
||||
if (env_opts->experimental_wasm_modules) {
|
||||
v8_args.emplace_back("--js-source-phase-imports");
|
||||
}
|
||||
|
||||
#ifdef __POSIX__
|
||||
// Block SIGPROF signals when sleeping in epoll_wait/kevent/etc. Avoids the
|
||||
// performance penalty of frequent EINTR wakeups when the profiler is running.
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
|
|||
V(ERR_REQUIRE_ASYNC_MODULE, Error) \
|
||||
V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \
|
||||
V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \
|
||||
V(ERR_SOURCE_PHASE_NOT_DEFINED, SyntaxError) \
|
||||
V(ERR_STRING_TOO_LONG, Error) \
|
||||
V(ERR_TLS_INVALID_PROTOCOL_METHOD, TypeError) \
|
||||
V(ERR_TLS_PSK_SET_IDENTITY_HINT_FAILED, Error) \
|
||||
|
|
@ -278,6 +279,15 @@ inline v8::Local<v8::Object> ERR_BUFFER_TOO_LARGE(v8::Isolate* isolate) {
|
|||
return ERR_BUFFER_TOO_LARGE(isolate, message);
|
||||
}
|
||||
|
||||
inline void THROW_ERR_SOURCE_PHASE_NOT_DEFINED(v8::Isolate* isolate,
|
||||
v8::Local<v8::String> url) {
|
||||
std::string message = std::string(*v8::String::Utf8Value(isolate, url));
|
||||
return THROW_ERR_SOURCE_PHASE_NOT_DEFINED(
|
||||
isolate,
|
||||
"Source phase import object is not defined for module %s",
|
||||
message.c_str());
|
||||
}
|
||||
|
||||
inline v8::Local<v8::Object> ERR_STRING_TOO_LONG(v8::Isolate* isolate) {
|
||||
char message[128];
|
||||
snprintf(message, sizeof(message),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { spawnPromisified } from '../common/index.mjs';
|
||||
import * as fixtures from '../common/fixtures.mjs';
|
||||
import { strictEqual, match } from 'node:assert';
|
||||
import { ok, strictEqual, notStrictEqual, match } from 'node:assert';
|
||||
import { execPath } from 'node:process';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
|
|
@ -90,4 +90,161 @@ describe('ESM: WASM modules', { concurrency: !process.env.TEST_PARALLEL }, () =>
|
|||
match(stderr, /ExperimentalWarning/);
|
||||
match(stderr, /WebAssembly/);
|
||||
});
|
||||
|
||||
it('should support static source phase imports', async () => {
|
||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-wasm-modules',
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
[
|
||||
'import { strictEqual } from "node:assert";',
|
||||
`import * as wasmExports from ${JSON.stringify(fixtures.fileURL('es-modules/wasm-source-phase.js'))};`,
|
||||
'strictEqual(wasmExports.mod instanceof WebAssembly.Module, true);',
|
||||
'const AbstractModuleSourceProto = Object.getPrototypeOf(Object.getPrototypeOf(wasmExports.mod));',
|
||||
'const toStringTag = Object.getOwnPropertyDescriptor(AbstractModuleSourceProto, Symbol.toStringTag).get;',
|
||||
'strictEqual(toStringTag.call(wasmExports.mod), "WebAssembly.Module");',
|
||||
].join('\n'),
|
||||
]);
|
||||
|
||||
strictEqual(stderr, '');
|
||||
strictEqual(stdout, '');
|
||||
strictEqual(code, 0);
|
||||
});
|
||||
|
||||
// TODO: Enable this once https://github.com/nodejs/node/pull/56842 lands.
|
||||
it.skip('should support dynamic source phase imports', async () => {
|
||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-wasm-modules',
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
[
|
||||
'import { strictEqual } from "node:assert";',
|
||||
`import * as wasmExports from ${JSON.stringify(fixtures.fileURL('es-modules/wasm-source-phase.js'))};`,
|
||||
'strictEqual(wasmExports.mod instanceof WebAssembly.Module, true);',
|
||||
'strictEqual(await wasmExports.dyn("./simple.wasm") instanceof WebAssembly.Module, true);',
|
||||
'const AbstractModuleSourceProto = Object.getPrototypeOf(Object.getPrototypeOf(wasmExports.mod));',
|
||||
'const toStringTag = Object.getOwnPropertyDescriptor(AbstractModuleSourceProto, Symbol.toStringTag).get;',
|
||||
'strictEqual(toStringTag.call(wasmExports.mod), "WebAssembly.Module");',
|
||||
].join('\n'),
|
||||
]);
|
||||
|
||||
strictEqual(stderr, '');
|
||||
strictEqual(stdout, '');
|
||||
strictEqual(code, 0);
|
||||
});
|
||||
|
||||
it('should not execute source phase imports', async () => {
|
||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-wasm-modules',
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
[
|
||||
'import { strictEqual } from "node:assert";',
|
||||
`import source mod from ${JSON.stringify(fixtures.fileURL('es-modules/unimportable.wasm'))};`,
|
||||
'assert.strictEqual(mod instanceof WebAssembly.Module, true);',
|
||||
`await assert.rejects(import(${JSON.stringify(fixtures.fileURL('es-modules/unimportable.wasm'))}));`,
|
||||
].join('\n'),
|
||||
]);
|
||||
|
||||
strictEqual(stderr, '');
|
||||
strictEqual(stdout, '');
|
||||
strictEqual(code, 0);
|
||||
});
|
||||
|
||||
// TODO: Enable this once https://github.com/nodejs/node/pull/56842 lands.
|
||||
it.skip('should not execute dynamic source phase imports', async () => {
|
||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-wasm-modules',
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
`await import.source(${JSON.stringify(fixtures.fileURL('es-modules/unimportable.wasm'))})`,
|
||||
]);
|
||||
|
||||
strictEqual(stderr, '');
|
||||
strictEqual(stdout, '');
|
||||
strictEqual(code, 0);
|
||||
});
|
||||
|
||||
// TODO: Enable this once https://github.com/nodejs/node/pull/56842 lands.
|
||||
it.skip('should throw for dynamic source phase imports not defined', async () => {
|
||||
const fileUrl = fixtures.fileURL('es-modules/wasm-source-phase.js');
|
||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-wasm-modules',
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
[
|
||||
'import { ok, strictEqual } from "node:assert";',
|
||||
`await assert.rejects(import.source(${JSON.stringify(fileUrl)}), (e) => {`,
|
||||
' strictEqual(e instanceof SyntaxError, true);',
|
||||
' strictEqual(e.message.includes("Source phase import object is not defined for module"), true);',
|
||||
` strictEqual(e.message.includes(${JSON.stringify(fileUrl)}), true);`,
|
||||
'});',
|
||||
].join('\n'),
|
||||
]);
|
||||
|
||||
strictEqual(stderr, '');
|
||||
strictEqual(stdout, '');
|
||||
strictEqual(code, 0);
|
||||
});
|
||||
|
||||
it('should throw for static source phase imports not defined', async () => {
|
||||
const fileUrl = fixtures.fileURL('es-modules/wasm-source-phase.js');
|
||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-wasm-modules',
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
`import source nosource from ${JSON.stringify(fileUrl)};`,
|
||||
]);
|
||||
match(stderr, /Source phase import object is not defined for module/);
|
||||
ok(stderr.includes(fileUrl));
|
||||
strictEqual(stdout, '');
|
||||
notStrictEqual(code, 0);
|
||||
});
|
||||
|
||||
it('should throw for vm source phase static import', async () => {
|
||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-wasm-modules',
|
||||
'--experimental-vm-modules',
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
[
|
||||
'const m1 = new vm.SourceTextModule("import source x from \\"y\\";");',
|
||||
'const m2 = new vm.SourceTextModule("export var p = 5;");',
|
||||
'await m1.link(() => m2);',
|
||||
'await m1.evaluate();',
|
||||
].join('\n'),
|
||||
]);
|
||||
match(stderr, /Source phase import object is not defined for module/);
|
||||
strictEqual(stdout, '');
|
||||
notStrictEqual(code, 0);
|
||||
});
|
||||
|
||||
// TODO: Enable this once https://github.com/nodejs/node/pull/56842 lands.
|
||||
it.skip('should throw for vm source phase dynamic import', async () => {
|
||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [
|
||||
'--no-warnings',
|
||||
'--experimental-wasm-modules',
|
||||
'--experimental-vm-modules',
|
||||
'--input-type=module',
|
||||
'--eval',
|
||||
[
|
||||
'import { constants } from "node:vm";',
|
||||
'const opts = { importModuleDynamically: () => m2 };',
|
||||
'const m1 = new vm.SourceTextModule("await import.source(\\"y\\");", opts);',
|
||||
'const m2 = new vm.SourceTextModule("export var p = 5;");',
|
||||
'await m1.link(() => m2);',
|
||||
'await m1.evaluate();',
|
||||
].join('\n'),
|
||||
]);
|
||||
match(stderr, /Source phase import object is not defined for module/);
|
||||
strictEqual(stdout, '');
|
||||
notStrictEqual(code, 0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
BIN
test/fixtures/es-modules/unimportable.wasm
vendored
Normal file
BIN
test/fixtures/es-modules/unimportable.wasm
vendored
Normal file
Binary file not shown.
7
test/fixtures/es-modules/wasm-source-phase.js
vendored
Normal file
7
test/fixtures/es-modules/wasm-source-phase.js
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import source mod from './simple.wasm';
|
||||
|
||||
export function dyn (specifier) {
|
||||
return import.source(specifier);
|
||||
}
|
||||
|
||||
export { mod };
|
||||
|
|
@ -59,10 +59,11 @@ async function test() {
|
|||
|
||||
{
|
||||
const s = new Script('import("foo", { with: { key: "value" } })', {
|
||||
importModuleDynamically: common.mustCall((specifier, wrap, attributes) => {
|
||||
importModuleDynamically: common.mustCall((specifier, wrap, attributes, phase) => {
|
||||
assert.strictEqual(specifier, 'foo');
|
||||
assert.strictEqual(wrap, s);
|
||||
assert.deepStrictEqual(attributes, { __proto__: null, key: 'value' });
|
||||
assert.strictEqual(phase, 'evaluation');
|
||||
return foo;
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user