Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,14 @@ The JS execution context is not associated with a Node.js environment.
This may occur when Node.js is used as an embedded library and some hooks
for the JS engine are not set up properly.

<a id="ERR_FAILED_IMPORT_ASSERTION"></a>
### `ERR_FAILED_IMPORT_ASSERTION`
<!-- YAML
added: REPLACEME
-->

An import assertion has failed, preventing the specified module to be imported.

<a id="ERR_FALSY_VALUE_REJECTION"></a>
### `ERR_FALSY_VALUE_REJECTION`

Expand Down
39 changes: 39 additions & 0 deletions doc/api/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ executed in specific contexts.
<!-- YAML
added: v0.3.1
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39921
description: Added suppoort of import assertions to the
`importModuleDynamically` parameter.
- version: v10.6.0
pr-url: https://github.com/nodejs/node/pull/20300
description: The `produceCachedData` is deprecated in favour of
Expand Down Expand Up @@ -91,6 +95,8 @@ changes:
using it in a production environment.
* `specifier` {string} specifier passed to `import()`
* `script` {vm.Script}
* `import_assertions` {Object} The `"assert"` value passed to the
`optionExpression` optional parameter.
* 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.
Expand Down Expand Up @@ -642,6 +648,13 @@ The `vm.SourceTextModule` class provides the [Source Text Module Record][] as
defined in the ECMAScript specification.

### `new vm.SourceTextModule(code[, options])`
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39921
description: Added suppoort of import assertions to the
`importModuleDynamically` parameter.
-->

* `code` {string} JavaScript Module code to parse
* `options`
Expand All @@ -667,6 +680,8 @@ defined in the ECMAScript specification.
`import()` will reject with [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][].
* `specifier` {string} specifier passed to `import()`
* `module` {vm.Module}
* `import_assertions` {Object} The `"assert"` value passed to the
`optionExpression` optional parameter.
* 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.
Expand Down Expand Up @@ -852,6 +867,10 @@ const vm = require('vm');
<!-- YAML
added: v10.10.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39921
description: Added suppoort of import assertions to the
`importModuleDynamically` parameter.
- version: v15.9.0
pr-url: https://github.com/nodejs/node/pull/35431
description: Added `importModuleDynamically` option again.
Expand Down Expand Up @@ -893,6 +912,8 @@ changes:
considered stable.
* `specifier` {string} specifier passed to `import()`
* `function` {Function}
* `import_assertions` {Object} The `"assert"` value passed to the
`optionExpression` optional parameter.
* 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.
Expand Down Expand Up @@ -1068,6 +1089,10 @@ vm.measureMemory({ mode: 'detailed', execution: 'eager' })
<!-- YAML
added: v0.3.1
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39921
description: Added suppoort of import assertions to the
`importModuleDynamically` parameter.
- version: v6.3.0
pr-url: https://github.com/nodejs/node/pull/6635
description: The `breakOnSigint` option is supported now.
Expand Down Expand Up @@ -1113,6 +1138,8 @@ changes:
using it in a production environment.
* `specifier` {string} specifier passed to `import()`
* `script` {vm.Script}
* `import_assertions` {Object} The `"assert"` value passed to the
`optionExpression` optional parameter.
* 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.
Expand Down Expand Up @@ -1145,6 +1172,10 @@ console.log(contextObject);
<!-- YAML
added: v0.3.1
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39921
description: Added suppoort of import assertions to the
`importModuleDynamically` parameter.
- version: v14.6.0
pr-url: https://github.com/nodejs/node/pull/34023
description: The `microtaskMode` option is supported now.
Expand Down Expand Up @@ -1211,6 +1242,8 @@ changes:
using it in a production environment.
* `specifier` {string} specifier passed to `import()`
* `script` {vm.Script}
* `import_assertions` {Object} The `"assert"` value passed to the
`optionExpression` optional parameter.
* 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.
Expand Down Expand Up @@ -1247,6 +1280,10 @@ console.log(contextObject);
<!-- YAML
added: v0.3.1
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39921
description: Added suppoort of import assertions to the
`importModuleDynamically` parameter.
- version: v6.3.0
pr-url: https://github.com/nodejs/node/pull/6635
description: The `breakOnSigint` option is supported now.
Expand Down Expand Up @@ -1290,6 +1327,8 @@ changes:
using it in a production environment.
* `specifier` {string} specifier passed to `import()`
* `script` {vm.Script}
* `import_assertions` {Object} The `"assert"` value passed to the
`optionExpression` optional parameter.
* 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.
Expand Down
3 changes: 3 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,9 @@ E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported',
RangeError);
E('ERR_EVAL_ESM_CANNOT_PRINT', '--print cannot be used with ESM input', Error);
E('ERR_EVENT_RECURSION', 'The event "%s" is already being dispatched', Error);
E('ERR_FAILED_IMPORT_ASSERTION', (request, key, expectedValue, actualValue) => {
return `Failed to load module "${request}", expected ${key} to be ${JSONStringify(expectedValue)}, got ${JSONStringify(actualValue)} instead`;
}, TypeError);
E('ERR_FALSY_VALUE_REJECTION', function(reason) {
this.reason = reason;
return 'Promise was rejected with falsy value';
Expand Down
10 changes: 6 additions & 4 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -1021,9 +1021,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
filename,
lineOffset: 0,
displayErrors: true,
importModuleDynamically: async (specifier) => {
importModuleDynamically: async (specifier, _, import_assertions) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn’t import_assertions be camelcased everywhere it appears?

const loader = asyncESM.esmLoader;
return loader.import(specifier, normalizeReferrerURL(filename));
return loader.import(specifier, normalizeReferrerURL(filename),
import_assertions);
},
});
}
Expand All @@ -1036,9 +1037,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
'__dirname',
], {
filename,
importModuleDynamically(specifier) {
importModuleDynamically(specifier, _, import_assertions) {
const loader = asyncESM.esmLoader;
return loader.import(specifier, normalizeReferrerURL(filename));
return loader.import(specifier, normalizeReferrerURL(filename),
import_assertions);
},
});
} catch (err) {
Expand Down
43 changes: 30 additions & 13 deletions lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
} = primordials;

