diff --git a/doc/api/errors.md b/doc/api/errors.md
index e02624f3675558..0817d2572b3ae5 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -2724,6 +2724,13 @@ 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.
+
+
+### `ERR_SOURCE_PHASE_NOT_DEFINED`
+
+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)`.
+
### `ERR_SQLITE_ERROR`
diff --git a/doc/api/esm.md b/doc/api/esm.md
index b3cf2eade965f8..c8d6e23de5b477 100644
--- a/doc/api/esm.md
+++ b/doc/api/esm.md
@@ -667,17 +667,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);
```
@@ -687,7 +689,37 @@ 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
+
+
+
+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
+});
+```
@@ -1124,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
diff --git a/doc/api/vm.md b/doc/api/vm.md
index a5dd038070a498..d9480f84f81785 100644
--- a/doc/api/vm.md
+++ b/doc/api/vm.md
@@ -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.
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index 2e0492151a29bc..acec19221de5ff 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -1501,7 +1501,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);
diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js
index a3b437ade87c75..302011a4d2be7a 100644
--- a/lib/internal/modules/esm/loader.js
+++ b/lib/internal/modules/esm/loader.js
@@ -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}
*/
- 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}
*/
- 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;
@@ -360,7 +366,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().namespace };
@@ -372,9 +378,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;
@@ -405,6 +412,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
@@ -446,7 +454,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;
@@ -526,13 +534,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;
@@ -558,6 +567,7 @@ class ModuleLoader {
url,
importAttributes,
moduleOrModulePromise,
+ phase,
isMain,
inspectBrk,
isForRequireInImportedCJS,
@@ -575,11 +585,18 @@ class ModuleLoader {
* @param {string} parentURL Path of the parent importing the module.
* @param {Record} 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}
*/
- 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();
}, {
diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js
index 846a336d27547e..0cdb78d2f0097d 100644
--- a/lib/internal/modules/esm/module_job.js
+++ b/lib/internal/modules/esm/module_job.js
@@ -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,
@@ -58,8 +58,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;
@@ -77,14 +79,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} 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.
@@ -96,22 +99,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} Dependency module jobs.
*/
- async _link() {
+ async #link() {
this.module = await this.modulePromise;
assert(this.module instanceof ModuleWrap);
@@ -122,23 +140,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;
@@ -148,17 +176,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);
@@ -246,6 +274,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 };
@@ -261,7 +290,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;
}
@@ -316,40 +346,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);
}
}
@@ -358,6 +412,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) {
@@ -381,6 +436,7 @@ class ModuleJobSync extends ModuleJobBase {
}
runSync() {
+ 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
diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js
index 678659aacaad3e..c300b0305318bd 100644
--- a/lib/internal/modules/esm/translators.js
+++ b/lib/internal/modules/esm/translators.js
@@ -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
diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js
index 99061e62976e7c..b0bd8a449e84f4 100644
--- a/lib/internal/modules/esm/utils.js
+++ b/lib/internal/modules/esm/utils.js
@@ -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,
@@ -106,6 +106,7 @@ function getConditionsSet(conditions) {
* @param {string} specifier
* @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
* @param {Record} attributes
+ * @param {number} phase
* @returns { Promise }
*/
@@ -206,58 +207,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} attributes - The import attributes object.
* @param {string|null|undefined} referrerName - name of the referrer.
* @returns {Promise} - 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} attributes - The import attributes object.
* @param {string|null|undefined} referrerName - name of the referrer.
* @returns {Promise} - 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} attributes - The import attributes object.
* @param {string|null|undefined} referrerName - name of the referrer.
* @returns {Promise} - 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) {
diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js
index 9c90f0f6d3e33b..044926b4138d79 100644
--- a/lib/internal/modules/run_main.js
+++ b/lib/internal/modules/run_main.js
@@ -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);
});
}
}
diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js
index d4d7a604851ef1..c7f35759805c55 100644
--- a/lib/internal/process/execution.js
+++ b/lib/internal/process/execution.js
@@ -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
diff --git a/lib/internal/vm.js b/lib/internal/vm.js
index 0b9865ea9a0cf6..7c28b640bd47f1 100644
--- a/lib/internal/vm.js
+++ b/lib/internal/vm.js
@@ -31,6 +31,15 @@ const {
},
} = internalBinding('util');
+/**
+ * @callback VmImportModuleDynamicallyCallback
+ * @param {string} specifier
+ * @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
+ * @param {Record} attributes
+ * @param {string} phase
+ * @returns { Promise }
+ */
+
/**
* 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.
diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js
index 16f6422c9b8ee9..5c20f51cf481ad 100644
--- a/lib/internal/vm/module.js
+++ b/lib/internal/vm/module.js
@@ -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} attributes
+ * @param {number} phase
+ * @returns { Promise }
+ */
+
+/**
+ * @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;
diff --git a/lib/repl.js b/lib/repl.js
index fd8b51b9547e8a..6cfceacac620f0 100644
--- a/lib/repl.js
+++ b/lib/repl.js
@@ -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.
diff --git a/src/env_properties.h b/src/env_properties.h
index 43725de9b51237..e64d92f4b54996 100644
--- a/src/env_properties.h
+++ b/src/env_properties.h
@@ -290,6 +290,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") \
diff --git a/src/module_wrap.cc b/src/module_wrap.cc
index 649ec428e2dd6f..460ec673262b23 100644
--- a/src/module_wrap.cc
+++ b/src/module_wrap.cc
@@ -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);
@@ -102,8 +105,7 @@ Local ModuleWrap::context() const {
return obj.As