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";