const {
ERR_FAILED_IMPORT_ASSERTION,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_MODULE_SPECIFIER,
Expand Down Expand Up @@ -202,33 +203,48 @@ class ESMLoader {
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
const module = new ModuleWrap(url, undefined, source, 0, 0);
callbackMap.set(module, {
importModuleDynamically: (specifier, { url }) => {
return this.import(specifier, url);
importModuleDynamically: (specifier, { url }, import_assertions) => {
return this.import(specifier, url, import_assertions);
}
});

return module;
};
const job = new ModuleJob(this, url, evalInstance, false, false);
this.moduleMap.set(url, job);
this.moduleMap.set(url, job, undefined);
const { module } = await job.run();

return {
namespace: module.getNamespace(),
};
}

async getModuleJob(specifier, parentURL) {
async getModuleJob(specifier, parentURL, import_assertions) {
const { format, url } = await this.resolve(specifier, parentURL);
let job = this.moduleMap.get(url);
// CommonJS will set functions for lazy job evaluation.
if (typeof job === 'function') this.moduleMap.set(url, job = job());

if (job !== undefined) return job;
let job
const jobMap = this.moduleMap.get(url);

if (jobMap != null) {
// To avoid race conditions, always wait for non assertion job to fulfill
if(import_assertions.type != null) await jobMap[undefined];

let job = jobMap[import_assertions.type];

// CommonJS will set functions for lazy job evaluation.
if (typeof job === 'function') this.moduleMap.set(url, job = job(), import_assertions.type);

if (job !== undefined) return job;
}

const moduleProvider = async (url, isMain) => {
const { format: finalFormat, source } = await this.load(url, { format });

if (import_assertions.type === 'json' && finalFormat !== 'json') {
throw new ERR_FAILED_IMPORT_ASSERTION(
url, 'type', import_assertions.type, finalFormat);
}

const translator = translators.get(finalFormat);

if (!translator) throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat);
Expand All @@ -249,7 +265,7 @@ class ESMLoader {
inspectBrk
);

this.moduleMap.set(url, job);
this.moduleMap.set(url, job, import_assertions.type);

return job;
}
Expand All @@ -262,18 +278,19 @@ class ESMLoader {
* loader module.
*
* @param {string | string[]} specifiers Path(s) to the module
* @param {string} [parentURL] Path of the parent importing the module
* @returns {object | object[]} A list of module export(s)
* @param {string} parentURL Path of the parent importing the module
* @param {Record<string, Record<string, string>>} import_assertions
* @returns {Promise<object | object[]>} A list of module export(s)
*/
async import(specifiers, parentURL) {
async import(specifiers, parentURL, import_assertions) {
const wasArr = ArrayIsArray(specifiers);
if (!wasArr) specifiers = [specifiers];

const count = specifiers.length;
const jobs = new Array(count);

for (let i = 0; i < count; i++) {
jobs[i] = this.getModuleJob(specifiers[i], parentURL)
jobs[i] = this.getModuleJob(specifiers[i], parentURL, import_assertions)
.then((job) => job.run())
.then(({ module }) => module.getNamespace());
}
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ class ModuleJob {
// so that circular dependencies can't cause a deadlock by two of
// these `link` callbacks depending on each other.
const dependencyJobs = [];
const promises = this.module.link(async (specifier) => {
const jobPromise = this.loader.getModuleJob(specifier, url);
const promises = this.module.link(async (specifier, assertions) => {
const jobPromise = this.loader.getModuleJob(specifier, url, assertions);
ArrayPrototypePush(dependencyJobs, jobPromise);
const job = await jobPromise;
return job.modulePromise;
Expand Down
7 changes: 5 additions & 2 deletions lib/internal/modules/esm/module_map.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const ModuleJob = require('internal/modules/esm/module_job');
const {
ObjectCreate,
SafeMap,
} = primordials;
let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
Expand All @@ -17,14 +18,16 @@ class ModuleMap extends SafeMap {
validateString(url, 'url');
return super.get(url);
}
set(url, job) {
set(url, job, import_assertion_type) {
validateString(url, 'url');
if (job instanceof ModuleJob !== true &&
typeof job !== 'function') {
throw new ERR_INVALID_ARG_TYPE('job', 'ModuleJob', job);
}
debug(`Storing ${url} in ModuleMap`);
return super.set(url, job);
const jobMap = super.get(url) ?? ObjectCreate(null);
jobMap[import_assertion_type] = job
return super.set(url, jobMap);
}
has(url) {
validateString(url, 'url');
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ function errPath(url) {
return url;
}

async function importModuleDynamically(specifier, { url }) {
return asyncESM.esmLoader.import(specifier, url);
async function importModuleDynamically(specifier, { url }, assertions) {
return asyncESM.esmLoader.import(specifier, url, assertions);
}

function createImportMetaResolve(defaultParentUrl) {
Expand Down
6 changes: 3 additions & 3 deletions lib/internal/modules/run_main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const {
ObjectCreate,
StringPrototypeEndsWith,
} = primordials;
const CJSLoader = require('internal/modules/cjs/loader');
Expand Down Expand Up @@ -46,9 +47,8 @@ function runMainESM(mainPath) {

handleMainPromise(loadESM((esmLoader) => {
const main = path.isAbsolute(mainPath) ?
pathToFileURL(mainPath).href :
mainPath;
return esmLoader.import(main);
pathToFileURL(mainPath).href : mainPath;
return esmLoader.import(main, undefined, ObjectCreate(null));
}));
}

Expand Down
10 changes: 8 additions & 2 deletions lib/internal/process/esm_loader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
'use strict';

const {
ObjectCreate,
} = primordials;

const {
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
} = require('internal/errors').codes;
Expand All @@ -22,13 +26,14 @@ exports.initializeImportMetaObject = function(wrap, meta) {
}
};

exports.importModuleDynamicallyCallback = async function(wrap, specifier) {
exports.importModuleDynamicallyCallback =
async function importModuleDynamicallyCallback(wrap, specifier, assertions) {
const { callbackMap } = internalBinding('module_wrap');
if (callbackMap.has(wrap)) {
const { importModuleDynamically } = callbackMap.get(wrap);
if (importModuleDynamically !== undefined) {
return importModuleDynamically(
specifier, getModuleFromWrap(wrap) || wrap);
specifier, getModuleFromWrap(wrap) || wrap, assertions);
}
}
throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING();
Expand Down Expand Up @@ -69,6 +74,7 @@ async function initializeLoader() {
const exports = await internalEsmLoader.import(
customLoaders,
pathToFileURL(cwd).href,
ObjectCreate(null),
);

// Hooks must then be added to external/public loader
Expand Down
6 changes: 3 additions & 3 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ function evalScript(name, body, breakFirstLine, print) {
filename: name,
displayErrors: true,
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
async importModuleDynamically(specifier) {
const loader = await asyncESM.esmLoader;
return loader.import(specifier, baseUrl);
importModuleDynamically(specifier, _, import_assertions) {
const loader = asyncESM.esmLoader;
return loader.import(specifier, baseUrl, import_assertions);
}
}));
if (print) {
Expand Down
Loading