diff --git a/doc/api/errors.md b/doc/api/errors.md
index 2369e6eda72082..b2e6ad86f84f84 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1679,6 +1679,36 @@ is set for the `Http2Stream`.
An attempt was made to construct an object using a non-public constructor.
+
+
+### `ERR_IMPORT_ASSERTION_TYPE_FAILED`
+
+
+
+An import assertion has failed, preventing the specified module to be imported.
+
+
+
+### `ERR_IMPORT_ASSERTION_TYPE_MISSING`
+
+
+
+An import assertion is missing, preventing the specified module to be imported.
+
+
+
+### `ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED`
+
+
+
+An import assertion is not supported by this version of Node.js.
+
### `ERR_INCOMPATIBLE_OPTION_PAIR`
diff --git a/doc/api/esm.md b/doc/api/esm.md
index f6d340c04ad4cd..4c784da560fbaf 100644
--- a/doc/api/esm.md
+++ b/doc/api/esm.md
@@ -7,6 +7,9 @@
+
+> Stability: 1 - Experimental
+
+The [Import Assertions proposal][] adds an inline syntax for module import
+statements to pass on more information alongside the module specifier.
+
+```js
+import fooData from './foo.json' assert { type: 'json' };
+
+const { default: barData } =
+ await import('./bar.json', { assert: { type: 'json' } });
+```
+
+Node.js supports the following `type` values, for which the assertion is
+mandatory:
+
+| Assertion `type` | Needed for |
+| ---------------- | ---------------- |
+| `'json'` | [JSON modules][] |
+
## Builtin modules
[Core modules][] provide named exports of their public API. A
@@ -511,10 +539,8 @@ same path.
Assuming an `index.mjs` with
-
-
```js
-import packageConfig from './package.json';
+import packageConfig from './package.json' assert { type: 'json' };
```
The `--experimental-json-modules` flag is needed for the module
@@ -525,18 +551,20 @@ node index.mjs # fails
node --experimental-json-modules index.mjs # works
```
+The `assert { type: 'json' }` syntax is mandatory; see [Import Assertions][].
+
## Wasm modules
> Stability: 1 - Experimental
-Importing Web Assembly modules is supported under the
+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.
This integration is in line with the
-[ES Module Integration Proposal for Web Assembly][].
+[ES Module Integration Proposal for WebAssembly][].
For example, an `index.mjs` containing:
@@ -602,12 +630,20 @@ CommonJS modules loaded.
#### `resolve(specifier, context, defaultResolve)`
+
+
> Note: The loaders API is being redesigned. This hook may disappear or its
> signature may change. Do not rely on the API described below.
* `specifier` {string}
* `context` {Object}
* `conditions` {string\[]}
+ * `importAssertions` {Object}
* `parentURL` {string|undefined}
* `defaultResolve` {Function} The Node.js default resolver.
* Returns: {Object}
@@ -684,13 +720,15 @@ export async function resolve(specifier, context, defaultResolve) {
* `context` {Object}
* `format` {string|null|undefined} The format optionally supplied by the
`resolve` hook.
+ * `importAssertions` {Object}
* `defaultLoad` {Function}
* Returns: {Object}
* `format` {string}
* `source` {string|ArrayBuffer|TypedArray}
The `load` hook provides a way to define a custom method of determining how
-a URL should be interpreted, retrieved, and parsed.
+a URL should be interpreted, retrieved, and parsed. It is also in charge of
+validating the import assertion.
The final value of `format` must be one of the following:
@@ -1372,7 +1410,10 @@ success!
[Core modules]: modules.md#core-modules
[Dynamic `import()`]: https://wiki.developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports
[ECMAScript Top-Level `await` proposal]: https://github.com/tc39/proposal-top-level-await/
-[ES Module Integration Proposal for Web Assembly]: https://github.com/webassembly/esm-integration
+[ES Module Integration Proposal for WebAssembly]: https://github.com/webassembly/esm-integration
+[Import Assertions]: #import-assertions
+[Import Assertions proposal]: https://github.com/tc39/proposal-import-assertions
+[JSON modules]: #json-modules
[Node.js Module Resolution Algorithm]: #resolver-algorithm-specification
[Terminology]: #terminology
[URL]: https://url.spec.whatwg.org/
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index c510bd321512a0..a0b87013263754 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1083,6 +1083,12 @@ E('ERR_HTTP_SOCKET_ENCODING',
E('ERR_HTTP_TRAILER_INVALID',
'Trailers are invalid with this transfer encoding', Error);
E('ERR_ILLEGAL_CONSTRUCTOR', 'Illegal constructor', TypeError);
+E('ERR_IMPORT_ASSERTION_TYPE_FAILED',
+ 'Module "%s" is not of type "%s"', TypeError);
+E('ERR_IMPORT_ASSERTION_TYPE_MISSING',
+ 'Module "%s" needs an import assertion of type "%s"', TypeError);
+E('ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED',
+ 'Import assertion type "%s" is unsupported', TypeError);
E('ERR_INCOMPATIBLE_OPTION_PAIR',
'Option "%s" cannot be used in combination with option "%s"', TypeError);
E('ERR_INPUT_TYPE_NOT_ALLOWED', '--input-type can only be used with string ' +
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index b8eff0440624a4..5c2de2d9c57ffd 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -1021,9 +1021,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
filename,
lineOffset: 0,
displayErrors: true,
- importModuleDynamically: async (specifier) => {
+ importModuleDynamically: async (specifier, _, importAssertions) => {
const loader = asyncESM.esmLoader;
- return loader.import(specifier, normalizeReferrerURL(filename));
+ return loader.import(specifier, normalizeReferrerURL(filename),
+ importAssertions);
},
});
}
@@ -1036,9 +1037,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
'__dirname',
], {
filename,
- importModuleDynamically(specifier) {
+ importModuleDynamically(specifier, _, importAssertions) {
const loader = asyncESM.esmLoader;
- return loader.import(specifier, normalizeReferrerURL(filename));
+ return loader.import(specifier, normalizeReferrerURL(filename),
+ importAssertions);
},
});
} catch (err) {
diff --git a/lib/internal/modules/esm/assert.js b/lib/internal/modules/esm/assert.js
new file mode 100644
index 00000000000000..f0f8387bea1cbf
--- /dev/null
+++ b/lib/internal/modules/esm/assert.js
@@ -0,0 +1,110 @@
+'use strict';
+
+const {
+ ArrayPrototypeFilter,
+ ArrayPrototypeIncludes,
+ ObjectCreate,
+ ObjectValues,
+ ObjectPrototypeHasOwnProperty,
+} = primordials;
+const { validateString } = require('internal/validators');
+
+const {
+ ERR_IMPORT_ASSERTION_TYPE_FAILED,
+ ERR_IMPORT_ASSERTION_TYPE_MISSING,
+ ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED,
+} = require('internal/errors').codes;
+
+// The HTML spec has an implied default type of `'javascript'`.
+const kImplicitAssertType = 'javascript';
+
+/**
+ * Define a map of module formats to import assertion types (the value of
+ * `type` in `assert { type: 'json' }`).
+ * @type {Map}
+ */
+const formatTypeMap = {
+ '__proto__': null,
+ 'builtin': kImplicitAssertType,
+ 'commonjs': kImplicitAssertType,
+ 'json': 'json',
+ 'module': kImplicitAssertType,
+ 'wasm': kImplicitAssertType, // It's unclear whether the HTML spec will require an assertion type or not for Wasm; see https://github.com/WebAssembly/esm-integration/issues/42
+};
+
+/**
+ * The HTML spec disallows the default type to be explicitly specified
+ * (for now); so `import './file.js'` is okay but
+ * `import './file.js' assert { type: 'javascript' }` throws.
+ * @type {Array}
+ */
+const supportedAssertionTypes = ArrayPrototypeFilter(
+ ObjectValues(formatTypeMap),
+ (type) => type !== kImplicitAssertType);
+
+
+/**
+ * Test a module's import assertions.
+ * @param {string} url The URL of the imported module, for error reporting.
+ * @param {string} format One of Node's supported translators
+ * @param {Record} importAssertions Validations for the
+ * module import.
+ * @returns {true}
+ * @throws {TypeError} If the format and assertion type are incompatible.
+ */
+function validateAssertions(url, format,
+ importAssertions = ObjectCreate(null)) {
+ const validType = formatTypeMap[format];
+
+ switch (validType) {
+ case undefined:
+ // Ignore assertions for module formats we don't recognize, to allow new
+ // formats in the future.
+ return true;
+
+ case kImplicitAssertType:
+ // This format doesn't allow an import assertion type, so the property
+ // must not be set on the import assertions object.
+ if (!ObjectPrototypeHasOwnProperty(importAssertions, 'type')) {
+ return true;
+ }
+ return handleInvalidType(url, importAssertions.type);
+
+ case importAssertions.type:
+ // The asserted type is the valid type for this format.
+ return true;
+
+ default:
+ // There is an expected type for this format, but the value of
+ // `importAssertions.type` might not have been it.
+ if (!ObjectPrototypeHasOwnProperty(importAssertions, 'type')) {
+ // `type` wasn't specified at all.
+ throw new ERR_IMPORT_ASSERTION_TYPE_MISSING(url, validType);
+ }
+ handleInvalidType(url, importAssertions.type);
+ }
+}
+
+/**
+ * Throw the correct error depending on what's wrong with the type assertion.
+ * @param {string} url The resolved URL for the module to be imported
+ * @param {string} type The value of the import assertion `type` property
+ */
+function handleInvalidType(url, type) {
+ // `type` might have not been a string.
+ validateString(type, 'type');
+
+ // `type` might not have been one of the types we understand.
+ if (!ArrayPrototypeIncludes(supportedAssertionTypes, type)) {
+ throw new ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED(type);
+ }
+
+ // `type` was the wrong value for this format.
+ throw new ERR_IMPORT_ASSERTION_TYPE_FAILED(url, type);
+}
+
+
+module.exports = {
+ kImplicitAssertType,
+ validateAssertions,
+};
diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js
index 38785e78f338ce..67123792e8903a 100644
--- a/lib/internal/modules/esm/load.js
+++ b/lib/internal/modules/esm/load.js
@@ -3,14 +3,26 @@
const { defaultGetFormat } = require('internal/modules/esm/get_format');
const { defaultGetSource } = require('internal/modules/esm/get_source');
const { translators } = require('internal/modules/esm/translators');
+const { validateAssertions } = require('internal/modules/esm/assert');
+/**
+ * Node.js default load hook.
+ * @param {string} url
+ * @param {object} context
+ * @returns {object}
+ */
async function defaultLoad(url, context) {
let {
format,
source,
} = context;
+ const { importAssertions } = context;
- if (!translators.has(format)) format = defaultGetFormat(url);
+ if (!format || !translators.has(format)) {
+ format = defaultGetFormat(url);
+ }
+
+ validateAssertions(url, format, importAssertions);
if (
format === 'builtin' ||
diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js
index b12a87a9021242..3b8d2ae158f930 100644
--- a/lib/internal/modules/esm/loader.js
+++ b/lib/internal/modules/esm/loader.js
@@ -10,6 +10,7 @@ const {
ArrayPrototypePush,
FunctionPrototypeBind,
FunctionPrototypeCall,
+ ObjectAssign,
ObjectCreate,
ObjectSetPrototypeOf,
PromiseAll,
@@ -202,15 +203,16 @@ 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 }, importAssertions) => {
+ return this.import(specifier, url, importAssertions);
}
});
return module;
};
- const job = new ModuleJob(this, url, evalInstance, false, false);
- this.moduleMap.set(url, job);
+ const job = new ModuleJob(
+ this, url, undefined, evalInstance, false, false);
+ this.moduleMap.set(url, undefined, job);
const { module } = await job.run();
return {
@@ -218,20 +220,65 @@ class ESMLoader {
};
}
- async getModuleJob(specifier, parentURL) {
- const { format, url } = await this.resolve(specifier, parentURL);
- let job = this.moduleMap.get(url);
+ /**
+ * Get a (possibly still pending) module job from the cache,
+ * or create one and return its Promise.
+ * @param {string} specifier The string after `from` in an `import` statement,
+ * or the first parameter of an `import()`
+ * expression
+ * @param {string | undefined} parentURL The URL of the module importing this
+ * one, unless this is the Node.js entry
+ * point.
+ * @param {Record} importAssertions Validations for the
+ * module import.
+ * @returns {Promise} The (possibly pending) module job
+ */
+ async getModuleJob(specifier, parentURL, importAssertions) {
+ let importAssertionsForResolve;
+ if (this.#loaders.length !== 1) {
+ // We can skip cloning if there are no user provided loaders because
+ // the Node.js default resolve hook does not use import assertions.
+ importAssertionsForResolve =
+ ObjectAssign(ObjectCreate(null), importAssertions);
+ }
+ const { format, url } =
+ await this.resolve(specifier, parentURL, importAssertionsForResolve);
+
+ let job = this.moduleMap.get(url, importAssertions.type);
+
// CommonJS will set functions for lazy job evaluation.
- if (typeof job === 'function') this.moduleMap.set(url, job = job());
+ if (typeof job === 'function') {
+ this.moduleMap.set(url, undefined, job = job());
+ }
+
+ if (job === undefined) {
+ job = this.#createModuleJob(url, importAssertions, parentURL, format);
+ }
- if (job !== undefined) return job;
+ return job;
+ }
+ /**
+ * Create and cache an object representing a loaded module.
+ * @param {string} url The absolute URL that was resolved for this module
+ * @param {Record} importAssertions Validations for the
+ * module import.
+ * @param {string} [parentURL] The absolute URL of the module importing this
+ * one, unless this is the Node.js entry point
+ * @param {string} [format] The format hint possibly returned by the
+ * `resolve` hook
+ * @returns {Promise} The (possibly pending) module job
+ */
+ #createModuleJob(url, importAssertions, parentURL, format) {
const moduleProvider = async (url, isMain) => {
- const { format: finalFormat, source } = await this.load(url, { format });
+ const { format: finalFormat, source } = await this.load(
+ url, { format, importAssertions });
const translator = translators.get(finalFormat);
- if (!translator) throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat);
+ if (!translator) {
+ throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat);
+ }
return FunctionPrototypeCall(translator, this, url, source, isMain);
};
@@ -241,15 +288,16 @@ class ESMLoader {
getOptionValue('--inspect-brk')
);
- job = new ModuleJob(
+ const job = new ModuleJob(
this,
url,
+ importAssertions,
moduleProvider,
parentURL === undefined,
inspectBrk
);
- this.moduleMap.set(url, job);
+ this.moduleMap.set(url, importAssertions.type, job);
return job;
}
@@ -261,11 +309,13 @@ class ESMLoader {
* This method must NOT be renamed: it functions as a dynamic import on a
* 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 | string[]} specifiers Path(s) to the module.
+ * @param {string} parentURL Path of the parent importing the module.
+ * @param {Record} importAssertions Validations for the
+ * module import.
+ * @returns {Promise} A list of module export(s).
*/
- async import(specifiers, parentURL) {
+ async import(specifiers, parentURL, importAssertions) {
const wasArr = ArrayIsArray(specifiers);
if (!wasArr) specifiers = [specifiers];
@@ -273,7 +323,7 @@ class ESMLoader {
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, importAssertions)
.then((job) => job.run())
.then(({ module }) => module.getNamespace());
}
@@ -393,13 +443,16 @@ class ESMLoader {
* Resolve the location of the module.
*
* The internals of this WILL change when chaining is implemented,
- * depending on the resolution/consensus from #36954
+ * depending on the resolution/consensus from #36954.
* @param {string} originalSpecifier The specified URL path of the module to
- * be resolved
- * @param {String} parentURL The URL path of the module's parent
- * @returns {{ url: String }}
+ * be resolved.
+ * @param {string} [parentURL] The URL path of the module's parent.
+ * @param {ImportAssertions} [importAssertions] Assertions from the import
+ * statement or expression.
+ * @returns {{ url: string }}
*/
- async resolve(originalSpecifier, parentURL) {
+ async resolve(originalSpecifier, parentURL,
+ importAssertions = ObjectCreate(null)) {
const isMain = parentURL === undefined;
if (
@@ -423,6 +476,7 @@ class ESMLoader {
originalSpecifier,
{
conditions,
+ importAssertions,
parentURL,
},
defaultResolver,
diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js
index 2f699376d6eaea..018d598796f153 100644
--- a/lib/internal/modules/esm/module_job.js
+++ b/lib/internal/modules/esm/module_job.js
@@ -6,6 +6,7 @@ const {
ArrayPrototypePush,
ArrayPrototypeSome,
FunctionPrototype,
+ ObjectCreate,
ObjectSetPrototypeOf,
PromiseAll,
PromiseResolve,
@@ -52,8 +53,10 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
class ModuleJob {
// `loader` is the Loader instance used for loading dependencies.
// `moduleProvider` is a function
- constructor(loader, url, moduleProvider, isMain, inspectBrk) {
+ constructor(loader, url, importAssertions = ObjectCreate(null),
+ moduleProvider, isMain, inspectBrk) {
this.loader = loader;
+ this.importAssertions = importAssertions;
this.isMain = isMain;
this.inspectBrk = inspectBrk;
@@ -72,8 +75,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;
@@ -144,7 +147,14 @@ class ModuleJob {
const { url: childFileURL } = await this.loader.resolve(
childSpecifier, parentFileUrl,
);
- const { format } = await this.loader.load(childFileURL);
+ let format;
+ try {
+ // This might throw for non-CommonJS modules because we aren't passing
+ // in the import assertions and some formats require them; but we only
+ // care about CommonJS for the purposes of this error message.
+ ({ format } =
+ await this.loader.load(childFileURL));
+ } catch {}
if (format === 'commonjs') {
const importStatement = splitStack[1];
diff --git a/lib/internal/modules/esm/module_map.js b/lib/internal/modules/esm/module_map.js
index 9e1116a5647f5f..df02ebd708517f 100644
--- a/lib/internal/modules/esm/module_map.js
+++ b/lib/internal/modules/esm/module_map.js
@@ -1,7 +1,9 @@
'use strict';
const ModuleJob = require('internal/modules/esm/module_job');
+const { kImplicitAssertType } = require('internal/modules/esm/assert');
const {
+ ObjectCreate,
SafeMap,
} = primordials;
let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
@@ -13,22 +15,29 @@ const { validateString } = require('internal/validators');
// Tracks the state of the loader-level module cache
class ModuleMap extends SafeMap {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
- get(url) {
+ get(url, type = kImplicitAssertType) {
validateString(url, 'url');
- return super.get(url);
+ validateString(type, 'type');
+ return super.get(url)?.[type];
}
- set(url, job) {
+ set(url, type = kImplicitAssertType, job) {
validateString(url, 'url');
+ validateString(type, 'type');
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);
+ debug(`Storing ${url} (${
+ type === kImplicitAssertType ? 'implicit type' : type
+ }) in ModuleMap`);
+ const cachedJobsForUrl = super.get(url) ?? ObjectCreate(null);
+ cachedJobsForUrl[type] = job;
+ return super.set(url, cachedJobsForUrl);
}
- has(url) {
+ has(url, type = kImplicitAssertType) {
validateString(url, 'url');
- return super.has(url);
+ validateString(type, 'type');
+ return super.get(url)?.[type] !== undefined;
}
}
module.exports = ModuleMap;
diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js
index ba00041c417706..fdeaba0549ae9b 100644
--- a/lib/internal/modules/esm/translators.js
+++ b/lib/internal/modules/esm/translators.js
@@ -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) {
@@ -337,7 +337,7 @@ translators.set('json', async function jsonStrategy(url, source) {
// Strategy for loading a wasm module
translators.set('wasm', async function(url, source) {
- emitExperimentalWarning('Importing Web Assembly modules');
+ emitExperimentalWarning('Importing WebAssembly modules');
assertBufferSource(source, false, 'load');
diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js
index d0c08b75e7a524..9a0263024144fb 100644
--- a/lib/internal/modules/run_main.js
+++ b/lib/internal/modules/run_main.js
@@ -1,6 +1,7 @@
'use strict';
const {
+ ObjectCreate,
StringPrototypeEndsWith,
} = primordials;
const CJSLoader = require('internal/modules/cjs/loader');
@@ -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));
}));
}
diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js
index e10a4f413cdd0d..2bed5f3c762cf0 100644
--- a/lib/internal/process/execution.js
+++ b/lib/internal/process/execution.js
@@ -76,9 +76,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, _, importAssertions) {
+ const loader = asyncESM.esmLoader;
+ return loader.import(specifier, baseUrl, importAssertions);
}
}));
if (print) {
diff --git a/lib/repl.js b/lib/repl.js
index 4ee8e24d47588c..c85ccbde5a44ac 100644
--- a/lib/repl.js
+++ b/lib/repl.js
@@ -454,8 +454,9 @@ function REPLServer(prompt,
vm.createScript(fallbackCode, {
filename: file,
displayErrors: true,
- importModuleDynamically: async (specifier) => {
- return asyncESM.esmLoader.import(specifier, parentURL);
+ importModuleDynamically: (specifier, _, importAssertions) => {
+ return asyncESM.esmLoader.import(specifier, parentURL,
+ importAssertions);
}
});
} catch (fallbackError) {
@@ -496,8 +497,9 @@ function REPLServer(prompt,
script = vm.createScript(code, {
filename: file,
displayErrors: true,
- importModuleDynamically: async (specifier) => {
- return asyncESM.esmLoader.import(specifier, parentURL);
+ importModuleDynamically: (specifier, _, importAssertions) => {
+ return asyncESM.esmLoader.import(specifier, parentURL,
+ importAssertions);
}
});
} catch (e) {
diff --git a/src/node.cc b/src/node.cc
index 0fa51b269764f4..254181b6fbdb25 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -803,6 +803,13 @@ int ProcessGlobalArgs(std::vector* args,
return 12;
}
+ // TODO(aduh95): remove this when the harmony-import-assertions flag
+ // is removed in V8.
+ if (std::find(v8_args.begin(), v8_args.end(),
+ "--no-harmony-import-assertions") == v8_args.end()) {
+ v8_args.push_back("--harmony-import-assertions");
+ }
+
auto env_opts = per_process::cli_options->per_isolate->per_env;
if (std::find(v8_args.begin(), v8_args.end(),
"--abort-on-uncaught-exception") != v8_args.end() ||
diff --git a/test/common/fixtures.js b/test/common/fixtures.js
index e5e1d887df525e..3ee87e8b2d7b59 100644
--- a/test/common/fixtures.js
+++ b/test/common/fixtures.js
@@ -2,6 +2,7 @@
const path = require('path');
const fs = require('fs');
+const { pathToFileURL } = require('url');
const fixturesDir = path.join(__dirname, '..', 'fixtures');
@@ -9,6 +10,10 @@ function fixturesPath(...args) {
return path.join(fixturesDir, ...args);
}
+function fixturesFileURL(...args) {
+ return pathToFileURL(fixturesPath(...args));
+}
+
function readFixtureSync(args, enc) {
if (Array.isArray(args))
return fs.readFileSync(fixturesPath(...args), enc);
@@ -26,6 +31,7 @@ function readFixtureKeys(enc, ...names) {
module.exports = {
fixturesDir,
path: fixturesPath,
+ fileURL: fixturesFileURL,
readSync: readFixtureSync,
readKey: readFixtureKey,
readKeys: readFixtureKeys,
diff --git a/test/common/fixtures.mjs b/test/common/fixtures.mjs
index 06564de6fa3bb9..d6f7f6c092aaa9 100644
--- a/test/common/fixtures.mjs
+++ b/test/common/fixtures.mjs
@@ -3,6 +3,7 @@ import fixtures from './fixtures.js';
const {
fixturesDir,
path,
+ fileURL,
readSync,
readKey,
} = fixtures;
@@ -10,6 +11,7 @@ const {
export {
fixturesDir,
path,
+ fileURL,
readSync,
readKey,
};
diff --git a/test/es-module/test-esm-assertionless-json-import.js b/test/es-module/test-esm-assertionless-json-import.js
new file mode 100644
index 00000000000000..2f06508dd2e509
--- /dev/null
+++ b/test/es-module/test-esm-assertionless-json-import.js
@@ -0,0 +1,81 @@
+// Flags: --experimental-json-modules --experimental-loader ./test/fixtures/es-module-loaders/assertionless-json-import.mjs
+'use strict';
+const common = require('../common');
+const { strictEqual } = require('assert');
+
+async function test() {
+ {
+ const [secret0, secret1] = await Promise.all([
+ import('../fixtures/experimental.json'),
+ import(
+ '../fixtures/experimental.json',
+ { assert: { type: 'json' } }
+ ),
+ ]);
+
+ strictEqual(secret0.default.ofLife, 42);
+ strictEqual(secret1.default.ofLife, 42);
+ strictEqual(secret0.default, secret1.default);
+ strictEqual(secret0, secret1);
+ }
+
+ {
+ const [secret0, secret1] = await Promise.all([
+ import('../fixtures/experimental.json?test'),
+ import(
+ '../fixtures/experimental.json?test',
+ { assert: { type: 'json' } }
+ ),
+ ]);
+
+ strictEqual(secret0.default.ofLife, 42);
+ strictEqual(secret1.default.ofLife, 42);
+ strictEqual(secret0.default, secret1.default);
+ strictEqual(secret0, secret1);
+ }
+
+ {
+ const [secret0, secret1] = await Promise.all([
+ import('../fixtures/experimental.json#test'),
+ import(
+ '../fixtures/experimental.json#test',
+ { assert: { type: 'json' } }
+ ),
+ ]);
+
+ strictEqual(secret0.default.ofLife, 42);
+ strictEqual(secret1.default.ofLife, 42);
+ strictEqual(secret0.default, secret1.default);
+ strictEqual(secret0, secret1);
+ }
+
+ {
+ const [secret0, secret1] = await Promise.all([
+ import('../fixtures/experimental.json?test2#test'),
+ import(
+ '../fixtures/experimental.json?test2#test',
+ { assert: { type: 'json' } }
+ ),
+ ]);
+
+ strictEqual(secret0.default.ofLife, 42);
+ strictEqual(secret1.default.ofLife, 42);
+ strictEqual(secret0.default, secret1.default);
+ strictEqual(secret0, secret1);
+ }
+
+ {
+ const [secret0, secret1] = await Promise.all([
+ import('data:application/json,{"ofLife":42}'),
+ import(
+ 'data:application/json,{"ofLife":42}',
+ { assert: { type: 'json' } }
+ ),
+ ]);
+
+ strictEqual(secret0.default.ofLife, 42);
+ strictEqual(secret1.default.ofLife, 42);
+ }
+}
+
+test().then(common.mustCall());
diff --git a/test/es-module/test-esm-data-urls.js b/test/es-module/test-esm-data-urls.js
index 78cd01b4d55a12..3c0e276b2c0f44 100644
--- a/test/es-module/test-esm-data-urls.js
+++ b/test/es-module/test-esm-data-urls.js
@@ -59,21 +59,22 @@ function createBase64URL(mime, body) {
assert.deepStrictEqual(ns.default, plainESMURL);
}
{
- const ns = await import('data:application/json;foo="test,"this"');
+ const ns = await import('data:application/json;foo="test,"this"',
+ { assert: { type: 'json' } });
assert.deepStrictEqual(Object.keys(ns), ['default']);
assert.deepStrictEqual(ns.default, 'this');
}
{
const ns = await import(`data:application/json;foo=${
encodeURIComponent('test,')
- },0`);
+ },0`, { assert: { type: 'json' } });
assert.deepStrictEqual(Object.keys(ns), ['default']);
assert.deepStrictEqual(ns.default, 0);
}
{
- await assert.rejects(async () => {
- return import('data:application/json;foo="test,",0');
- }, {
+ await assert.rejects(async () =>
+ import('data:application/json;foo="test,",0',
+ { assert: { type: 'json' } }), {
name: 'SyntaxError',
message: /Unexpected end of JSON input/
});
@@ -81,14 +82,14 @@ function createBase64URL(mime, body) {
{
const body = '{"x": 1}';
const plainESMURL = createURL('application/json', body);
- const ns = await import(plainESMURL);
+ const ns = await import(plainESMURL, { assert: { type: 'json' } });
assert.deepStrictEqual(Object.keys(ns), ['default']);
assert.deepStrictEqual(ns.default.x, 1);
}
{
const body = '{"default": 2}';
const plainESMURL = createURL('application/json', body);
- const ns = await import(plainESMURL);
+ const ns = await import(plainESMURL, { assert: { type: 'json' } });
assert.deepStrictEqual(Object.keys(ns), ['default']);
assert.deepStrictEqual(ns.default.default, 2);
}
diff --git a/test/es-module/test-esm-dynamic-import-assertion.js b/test/es-module/test-esm-dynamic-import-assertion.js
new file mode 100644
index 00000000000000..c6ff97d790a44c
--- /dev/null
+++ b/test/es-module/test-esm-dynamic-import-assertion.js
@@ -0,0 +1,48 @@
+// Flags: --experimental-json-modules
+'use strict';
+const common = require('../common');
+const { strictEqual } = require('assert');
+
+async function test() {
+ {
+ const results = await Promise.allSettled([
+ import('../fixtures/empty.js', { assert: { type: 'json' } }),
+ import('../fixtures/empty.js'),
+ ]);
+
+ strictEqual(results[0].status, 'rejected');
+ strictEqual(results[1].status, 'fulfilled');
+ }
+
+ {
+ const results = await Promise.allSettled([
+ import('../fixtures/empty.js'),
+ import('../fixtures/empty.js', { assert: { type: 'json' } }),
+ ]);
+
+ strictEqual(results[0].status, 'fulfilled');
+ strictEqual(results[1].status, 'rejected');
+ }
+
+ {
+ const results = await Promise.allSettled([
+ import('../fixtures/empty.json', { assert: { type: 'json' } }),
+ import('../fixtures/empty.json'),
+ ]);
+
+ strictEqual(results[0].status, 'fulfilled');
+ strictEqual(results[1].status, 'rejected');
+ }
+
+ {
+ const results = await Promise.allSettled([
+ import('../fixtures/empty.json'),
+ import('../fixtures/empty.json', { assert: { type: 'json' } }),
+ ]);
+
+ strictEqual(results[0].status, 'rejected');
+ strictEqual(results[1].status, 'fulfilled');
+ }
+}
+
+test().then(common.mustCall());
diff --git a/test/es-module/test-esm-dynamic-import-assertion.mjs b/test/es-module/test-esm-dynamic-import-assertion.mjs
new file mode 100644
index 00000000000000..a53ea145479eb5
--- /dev/null
+++ b/test/es-module/test-esm-dynamic-import-assertion.mjs
@@ -0,0 +1,43 @@
+// Flags: --experimental-json-modules
+import '../common/index.mjs';
+import { strictEqual } from 'assert';
+
+{
+ const results = await Promise.allSettled([
+ import('../fixtures/empty.js', { assert: { type: 'json' } }),
+ import('../fixtures/empty.js'),
+ ]);
+
+ strictEqual(results[0].status, 'rejected');
+ strictEqual(results[1].status, 'fulfilled');
+}
+
+{
+ const results = await Promise.allSettled([
+ import('../fixtures/empty.js'),
+ import('../fixtures/empty.js', { assert: { type: 'json' } }),
+ ]);
+
+ strictEqual(results[0].status, 'fulfilled');
+ strictEqual(results[1].status, 'rejected');
+}
+
+{
+ const results = await Promise.allSettled([
+ import('../fixtures/empty.json', { assert: { type: 'json' } }),
+ import('../fixtures/empty.json'),
+ ]);
+
+ strictEqual(results[0].status, 'fulfilled');
+ strictEqual(results[1].status, 'rejected');
+}
+
+{
+ const results = await Promise.allSettled([
+ import('../fixtures/empty.json'),
+ import('../fixtures/empty.json', { assert: { type: 'json' } }),
+ ]);
+
+ strictEqual(results[0].status, 'rejected');
+ strictEqual(results[1].status, 'fulfilled');
+}
diff --git a/test/es-module/test-esm-export-not-found.mjs b/test/es-module/test-esm-export-not-found.mjs
new file mode 100644
index 00000000000000..cdfe6df0fcde31
--- /dev/null
+++ b/test/es-module/test-esm-export-not-found.mjs
@@ -0,0 +1,39 @@
+import { mustCall } from '../common/index.mjs';
+import { path } from '../common/fixtures.mjs';
+import { match, notStrictEqual } from 'assert';
+import { spawn } from 'child_process';
+import { execPath } from 'process';
+
+const importStatement =
+ 'import { foo, notfound } from \'./module-named-exports.mjs\';';
+const importStatementMultiline = `import {
+ foo,
+ notfound
+} from './module-named-exports.mjs';
+`;
+
+[importStatement, importStatementMultiline].forEach((input) => {
+ const child = spawn(execPath, [
+ '--input-type=module',
+ '--eval',
+ input,
+ ], {
+ cwd: path('es-module-loaders'),
+ });
+
+ let stderr = '';
+ child.stderr.setEncoding('utf8');
+ child.stderr.on('data', (data) => {
+ stderr += data;
+ });
+ child.on('close', mustCall((code, _signal) => {
+ notStrictEqual(code, 0);
+
+ // SyntaxError: The requested module './module-named-exports.mjs'
+ // does not provide an export named 'notfound'
+ match(stderr, /SyntaxError:/);
+ // The quotes ensure that the path starts with ./ and not ../
+ match(stderr, /'\.\/module-named-exports\.mjs'/);
+ match(stderr, /notfound/);
+ }));
+});
diff --git a/test/es-module/test-esm-import-assertion-1.mjs b/test/es-module/test-esm-import-assertion-1.mjs
index 90ccadf5334f7f..f011c948d8edea 100644
--- a/test/es-module/test-esm-import-assertion-1.mjs
+++ b/test/es-module/test-esm-import-assertion-1.mjs
@@ -1,4 +1,4 @@
-// Flags: --experimental-json-modules --harmony-import-assertions
+// Flags: --experimental-json-modules
import '../common/index.mjs';
import { strictEqual } from 'assert';
diff --git a/test/es-module/test-esm-import-assertion-2.mjs b/test/es-module/test-esm-import-assertion-2.mjs
new file mode 100644
index 00000000000000..3598f353a3f9d5
--- /dev/null
+++ b/test/es-module/test-esm-import-assertion-2.mjs
@@ -0,0 +1,8 @@
+// Flags: --experimental-json-modules
+import '../common/index.mjs';
+import { strictEqual } from 'assert';
+
+// eslint-disable-next-line max-len
+import secret from '../fixtures/experimental.json' assert { type: 'json', unsupportedAssertion: 'should ignore' };
+
+strictEqual(secret.ofLife, 42);
diff --git a/test/es-module/test-esm-import-assertion-3.mjs b/test/es-module/test-esm-import-assertion-3.mjs
new file mode 100644
index 00000000000000..0409095aec5d97
--- /dev/null
+++ b/test/es-module/test-esm-import-assertion-3.mjs
@@ -0,0 +1,11 @@
+// Flags: --experimental-json-modules
+import '../common/index.mjs';
+import { strictEqual } from 'assert';
+
+import secret0 from '../fixtures/experimental.json' assert { type: 'json' };
+const secret1 = await import('../fixtures/experimental.json',
+ { assert: { type: 'json' } });
+
+strictEqual(secret0.ofLife, 42);
+strictEqual(secret1.default.ofLife, 42);
+strictEqual(secret1.default, secret0);
diff --git a/test/es-module/test-esm-import-assertion-4.mjs b/test/es-module/test-esm-import-assertion-4.mjs
new file mode 100644
index 00000000000000..4f3e33a6eefe2d
--- /dev/null
+++ b/test/es-module/test-esm-import-assertion-4.mjs
@@ -0,0 +1,12 @@
+// Flags: --experimental-json-modules
+import '../common/index.mjs';
+import { strictEqual } from 'assert';
+
+import secret0 from '../fixtures/experimental.json' assert { type: 'json' };
+const secret1 = await import('../fixtures/experimental.json', {
+ assert: { type: 'json' },
+ });
+
+strictEqual(secret0.ofLife, 42);
+strictEqual(secret1.default.ofLife, 42);
+strictEqual(secret1.default, secret0);
diff --git a/test/es-module/test-esm-import-assertion-errors.js b/test/es-module/test-esm-import-assertion-errors.js
new file mode 100644
index 00000000000000..c7d5abee693979
--- /dev/null
+++ b/test/es-module/test-esm-import-assertion-errors.js
@@ -0,0 +1,53 @@
+// Flags: --experimental-json-modules
+'use strict';
+const common = require('../common');
+const { rejects } = require('assert');
+
+const jsModuleDataUrl = 'data:text/javascript,export{}';
+const jsonModuleDataUrl = 'data:application/json,""';
+
+async function test() {
+ await rejects(
+ // This rejects because of the unsupported MIME type, not because of the
+ // unsupported assertion.
+ import('data:text/css,', { assert: { type: 'css' } }),
+ { code: 'ERR_INVALID_MODULE_SPECIFIER' }
+ );
+
+ await rejects(
+ import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
+ );
+
+ await rejects(
+ import(jsModuleDataUrl, { assert: { type: 'json' } }),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
+ );
+
+ await rejects(
+ import(jsModuleDataUrl, { assert: { type: 'unsupported' } }),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
+ );
+
+ await rejects(
+ import(jsonModuleDataUrl),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
+ );
+
+ await rejects(
+ import(jsonModuleDataUrl, { assert: {} }),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
+ );
+
+ await rejects(
+ import(jsonModuleDataUrl, { assert: { foo: 'bar' } }),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
+ );
+
+ await rejects(
+ import(jsonModuleDataUrl, { assert: { type: 'unsupported' }}),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
+ );
+}
+
+test().then(common.mustCall());
diff --git a/test/es-module/test-esm-import-assertion-errors.mjs b/test/es-module/test-esm-import-assertion-errors.mjs
new file mode 100644
index 00000000000000..c96e8f3dd046b7
--- /dev/null
+++ b/test/es-module/test-esm-import-assertion-errors.mjs
@@ -0,0 +1,48 @@
+// Flags: --experimental-json-modules
+import '../common/index.mjs';
+import { rejects } from 'assert';
+
+const jsModuleDataUrl = 'data:text/javascript,export{}';
+const jsonModuleDataUrl = 'data:application/json,""';
+
+await rejects(
+ // This rejects because of the unsupported MIME type, not because of the
+ // unsupported assertion.
+ import('data:text/css,', { assert: { type: 'css' } }),
+ { code: 'ERR_INVALID_MODULE_SPECIFIER' }
+);
+
+await rejects(
+ import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
+);
+
+await rejects(
+ import(jsModuleDataUrl, { assert: { type: 'json' } }),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
+);
+
+await rejects(
+ import(import.meta.url, { assert: { type: 'unsupported' } }),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
+);
+
+await rejects(
+ import(jsonModuleDataUrl),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
+);
+
+await rejects(
+ import(jsonModuleDataUrl, { assert: {} }),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
+);
+
+await rejects(
+ import(jsonModuleDataUrl, { assert: { foo: 'bar' } }),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
+);
+
+await rejects(
+ import(jsonModuleDataUrl, { assert: { type: 'unsupported' }}),
+ { code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
+);
diff --git a/test/es-module/test-esm-import-assertion-validation.js b/test/es-module/test-esm-import-assertion-validation.js
new file mode 100644
index 00000000000000..3792ad7ff1617c
--- /dev/null
+++ b/test/es-module/test-esm-import-assertion-validation.js
@@ -0,0 +1,37 @@
+// Flags: --expose-internals
+'use strict';
+require('../common');
+
+const assert = require('assert');
+
+const { validateAssertions } = require('internal/modules/esm/assert');
+
+const url = 'test://';
+
+assert.ok(validateAssertions(url, 'builtin', {}));
+assert.ok(validateAssertions(url, 'commonjs', {}));
+assert.ok(validateAssertions(url, 'json', { type: 'json' }));
+assert.ok(validateAssertions(url, 'module', {}));
+assert.ok(validateAssertions(url, 'wasm', {}));
+
+assert.throws(() => validateAssertions(url, 'json', {}), {
+ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING',
+});
+
+assert.throws(() => validateAssertions(url, 'module', { type: 'json' }), {
+ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED',
+});
+
+// The HTML spec specifically disallows this for now, while Wasm module import
+// and whether it will require a type assertion is still an open question.
+assert.throws(() => validateAssertions(url, 'module', { type: 'javascript' }), {
+ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED',
+});
+
+assert.throws(() => validateAssertions(url, 'module', { type: 'css' }), {
+ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED',
+});
+
+assert.throws(() => validateAssertions(url, 'module', { type: false }), {
+ code: 'ERR_INVALID_ARG_TYPE',
+});
diff --git a/test/es-module/test-esm-import-json-named-export.mjs b/test/es-module/test-esm-import-json-named-export.mjs
new file mode 100644
index 00000000000000..f70b927329b6a6
--- /dev/null
+++ b/test/es-module/test-esm-import-json-named-export.mjs
@@ -0,0 +1,25 @@
+import { mustCall } from '../common/index.mjs';
+import { path } from '../common/fixtures.mjs';
+import { match, notStrictEqual } from 'assert';
+import { spawn } from 'child_process';
+import { execPath } from 'process';
+
+const child = spawn(execPath, [
+ '--experimental-json-modules',
+ path('es-modules', 'import-json-named-export.mjs'),
+]);
+
+let stderr = '';
+child.stderr.setEncoding('utf8');
+child.stderr.on('data', (data) => {
+ stderr += data;
+});
+child.on('close', mustCall((code, _signal) => {
+ notStrictEqual(code, 0);
+
+ // SyntaxError: The requested module '../experimental.json'
+ // does not provide an export named 'ofLife'
+ match(stderr, /SyntaxError:/);
+ match(stderr, /'\.\.\/experimental\.json'/);
+ match(stderr, /'ofLife'/);
+}));
diff --git a/test/es-module/test-esm-json-cache.mjs b/test/es-module/test-esm-json-cache.mjs
index 68ea832ab69585..90694748c39e5f 100644
--- a/test/es-module/test-esm-json-cache.mjs
+++ b/test/es-module/test-esm-json-cache.mjs
@@ -7,7 +7,8 @@ import { createRequire } from 'module';
import mod from '../fixtures/es-modules/json-cache/mod.cjs';
import another from '../fixtures/es-modules/json-cache/another.cjs';
-import test from '../fixtures/es-modules/json-cache/test.json';
+import test from '../fixtures/es-modules/json-cache/test.json' assert
+ { type: 'json' };
const require = createRequire(import.meta.url);
diff --git a/test/es-module/test-esm-json.mjs b/test/es-module/test-esm-json.mjs
index df4f75fbd6e067..f33b4f9937ddb1 100644
--- a/test/es-module/test-esm-json.mjs
+++ b/test/es-module/test-esm-json.mjs
@@ -4,7 +4,7 @@ import { path } from '../common/fixtures.mjs';
import { strictEqual, ok } from 'assert';
import { spawn } from 'child_process';
-import secret from '../fixtures/experimental.json';
+import secret from '../fixtures/experimental.json' assert { type: 'json' };
strictEqual(secret.ofLife, 42);
diff --git a/test/es-module/test-esm-loader-modulemap.js b/test/es-module/test-esm-loader-modulemap.js
index 48443de4c270c6..190676ec725cd2 100644
--- a/test/es-module/test-esm-loader-modulemap.js
+++ b/test/es-module/test-esm-loader-modulemap.js
@@ -1,61 +1,100 @@
'use strict';
// Flags: --expose-internals
-// This test ensures that the type checking of ModuleMap throws
-// errors appropriately
-
require('../common');
-const assert = require('assert');
+const { strictEqual, throws } = require('assert');
const { ESMLoader } = require('internal/modules/esm/loader');
const ModuleMap = require('internal/modules/esm/module_map');
const ModuleJob = require('internal/modules/esm/module_job');
const createDynamicModule = require(
'internal/modules/esm/create_dynamic_module');
-const stubModuleUrl = new URL('file://tmp/test');
-const stubModule = createDynamicModule(['default'], stubModuleUrl);
+const jsModuleDataUrl = 'data:text/javascript,export{}';
+const jsonModuleDataUrl = 'data:application/json,""';
+
+const stubJsModule = createDynamicModule([], ['default'], jsModuleDataUrl);
+const stubJsonModule = createDynamicModule([], ['default'], jsonModuleDataUrl);
+
const loader = new ESMLoader();
-const moduleMap = new ModuleMap();
-const moduleJob = new ModuleJob(loader, stubModule.module,
- () => new Promise(() => {}));
+const jsModuleJob = new ModuleJob(loader, stubJsModule.module, undefined,
+ () => new Promise(() => {}));
+const jsonModuleJob = new ModuleJob(loader, stubJsonModule.module,
+ { type: 'json' },
+ () => new Promise(() => {}));
-assert.throws(
- () => moduleMap.get(1),
- {
- code: 'ERR_INVALID_ARG_TYPE',
- name: 'TypeError',
- message: 'The "url" argument must be of type string. Received type number' +
- ' (1)'
- }
-);
-
-assert.throws(
- () => moduleMap.set(1, moduleJob),
- {
- code: 'ERR_INVALID_ARG_TYPE',
- name: 'TypeError',
- message: 'The "url" argument must be of type string. Received type number' +
- ' (1)'
- }
-);
-
-assert.throws(
- () => moduleMap.set('somestring', 'notamodulejob'),
- {
+
+// ModuleMap.set and ModuleMap.get store and retrieve module jobs for a
+// specified url/type tuple; ModuleMap.has correctly reports whether such jobs
+// are stored in the map.
+{
+ const moduleMap = new ModuleMap();
+
+ moduleMap.set(jsModuleDataUrl, undefined, jsModuleJob);
+ moduleMap.set(jsonModuleDataUrl, 'json', jsonModuleJob);
+
+ strictEqual(moduleMap.get(jsModuleDataUrl), jsModuleJob);
+ strictEqual(moduleMap.get(jsonModuleDataUrl, 'json'), jsonModuleJob);
+
+ strictEqual(moduleMap.has(jsModuleDataUrl), true);
+ strictEqual(moduleMap.has(jsModuleDataUrl, 'javascript'), true);
+ strictEqual(moduleMap.has(jsonModuleDataUrl, 'json'), true);
+
+ strictEqual(moduleMap.has('unknown'), false);
+
+ // The types must match
+ strictEqual(moduleMap.has(jsModuleDataUrl, 'json'), false);
+ strictEqual(moduleMap.has(jsonModuleDataUrl, 'javascript'), false);
+ strictEqual(moduleMap.has(jsonModuleDataUrl), false);
+ strictEqual(moduleMap.has(jsModuleDataUrl, 'unknown'), false);
+ strictEqual(moduleMap.has(jsonModuleDataUrl, 'unknown'), false);
+}
+
+// ModuleMap.get, ModuleMap.has and ModuleMap.set should only accept string
+// values as url argument.
+{
+ const moduleMap = new ModuleMap();
+
+ const errorObj = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
- message: 'The "job" argument must be an instance of ModuleJob. ' +
- "Received type string ('notamodulejob')"
- }
-);
-
-assert.throws(
- () => moduleMap.has(1),
- {
+ message: /^The "url" argument must be of type string/
+ };
+
+ [{}, [], true, 1].forEach((value) => {
+ throws(() => moduleMap.get(value), errorObj);
+ throws(() => moduleMap.has(value), errorObj);
+ throws(() => moduleMap.set(value, undefined, jsModuleJob), errorObj);
+ });
+}
+
+// ModuleMap.get, ModuleMap.has and ModuleMap.set should only accept string
+// values (or the kAssertType symbol) as type argument.
+{
+ const moduleMap = new ModuleMap();
+
+ const errorObj = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
- message: 'The "url" argument must be of type string. Received type number' +
- ' (1)'
- }
-);
+ message: /^The "type" argument must be of type string/
+ };
+
+ [{}, [], true, 1].forEach((value) => {
+ throws(() => moduleMap.get(jsModuleDataUrl, value), errorObj);
+ throws(() => moduleMap.has(jsModuleDataUrl, value), errorObj);
+ throws(() => moduleMap.set(jsModuleDataUrl, value, jsModuleJob), errorObj);
+ });
+}
+
+// ModuleMap.set should only accept ModuleJob values as job argument.
+{
+ const moduleMap = new ModuleMap();
+
+ [{}, [], true, 1].forEach((value) => {
+ throws(() => moduleMap.set('', undefined, value), {
+ code: 'ERR_INVALID_ARG_TYPE',
+ name: 'TypeError',
+ message: /^The "job" argument must be an instance of ModuleJob/
+ });
+ });
+}
diff --git a/test/es-module/test-esm-loader-not-found.mjs b/test/es-module/test-esm-loader-not-found.mjs
new file mode 100644
index 00000000000000..275f0b0f1e8515
--- /dev/null
+++ b/test/es-module/test-esm-loader-not-found.mjs
@@ -0,0 +1,27 @@
+import { mustCall } from '../common/index.mjs';
+import { path } from '../common/fixtures.mjs';
+import { match, ok, notStrictEqual } from 'assert';
+import { spawn } from 'child_process';
+import { execPath } from 'process';
+
+const child = spawn(execPath, [
+ '--experimental-loader',
+ 'i-dont-exist',
+ path('print-error-message.js'),
+]);
+
+let stderr = '';
+child.stderr.setEncoding('utf8');
+child.stderr.on('data', (data) => {
+ stderr += data;
+});
+child.on('close', mustCall((code, _signal) => {
+ notStrictEqual(code, 0);
+
+ // Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'i-dont-exist'
+ // imported from
+ match(stderr, /ERR_MODULE_NOT_FOUND/);
+ match(stderr, /'i-dont-exist'/);
+
+ ok(!stderr.includes('Bad command or file name'));
+}));
diff --git a/test/es-module/test-esm-loader-obsolete-hooks.mjs b/test/es-module/test-esm-loader-obsolete-hooks.mjs
new file mode 100644
index 00000000000000..eff4104fc265ae
--- /dev/null
+++ b/test/es-module/test-esm-loader-obsolete-hooks.mjs
@@ -0,0 +1,30 @@
+import { mustCall } from '../common/index.mjs';
+import { fileURL, path } from '../common/fixtures.mjs';
+import { match, notStrictEqual } from 'assert';
+import { spawn } from 'child_process';
+import { execPath } from 'process';
+
+const child = spawn(execPath, [
+ '--no-warnings',
+ '--throw-deprecation',
+ '--experimental-loader',
+ fileURL('es-module-loaders', 'hooks-obsolete.mjs').href,
+ path('print-error-message.js'),
+]);
+
+let stderr = '';
+child.stderr.setEncoding('utf8');
+child.stderr.on('data', (data) => {
+ stderr += data;
+});
+child.on('close', mustCall((code, _signal) => {
+ notStrictEqual(code, 0);
+
+ // DeprecationWarning: Obsolete loader hook(s) supplied and will be ignored:
+ // dynamicInstantiate, getFormat, getSource, transformSource
+ match(stderr, /DeprecationWarning:/);
+ match(stderr, /dynamicInstantiate/);
+ match(stderr, /getFormat/);
+ match(stderr, /getSource/);
+ match(stderr, /transformSource/);
+}));
diff --git a/test/es-module/test-esm-loader-with-syntax-error.mjs b/test/es-module/test-esm-loader-with-syntax-error.mjs
new file mode 100644
index 00000000000000..d973e72975e88f
--- /dev/null
+++ b/test/es-module/test-esm-loader-with-syntax-error.mjs
@@ -0,0 +1,24 @@
+import { mustCall } from '../common/index.mjs';
+import { fileURL, path } from '../common/fixtures.mjs';
+import { match, ok, notStrictEqual } from 'assert';
+import { spawn } from 'child_process';
+import { execPath } from 'process';
+
+const child = spawn(execPath, [
+ '--experimental-loader',
+ fileURL('es-module-loaders', 'syntax-error.mjs').href,
+ path('print-error-message.js'),
+]);
+
+let stderr = '';
+child.stderr.setEncoding('utf8');
+child.stderr.on('data', (data) => {
+ stderr += data;
+});
+child.on('close', mustCall((code, _signal) => {
+ notStrictEqual(code, 0);
+
+ match(stderr, /SyntaxError:/);
+
+ ok(!stderr.includes('Bad command or file name'));
+}));
diff --git a/test/es-module/test-esm-module-not-found-commonjs-hint.mjs b/test/es-module/test-esm-module-not-found-commonjs-hint.mjs
new file mode 100644
index 00000000000000..58f70d0b685391
--- /dev/null
+++ b/test/es-module/test-esm-module-not-found-commonjs-hint.mjs
@@ -0,0 +1,35 @@
+import { mustCall } from '../common/index.mjs';
+import { fixturesDir } from '../common/fixtures.mjs';
+import { match, notStrictEqual } from 'assert';
+import { spawn } from 'child_process';
+import { execPath } from 'process';
+
+[
+ {
+ input: 'import "./print-error-message"',
+ // Did you mean to import ../print-error-message.js?
+ expected: / \.\.\/print-error-message\.js\?/,
+ },
+ {
+ input: 'import obj from "some_module/obj"',
+ expected: / some_module\/obj\.js\?/,
+ },
+].forEach(({ input, expected }) => {
+ const child = spawn(execPath, [
+ '--input-type=module',
+ '--eval',
+ input,
+ ], {
+ cwd: fixturesDir,
+ });
+
+ let stderr = '';
+ child.stderr.setEncoding('utf8');
+ child.stderr.on('data', (data) => {
+ stderr += data;
+ });
+ child.on('close', mustCall((code, _signal) => {
+ notStrictEqual(code, 0);
+ match(stderr, expected);
+ }));
+});
diff --git a/test/es-module/test-esm-syntax-error.mjs b/test/es-module/test-esm-syntax-error.mjs
new file mode 100644
index 00000000000000..a8c019171717dd
--- /dev/null
+++ b/test/es-module/test-esm-syntax-error.mjs
@@ -0,0 +1,19 @@
+import { mustCall } from '../common/index.mjs';
+import { path } from '../common/fixtures.mjs';
+import { match, notStrictEqual } from 'assert';
+import { spawn } from 'child_process';
+import { execPath } from 'process';
+
+const child = spawn(execPath, [
+ path('es-module-loaders', 'syntax-error.mjs'),
+]);
+
+let stderr = '';
+child.stderr.setEncoding('utf8');
+child.stderr.on('data', (data) => {
+ stderr += data;
+});
+child.on('close', mustCall((code, _signal) => {
+ notStrictEqual(code, 0);
+ match(stderr, /SyntaxError:/);
+}));
diff --git a/test/es-module/test-esm-wasm.mjs b/test/es-module/test-esm-wasm.mjs
index 877a841850dc4f..01717c47714f6a 100644
--- a/test/es-module/test-esm-wasm.mjs
+++ b/test/es-module/test-esm-wasm.mjs
@@ -31,7 +31,7 @@ child.on('close', (code, signal) => {
strictEqual(code, 0);
strictEqual(signal, null);
ok(stderr.toString().includes(
- 'ExperimentalWarning: Importing Web Assembly modules is ' +
+ 'ExperimentalWarning: Importing WebAssembly modules is ' +
'an experimental feature. This feature could change at any time'
));
});
diff --git a/test/fixtures/empty.json b/test/fixtures/empty.json
new file mode 100644
index 00000000000000..0967ef424bce67
--- /dev/null
+++ b/test/fixtures/empty.json
@@ -0,0 +1 @@
+{}
diff --git a/test/fixtures/es-module-loaders/assertionless-json-import.mjs b/test/fixtures/es-module-loaders/assertionless-json-import.mjs
new file mode 100644
index 00000000000000..c5c2fadf28fb58
--- /dev/null
+++ b/test/fixtures/es-module-loaders/assertionless-json-import.mjs
@@ -0,0 +1,17 @@
+const DATA_URL_PATTERN = /^data:application\/json(?:[^,]*?)(;base64)?,([\s\S]*)$/;
+const JSON_URL_PATTERN = /\.json(\?[^#]*)?(#.*)?$/;
+
+export function resolve(url, context, next) {
+ // Mutation from resolve hook should be discarded.
+ context.importAssertions.type = 'whatever';
+ return next(url, context);
+}
+
+export function load(url, context, next) {
+ if (context.importAssertions.type == null &&
+ (DATA_URL_PATTERN.test(url) || JSON_URL_PATTERN.test(url))) {
+ const { importAssertions } = context;
+ importAssertions.type = 'json';
+ }
+ return next(url, context);
+}
diff --git a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs
index f206d7635b3f63..82e64567494842 100644
--- a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs
+++ b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs
@@ -19,6 +19,7 @@ export function resolve(specifier, context, next) {
if (def.url.startsWith('node:')) {
return {
url: `custom-${def.url}`,
+ importAssertions: context.importAssertions,
};
}
return def;
diff --git a/test/fixtures/es-module-loaders/hooks-custom.mjs b/test/fixtures/es-module-loaders/hooks-custom.mjs
index 59f49ff9e60c13..cd9d5020ad3234 100644
--- a/test/fixtures/es-module-loaders/hooks-custom.mjs
+++ b/test/fixtures/es-module-loaders/hooks-custom.mjs
@@ -63,6 +63,7 @@ export function resolve(specifier, context, next) {
if (specifier.startsWith('esmHook')) return {
format,
url: specifier,
+ importAssertions: context.importAssertions,
};
return next(specifier, context, next);
diff --git a/test/fixtures/es-module-loaders/loader-invalid-format.mjs b/test/fixtures/es-module-loaders/loader-invalid-format.mjs
index fc1b84658b76de..0210f73b554382 100644
--- a/test/fixtures/es-module-loaders/loader-invalid-format.mjs
+++ b/test/fixtures/es-module-loaders/loader-invalid-format.mjs
@@ -1,10 +1,10 @@
-export async function resolve(specifier, { parentURL }, defaultResolve) {
+export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') {
return {
url: 'file:///asdf'
};
}
- return defaultResolve(specifier, {parentURL}, defaultResolve);
+ return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
}
export async function load(url, context, next) {
diff --git a/test/fixtures/es-module-loaders/loader-invalid-url.mjs b/test/fixtures/es-module-loaders/loader-invalid-url.mjs
index e7de0d4ed92378..ad69faff26d40f 100644
--- a/test/fixtures/es-module-loaders/loader-invalid-url.mjs
+++ b/test/fixtures/es-module-loaders/loader-invalid-url.mjs
@@ -1,9 +1,10 @@
/* eslint-disable node-core/required-modules */
-export async function resolve(specifier, { parentURL }, defaultResolve) {
+export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') {
return {
- url: specifier
+ url: specifier,
+ importAssertions,
};
}
- return defaultResolve(specifier, {parentURL}, defaultResolve);
+ return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
}
diff --git a/test/fixtures/es-module-loaders/loader-shared-dep.mjs b/test/fixtures/es-module-loaders/loader-shared-dep.mjs
index 3576c074d52cec..387575794c00dc 100644
--- a/test/fixtures/es-module-loaders/loader-shared-dep.mjs
+++ b/test/fixtures/es-module-loaders/loader-shared-dep.mjs
@@ -1,11 +1,11 @@
import assert from 'assert';
-import {createRequire} from '../../common/index.mjs';
+import { createRequire } from '../../common/index.mjs';
const require = createRequire(import.meta.url);
const dep = require('./loader-dep.js');
-export function resolve(specifier, { parentURL }, defaultResolve) {
+export function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
assert.strictEqual(dep.format, 'module');
- return defaultResolve(specifier, {parentURL}, defaultResolve);
+ return defaultResolve(specifier, { parentURL, importAssertions }, defaultResolve);
}
diff --git a/test/fixtures/es-module-loaders/loader-with-dep.mjs b/test/fixtures/es-module-loaders/loader-with-dep.mjs
index da7d44ae793e22..78a72cca6d9009 100644
--- a/test/fixtures/es-module-loaders/loader-with-dep.mjs
+++ b/test/fixtures/es-module-loaders/loader-with-dep.mjs
@@ -3,9 +3,9 @@ import {createRequire} from '../../common/index.mjs';
const require = createRequire(import.meta.url);
const dep = require('./loader-dep.js');
-export function resolve (specifier, { parentURL }, defaultResolve) {
+export function resolve (specifier, { parentURL, importAssertions }, defaultResolve) {
return {
- url: defaultResolve(specifier, {parentURL}, defaultResolve).url,
+ url: defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve).url,
format: dep.format
};
}
diff --git a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs
index 9a2cd735a2fd66..5213ddedb34e8d 100644
--- a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs
+++ b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs
@@ -3,18 +3,19 @@ import assert from 'assert';
// a loader that asserts that the defaultResolve will throw "not found"
// (skipping the top-level main of course)
let mainLoad = true;
-export async function resolve(specifier, { parentURL }, defaultResolve) {
+export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
if (mainLoad) {
mainLoad = false;
- return defaultResolve(specifier, {parentURL}, defaultResolve);
+ return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
}
try {
- await defaultResolve(specifier, {parentURL}, defaultResolve);
+ await defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
}
catch (e) {
assert.strictEqual(e.code, 'ERR_MODULE_NOT_FOUND');
return {
- url: 'node:fs'
+ url: 'node:fs',
+ importAssertions,
};
}
assert.fail(`Module resolution for ${specifier} should be throw ERR_MODULE_NOT_FOUND`);
diff --git a/test/fixtures/es-module-loaders/string-sources.mjs b/test/fixtures/es-module-loaders/string-sources.mjs
index 180a356bc81478..384098d6d9e822 100644
--- a/test/fixtures/es-module-loaders/string-sources.mjs
+++ b/test/fixtures/es-module-loaders/string-sources.mjs
@@ -22,7 +22,7 @@ const SOURCES = {
}
export function resolve(specifier, context, next) {
if (specifier.startsWith('test:')) {
- return { url: specifier };
+ return { url: specifier, importAssertions: context.importAssertions };
}
return next(specifier, context);
}
diff --git a/test/fixtures/es-module-loaders/syntax-error-import.mjs b/test/fixtures/es-module-loaders/syntax-error-import.mjs
deleted file mode 100644
index 3a6bc5effc1940..00000000000000
--- a/test/fixtures/es-module-loaders/syntax-error-import.mjs
+++ /dev/null
@@ -1 +0,0 @@
-import { foo, notfound } from './module-named-exports.mjs';
diff --git a/test/fixtures/es-modules/import-json-named-export.mjs b/test/fixtures/es-modules/import-json-named-export.mjs
new file mode 100644
index 00000000000000..f491e8c252d41a
--- /dev/null
+++ b/test/fixtures/es-modules/import-json-named-export.mjs
@@ -0,0 +1,2 @@
+/* eslint-disable no-unused-vars */
+import { ofLife } from '../experimental.json' assert { type: 'json' };
diff --git a/test/fixtures/es-modules/json-modules.mjs b/test/fixtures/es-modules/json-modules.mjs
index fa3f936bac921e..607c09e51cda2b 100644
--- a/test/fixtures/es-modules/json-modules.mjs
+++ b/test/fixtures/es-modules/json-modules.mjs
@@ -1 +1 @@
-import secret from '../experimental.json';
+import secret from '../experimental.json' assert { type: 'json' };
diff --git a/test/fixtures/esm_loader_not_found_cjs_hint_bare.mjs b/test/fixtures/esm_loader_not_found_cjs_hint_bare.mjs
deleted file mode 100644
index 4eb5f190af43e4..00000000000000
--- a/test/fixtures/esm_loader_not_found_cjs_hint_bare.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-'use strict';
-
-import obj from 'some_module/obj';
-
-throw new Error('Should have errored');
diff --git a/test/fixtures/print-error-message.js b/test/fixtures/print-error-message.js
new file mode 100644
index 00000000000000..0650bfbd52bafa
--- /dev/null
+++ b/test/fixtures/print-error-message.js
@@ -0,0 +1 @@
+console.error('Bad command or file name');
diff --git a/test/message/esm_display_syntax_error_import.mjs b/test/message/esm_display_syntax_error_import.mjs
deleted file mode 100644
index 2173cb2b2e3a71..00000000000000
--- a/test/message/esm_display_syntax_error_import.mjs
+++ /dev/null
@@ -1,6 +0,0 @@
-/* eslint-disable no-unused-vars */
-import '../common/index.mjs';
-import {
- foo,
- notfound
-} from '../fixtures/es-module-loaders/module-named-exports.mjs';
diff --git a/test/message/esm_display_syntax_error_import.out b/test/message/esm_display_syntax_error_import.out
deleted file mode 100644
index 62607b93c7c593..00000000000000
--- a/test/message/esm_display_syntax_error_import.out
+++ /dev/null
@@ -1,10 +0,0 @@
-file:///*/test/message/esm_display_syntax_error_import.mjs:5
- notfound
- ^^^^^^^^
-SyntaxError: The requested module '../fixtures/es-module-loaders/module-named-exports.mjs' does not provide an export named 'notfound'
- at ModuleJob._instantiate (node:internal/modules/esm/module_job:*:*)
- at async ModuleJob.run (node:internal/modules/esm/module_job:*:*)
- at async Promise.all (index 0)
- at async ESMLoader.import (node:internal/modules/esm/loader:*:*)
- at async loadESM (node:internal/process/esm_loader:*:*)
- at async handleMainPromise (node:internal/modules/run_main:*:*)
diff --git a/test/message/esm_display_syntax_error_import_module.mjs b/test/message/esm_display_syntax_error_import_module.mjs
deleted file mode 100644
index c0345c44fb3fda..00000000000000
--- a/test/message/esm_display_syntax_error_import_module.mjs
+++ /dev/null
@@ -1,2 +0,0 @@
-import '../common/index.mjs';
-import '../fixtures/es-module-loaders/syntax-error-import.mjs';
diff --git a/test/message/esm_display_syntax_error_import_module.out b/test/message/esm_display_syntax_error_import_module.out
deleted file mode 100644
index 0ecb87a952e891..00000000000000
--- a/test/message/esm_display_syntax_error_import_module.out
+++ /dev/null
@@ -1,10 +0,0 @@
-file:///*/test/fixtures/es-module-loaders/syntax-error-import.mjs:1
-import { foo, notfound } from './module-named-exports.mjs';
- ^^^^^^^^
-SyntaxError: The requested module './module-named-exports.mjs' does not provide an export named 'notfound'
- at ModuleJob._instantiate (node:internal/modules/esm/module_job:*:*)
- at async ModuleJob.run (node:internal/modules/esm/module_job:*:*)
- at async Promise.all (index 0)
- at async ESMLoader.import (node:internal/modules/esm/loader:*:*)
- at async loadESM (node:internal/process/esm_loader:*:*)
- at async handleMainPromise (node:internal/modules/run_main:*:*)
diff --git a/test/message/esm_display_syntax_error_module.mjs b/test/message/esm_display_syntax_error_module.mjs
deleted file mode 100644
index da40a4ead8d3c1..00000000000000
--- a/test/message/esm_display_syntax_error_module.mjs
+++ /dev/null
@@ -1,2 +0,0 @@
-import '../common/index.mjs';
-import '../fixtures/es-module-loaders/syntax-error.mjs';
diff --git a/test/message/esm_display_syntax_error_module.out b/test/message/esm_display_syntax_error_module.out
deleted file mode 100644
index 26dbf480239b9c..00000000000000
--- a/test/message/esm_display_syntax_error_module.out
+++ /dev/null
@@ -1,7 +0,0 @@
-file:///*/test/fixtures/es-module-loaders/syntax-error.mjs:2
-await async () => 0;
-^^^^^^^^^^^^^
-
-SyntaxError: Malformed arrow function parameter list
- at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:*:*)
- at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*)
diff --git a/test/message/esm_loader_not_found.mjs b/test/message/esm_loader_not_found.mjs
deleted file mode 100644
index 2b47e5a03ec9e6..00000000000000
--- a/test/message/esm_loader_not_found.mjs
+++ /dev/null
@@ -1,3 +0,0 @@
-// Flags: --experimental-loader i-dont-exist
-import '../common/index.mjs';
-console.log('This should not be printed');
diff --git a/test/message/esm_loader_not_found.out b/test/message/esm_loader_not_found.out
deleted file mode 100644
index 21d7ab3e44f205..00000000000000
--- a/test/message/esm_loader_not_found.out
+++ /dev/null
@@ -1,18 +0,0 @@
-(node:*) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
-(Use `* --trace-warnings ...` to show where the warning was created)
-node:internal/errors:*
- ErrorCaptureStackTrace(err);
- ^
-Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'i-dont-exist' imported from *
- at new NodeError (node:internal/errors:*:*)
- at packageResolve (node:internal/modules/esm/resolve:*:*)
- at moduleResolve (node:internal/modules/esm/resolve:*:*)
- at defaultResolve (node:internal/modules/esm/resolve:*:*)
- at ESMLoader.resolve (node:internal/modules/esm/loader:*:*)
- at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*)
- at ESMLoader.import (node:internal/modules/esm/loader:*:*)
- at initializeLoader (node:internal/process/esm_loader:*:*)
- at loadESM (node:internal/process/esm_loader:*:*)
- at runMainESM (node:internal/modules/run_main:*:*) {
- code: 'ERR_MODULE_NOT_FOUND'
-}
diff --git a/test/message/esm_loader_not_found_cjs_hint_bare.js b/test/message/esm_loader_not_found_cjs_hint_bare.js
deleted file mode 100644
index 437fa2d3d430a1..00000000000000
--- a/test/message/esm_loader_not_found_cjs_hint_bare.js
+++ /dev/null
@@ -1,17 +0,0 @@
-'use strict';
-
-require('../common');
-const { spawn } = require('child_process');
-const { join } = require('path');
-const { fixturesDir } = require('../common/fixtures');
-
-spawn(
- process.execPath,
- [
- join(fixturesDir, 'esm_loader_not_found_cjs_hint_bare.mjs'),
- ],
- {
- cwd: fixturesDir,
- stdio: 'inherit'
- }
-);
diff --git a/test/message/esm_loader_not_found_cjs_hint_bare.out b/test/message/esm_loader_not_found_cjs_hint_bare.out
deleted file mode 100644
index 4a255ebf0b6972..00000000000000
--- a/test/message/esm_loader_not_found_cjs_hint_bare.out
+++ /dev/null
@@ -1,16 +0,0 @@
-node:internal/process/esm_loader:*
- internalBinding('errors').triggerUncaughtException(
- ^
-
-Error [ERR_MODULE_NOT_FOUND]: Cannot find module '*test*fixtures*node_modules*some_module*obj' imported from *test*fixtures*esm_loader_not_found_cjs_hint_bare.mjs
-Did you mean to import some_module/obj.js?
- at new NodeError (node:internal/errors:*:*)
- at finalizeResolution (node:internal/modules/esm/resolve:*:*)
- at moduleResolve (node:internal/modules/esm/resolve:*:*)
- at defaultResolve (node:internal/modules/esm/resolve:*:*)
- at ESMLoader.resolve (node:internal/modules/esm/loader:*:*)
- at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*)
- at ModuleWrap. (node:internal/modules/esm/module_job:*:*)
- at link (node:internal/modules/esm/module_job:*:*) {
- code: 'ERR_MODULE_NOT_FOUND'
-}
diff --git a/test/message/esm_loader_not_found_cjs_hint_relative.mjs b/test/message/esm_loader_not_found_cjs_hint_relative.mjs
deleted file mode 100644
index 928186318bb09a..00000000000000
--- a/test/message/esm_loader_not_found_cjs_hint_relative.mjs
+++ /dev/null
@@ -1,3 +0,0 @@
-// Flags: --experimental-loader ./test/common/fixtures
-import '../common/index.mjs';
-console.log('This should not be printed');
diff --git a/test/message/esm_loader_not_found_cjs_hint_relative.out b/test/message/esm_loader_not_found_cjs_hint_relative.out
deleted file mode 100644
index 817b4aa0724e8e..00000000000000
--- a/test/message/esm_loader_not_found_cjs_hint_relative.out
+++ /dev/null
@@ -1,20 +0,0 @@
-(node:*) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
-(Use `* --trace-warnings ...` to show where the warning was created)
-node:internal/process/esm_loader:*
- internalBinding('errors').triggerUncaughtException(
- ^
-
-Error [ERR_MODULE_NOT_FOUND]: Cannot find module '*test*common*fixtures' imported from *
-Did you mean to import ./test/common/fixtures.js?
- at new NodeError (node:internal/errors:*:*)
- at finalizeResolution (node:internal/modules/esm/resolve:*:*)
- at moduleResolve (node:internal/modules/esm/resolve:*:*)
- at defaultResolve (node:internal/modules/esm/resolve:*:*)
- at ESMLoader.resolve (node:internal/modules/esm/loader:*:*)
- at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*)
- at ESMLoader.import (node:internal/modules/esm/loader:*:*)
- at initializeLoader (node:internal/process/esm_loader:*:*)
- at loadESM (node:internal/process/esm_loader:*:*)
- at runMainESM (node:internal/modules/run_main:*:*) {
- code: 'ERR_MODULE_NOT_FOUND'
-}
diff --git a/test/message/esm_loader_syntax_error.mjs b/test/message/esm_loader_syntax_error.mjs
deleted file mode 100644
index 68cde42e585644..00000000000000
--- a/test/message/esm_loader_syntax_error.mjs
+++ /dev/null
@@ -1,3 +0,0 @@
-// Flags: --experimental-loader ./test/fixtures/es-module-loaders/syntax-error.mjs
-import '../common/index.mjs';
-console.log('This should not be printed');
diff --git a/test/message/esm_loader_syntax_error.out b/test/message/esm_loader_syntax_error.out
deleted file mode 100644
index fb8a2030828a9d..00000000000000
--- a/test/message/esm_loader_syntax_error.out
+++ /dev/null
@@ -1,10 +0,0 @@
-(node:*) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
-(Use `* --trace-warnings ...` to show where the warning was created)
-file://*/test/fixtures/es-module-loaders/syntax-error.mjs:2
-await async () => 0;
-^^^^^^^^^^^^^
-
-SyntaxError: Malformed arrow function parameter list
- at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:*:*)
- at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*)
- at async link (node:internal/modules/esm/module_job:*:*)
diff --git a/test/message/test-esm-loader-obsolete-hooks.mjs b/test/message/test-esm-loader-obsolete-hooks.mjs
deleted file mode 100644
index 9a6a9c48057b40..00000000000000
--- a/test/message/test-esm-loader-obsolete-hooks.mjs
+++ /dev/null
@@ -1,4 +0,0 @@
-// Flags: --no-warnings --throw-deprecation --experimental-loader ./test/fixtures/es-module-loaders/hooks-obsolete.mjs
-/* eslint-disable node-core/require-common-first, node-core/required-modules */
-
-await import('whatever');
diff --git a/test/message/test-esm-loader-obsolete-hooks.out b/test/message/test-esm-loader-obsolete-hooks.out
deleted file mode 100644
index 5099c9c8d8610b..00000000000000
--- a/test/message/test-esm-loader-obsolete-hooks.out
+++ /dev/null
@@ -1,10 +0,0 @@
-node:internal/process/warning:*
- throw warning;
- ^
-
-DeprecationWarning: Obsolete loader hook(s) supplied and will be ignored: dynamicInstantiate, getFormat, getSource, transformSource
- at Function.pluckHooks (node:internal/modules/esm/loader:*:*)
- at ESMLoader.addCustomLoaders (node:internal/modules/esm/loader:*:*)
- at initializeLoader (node:internal/process/esm_loader:*:*)
- at async loadESM (node:internal/process/esm_loader:*:*)
- at async handleMainPromise (node:internal/modules/run_main:*:*)
diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js
index 64cc8a5f165a7c..4177b3756e07a4 100644
--- a/test/parallel/test-bootstrap-modules.js
+++ b/test/parallel/test-bootstrap-modules.js
@@ -68,6 +68,7 @@ const expectedModules = new Set([
'NativeModule internal/modules/package_json_reader',
'NativeModule internal/modules/cjs/helpers',
'NativeModule internal/modules/cjs/loader',
+ 'NativeModule internal/modules/esm/assert',
'NativeModule internal/modules/esm/create_dynamic_module',
'NativeModule internal/modules/esm/get_format',
'NativeModule internal/modules/esm/get_source',
diff --git a/test/parallel/test-internal-module-map-asserts.js b/test/parallel/test-internal-module-map-asserts.js
deleted file mode 100644
index 6f985faccd92bb..00000000000000
--- a/test/parallel/test-internal-module-map-asserts.js
+++ /dev/null
@@ -1,42 +0,0 @@
-// Flags: --expose-internals
-'use strict';
-
-require('../common');
-const assert = require('assert');
-const ModuleMap = require('internal/modules/esm/module_map');
-
-// ModuleMap.get, ModuleMap.has and ModuleMap.set should only accept string
-// values as url argument.
-{
- const errorObj = {
- code: 'ERR_INVALID_ARG_TYPE',
- name: 'TypeError',
- message: /^The "url" argument must be of type string/
- };
-
- const moduleMap = new ModuleMap();
-
- // As long as the assertion of "job" argument is done after the assertion of
- // "url" argument this test suite is ok. Tried to mock the "job" parameter,
- // but I think it's useless, and was not simple to mock...
- const job = undefined;
-
- [{}, [], true, 1].forEach((value) => {
- assert.throws(() => moduleMap.get(value), errorObj);
- assert.throws(() => moduleMap.has(value), errorObj);
- assert.throws(() => moduleMap.set(value, job), errorObj);
- });
-}
-
-// ModuleMap.set, job argument should only accept ModuleJob values.
-{
- const moduleMap = new ModuleMap();
-
- [{}, [], true, 1].forEach((value) => {
- assert.throws(() => moduleMap.set('', value), {
- code: 'ERR_INVALID_ARG_TYPE',
- name: 'TypeError',
- message: /^The "job" argument must be an instance of ModuleJob/
- });
- });
-}
diff --git a/test/parallel/test-vm-module-dynamic-import.js b/test/parallel/test-vm-module-dynamic-import.js
index 2273497d27677c..cd318511401412 100644
--- a/test/parallel/test-vm-module-dynamic-import.js
+++ b/test/parallel/test-vm-module-dynamic-import.js
@@ -1,6 +1,6 @@
'use strict';
-// Flags: --experimental-vm-modules --harmony-import-assertions
+// Flags: --experimental-vm-modules
const common = require('../common');
diff --git a/test/parallel/test-vm-module-link.js b/test/parallel/test-vm-module-link.js
index 9805d8fe3eee9c..16694d5d846075 100644
--- a/test/parallel/test-vm-module-link.js
+++ b/test/parallel/test-vm-module-link.js
@@ -1,6 +1,6 @@
'use strict';
-// Flags: --experimental-vm-modules --harmony-import-assertions
+// Flags: --experimental-vm-modules
const common = require('../common');
diff --git a/tools/code_cache/mkcodecache.cc b/tools/code_cache/mkcodecache.cc
index 9a0127184372bc..babf8535dbb3e7 100644
--- a/tools/code_cache/mkcodecache.cc
+++ b/tools/code_cache/mkcodecache.cc
@@ -28,6 +28,7 @@ int main(int argc, char* argv[]) {
#endif // _WIN32
v8::V8::SetFlagsFromString("--random_seed=42");
+ v8::V8::SetFlagsFromString("--harmony-import-assertions");
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " \n";