From cdee44557e38ca35bdb2eff082cf08acebec8766 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sat, 28 Aug 2021 01:47:49 +0200 Subject: [PATCH 1/5] esm: add support for JSON import assertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove V8 flag for import assertions, enabling support for the syntax; require the import assertion syntax for imports of JSON. Support import assertions in user loaders. Use both resolved module URL and import assertion type as the key for caching modules. Co-authored-by: Geoffrey Booth PR-URL: https://github.com/nodejs/node/pull/40250 Reviewed-By: Bradley Farias Reviewed-By: Michaël Zasso Reviewed-By: Geoffrey Booth --- doc/api/errors.md | 30 ++++ doc/api/esm.md | 43 +++++- lib/internal/errors.js | 6 + lib/internal/modules/cjs/loader.js | 10 +- lib/internal/modules/esm/assert.js | 102 ++++++++++++++ lib/internal/modules/esm/load.js | 14 +- lib/internal/modules/esm/loader.js | 100 ++++++++++---- lib/internal/modules/esm/module_job.js | 18 ++- lib/internal/modules/esm/module_map.js | 26 +++- lib/internal/modules/esm/translators.js | 4 +- lib/internal/modules/run_main.js | 6 +- lib/internal/process/execution.js | 6 +- lib/repl.js | 10 +- src/node.cc | 7 + .../test-esm-assertionless-json-import.js | 81 +++++++++++ test/es-module/test-esm-data-urls.js | 15 +- .../test-esm-dynamic-import-assertion.js | 48 +++++++ .../test-esm-dynamic-import-assertion.mjs | 43 ++++++ .../es-module/test-esm-import-assertion-1.mjs | 2 +- .../es-module/test-esm-import-assertion-2.mjs | 8 ++ .../es-module/test-esm-import-assertion-3.mjs | 11 ++ .../es-module/test-esm-import-assertion-4.mjs | 12 ++ .../test-esm-import-assertion-errors.js | 53 ++++++++ .../test-esm-import-assertion-errors.mjs | 48 +++++++ .../test-esm-import-assertion-validation.js | 37 +++++ test/es-module/test-esm-json-cache.mjs | 3 +- test/es-module/test-esm-json.mjs | 2 +- test/es-module/test-esm-loader-modulemap.js | 128 ++++++++++++------ test/fixtures/empty.json | 1 + .../assertionless-json-import.mjs | 17 +++ .../builtin-named-exports-loader.mjs | 1 + .../es-module-loaders/hooks-custom.mjs | 1 + .../loader-invalid-format.mjs | 4 +- .../es-module-loaders/loader-invalid-url.mjs | 7 +- .../es-module-loaders/loader-shared-dep.mjs | 6 +- .../es-module-loaders/loader-with-dep.mjs | 4 +- .../not-found-assert-loader.mjs | 9 +- .../es-module-loaders/string-sources.mjs | 2 +- test/fixtures/es-modules/json-modules.mjs | 2 +- ..._syntax_error_import_json_named_export.mjs | 4 + ..._syntax_error_import_json_named_export.out | 10 ++ test/message/esm_import_assertion_failed.mjs | 2 + test/message/esm_import_assertion_failed.out | 17 +++ test/message/esm_import_assertion_missing.mjs | 3 + test/message/esm_import_assertion_missing.out | 17 +++ .../esm_import_assertion_unsupported.mjs | 2 + .../esm_import_assertion_unsupported.out | 17 +++ test/parallel/test-bootstrap-modules.js | 1 + .../test-internal-module-map-asserts.js | 42 ------ .../parallel/test-vm-module-dynamic-import.js | 2 +- test/parallel/test-vm-module-link.js | 2 +- tools/code_cache/mkcodecache.cc | 1 + 52 files changed, 878 insertions(+), 169 deletions(-) create mode 100644 lib/internal/modules/esm/assert.js create mode 100644 test/es-module/test-esm-assertionless-json-import.js create mode 100644 test/es-module/test-esm-dynamic-import-assertion.js create mode 100644 test/es-module/test-esm-dynamic-import-assertion.mjs create mode 100644 test/es-module/test-esm-import-assertion-2.mjs create mode 100644 test/es-module/test-esm-import-assertion-3.mjs create mode 100644 test/es-module/test-esm-import-assertion-4.mjs create mode 100644 test/es-module/test-esm-import-assertion-errors.js create mode 100644 test/es-module/test-esm-import-assertion-errors.mjs create mode 100644 test/es-module/test-esm-import-assertion-validation.js create mode 100644 test/fixtures/empty.json create mode 100644 test/fixtures/es-module-loaders/assertionless-json-import.mjs create mode 100644 test/message/esm_display_syntax_error_import_json_named_export.mjs create mode 100644 test/message/esm_display_syntax_error_import_json_named_export.out create mode 100644 test/message/esm_import_assertion_failed.mjs create mode 100644 test/message/esm_import_assertion_failed.out create mode 100644 test/message/esm_import_assertion_missing.mjs create mode 100644 test/message/esm_import_assertion_missing.out create mode 100644 test/message/esm_import_assertion_unsupported.mjs create mode 100644 test/message/esm_import_assertion_unsupported.out delete mode 100644 test/parallel/test-internal-module-map-asserts.js diff --git a/doc/api/errors.md b/doc/api/errors.md index 2369e6eda720..b2e6ad86f84f 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 f6d340c04ad4..bfaa04af923b 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -7,6 +7,9 @@ + +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: + +| `type` | Resolves to | +| -------- | ---------------- | +| `'json'` | [JSON modules][] | + ## Builtin modules [Core modules][] provide named exports of their public API. A @@ -511,10 +536,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 @@ -602,12 +625,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 +715,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: @@ -1373,6 +1406,8 @@ success! [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 +[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 c510bd321512..a0b870132637 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 b8eff0440624..5c2de2d9c57f 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 000000000000..e7d8cbb519fb --- /dev/null +++ b/lib/internal/modules/esm/assert.js @@ -0,0 +1,102 @@ +'use strict'; + +const { + ArrayPrototypeIncludes, + ObjectCreate, + ObjectValues, + ObjectPrototypeHasOwnProperty, + Symbol, +} = 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; + +const kImplicitAssertType = Symbol('implicit assert type'); + +/** + * Define a map of module formats to import assertion types (the value of `type` + * in `assert { type: 'json' }`). + * @type {Map} 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 types we don't recognize, to allow new + // formats in the future. + return true; + + case importAssertions.type: + // The asserted type is the valid type for this format. + 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); + + default: + // There is an expected type for this format, but the value of + // `importAssertions.type` was not 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` was not 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 38785e78f338..67123792e890 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 b12a87a90212..3b8d2ae158f9 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 2f699376d6ea..018d598796f1 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 9e1116a5647f..d51986dd700c 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) => { @@ -10,25 +12,35 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes; const { validateString } = require('internal/validators'); +const validateAssertType = (type) => + type === kImplicitAssertType || validateString(type, 'type'); + // 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); + validateAssertType(type); + return super.get(url)?.[type]; } - set(url, job) { + set(url, type = kImplicitAssertType, job) { validateString(url, 'url'); + validateAssertType(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); + validateAssertType(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 ba00041c4177..157e23044b07 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) { diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index d0c08b75e7a5..9a0263024144 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 e10a4f413cdd..2bed5f3c762c 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 4ee8e24d4758..c85ccbde5a44 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 0fa51b269764..254181b6fbdb 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/es-module/test-esm-assertionless-json-import.js b/test/es-module/test-esm-assertionless-json-import.js new file mode 100644 index 000000000000..2f06508dd2e5 --- /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 78cd01b4d55a..3c0e276b2c0f 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 000000000000..c6ff97d790a4 --- /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 000000000000..a53ea145479e --- /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-import-assertion-1.mjs b/test/es-module/test-esm-import-assertion-1.mjs index 90ccadf5334f..f011c948d8ed 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 000000000000..3598f353a3f9 --- /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 000000000000..0409095aec5d --- /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 000000000000..4f3e33a6eefe --- /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 000000000000..3a55c23fbfbd --- /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('data:text/javascript,', { 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 000000000000..c96e8f3dd046 --- /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 000000000000..7e64bd47392a --- /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', +}); + +// This should be allowed according to HTML spec. Let's keep it disabled +// until WASM module import is sorted out. +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-json-cache.mjs b/test/es-module/test-esm-json-cache.mjs index 68ea832ab695..90694748c39e 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 df4f75fbd6e0..f33b4f9937dd 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 48443de4c270..dbfda7549243 100644 --- a/test/es-module/test-esm-loader-modulemap.js +++ b/test/es-module/test-esm-loader-modulemap.js @@ -1,61 +1,101 @@ '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 { kImplicitAssertType } = require('internal/modules/esm/assert'); 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, kImplicitAssertType), 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, kImplicitAssertType), 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/fixtures/empty.json b/test/fixtures/empty.json new file mode 100644 index 000000000000..0967ef424bce --- /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 000000000000..c5c2fadf28fb --- /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 f206d7635b3f..82e645674948 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 59f49ff9e60c..cd9d5020ad32 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 fc1b84658b76..0210f73b5543 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 e7de0d4ed923..ad69faff26d4 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 3576c074d52c..387575794c00 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 da7d44ae793e..78a72cca6d90 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 9a2cd735a2fd..5213ddedb34e 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 180a356bc814..384098d6d9e8 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-modules/json-modules.mjs b/test/fixtures/es-modules/json-modules.mjs index fa3f936bac92..607c09e51cda 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/message/esm_display_syntax_error_import_json_named_export.mjs b/test/message/esm_display_syntax_error_import_json_named_export.mjs new file mode 100644 index 000000000000..3b7c721daf16 --- /dev/null +++ b/test/message/esm_display_syntax_error_import_json_named_export.mjs @@ -0,0 +1,4 @@ +// Flags: --experimental-json-modules +/* eslint-disable no-unused-vars */ +import '../common/index.mjs'; +import { ofLife } from '../fixtures/experimental.json' assert { type: 'json' }; diff --git a/test/message/esm_display_syntax_error_import_json_named_export.out b/test/message/esm_display_syntax_error_import_json_named_export.out new file mode 100644 index 000000000000..249f27f7e5fa --- /dev/null +++ b/test/message/esm_display_syntax_error_import_json_named_export.out @@ -0,0 +1,10 @@ +file:///*/test/message/esm_display_syntax_error_import_json_named_export.mjs:* +import { ofLife } from '../fixtures/experimental.json' assert { type: 'json' }; + ^^^^^^ +SyntaxError: The requested module '../fixtures/experimental.json' does not provide an export named 'ofLife' + 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_import_assertion_failed.mjs b/test/message/esm_import_assertion_failed.mjs new file mode 100644 index 000000000000..30ea65c3e34e --- /dev/null +++ b/test/message/esm_import_assertion_failed.mjs @@ -0,0 +1,2 @@ +import '../common/index.mjs'; +import 'data:text/javascript,export{}' assert {type:'json'}; diff --git a/test/message/esm_import_assertion_failed.out b/test/message/esm_import_assertion_failed.out new file mode 100644 index 000000000000..545f8cc0620f --- /dev/null +++ b/test/message/esm_import_assertion_failed.out @@ -0,0 +1,17 @@ +node:internal/errors:* + ErrorCaptureStackTrace(err); + ^ + +TypeError [ERR_IMPORT_ASSERTION_TYPE_FAILED]: Module "data:text/javascript,export{}" is not of type "json" + at new NodeError (node:internal/errors:*:*) + at handleInvalidType (node:internal/modules/esm/assert:*:*) + at validateAssertions (node:internal/modules/esm/assert:*:*) + at defaultLoad (node:internal/modules/esm/load:*:*) + at ESMLoader.load (node:internal/modules/esm/loader:*:*) + at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) + at new ModuleJob (node:internal/modules/esm/module_job:*:*) + at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:*:*) + at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*) + at async ModuleWrap. (node:internal/modules/esm/module_job:*:*) { + code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' +} diff --git a/test/message/esm_import_assertion_missing.mjs b/test/message/esm_import_assertion_missing.mjs new file mode 100644 index 000000000000..0b402d9e7ff9 --- /dev/null +++ b/test/message/esm_import_assertion_missing.mjs @@ -0,0 +1,3 @@ +// Flags: --experimental-json-modules +import '../common/index.mjs'; +import 'data:application/json,{}'; diff --git a/test/message/esm_import_assertion_missing.out b/test/message/esm_import_assertion_missing.out new file mode 100644 index 000000000000..0d25d948d847 --- /dev/null +++ b/test/message/esm_import_assertion_missing.out @@ -0,0 +1,17 @@ +node:internal/errors:* + ErrorCaptureStackTrace(err); + ^ + +TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "data:application/json,{}" needs an import assertion of type "json" + at new NodeError (node:internal/errors:*:*) + at validateAssertions (node:internal/modules/esm/assert:*:*) + at defaultLoad (node:internal/modules/esm/load:*:*) + at ESMLoader.load (node:internal/modules/esm/loader:*:*) + at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) + at new ModuleJob (node:internal/modules/esm/module_job:*:*) + at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:*:*) + at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*) + at async ModuleWrap. (node:internal/modules/esm/module_job:*:*) + at async Promise.all (index *) { + code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' +} diff --git a/test/message/esm_import_assertion_unsupported.mjs b/test/message/esm_import_assertion_unsupported.mjs new file mode 100644 index 000000000000..86e594ce02ae --- /dev/null +++ b/test/message/esm_import_assertion_unsupported.mjs @@ -0,0 +1,2 @@ +import '../common/index.mjs'; +import '../fixtures/empty.js' assert { type: 'unsupported' }; diff --git a/test/message/esm_import_assertion_unsupported.out b/test/message/esm_import_assertion_unsupported.out new file mode 100644 index 000000000000..c35ba5bfe07c --- /dev/null +++ b/test/message/esm_import_assertion_unsupported.out @@ -0,0 +1,17 @@ +node:internal/errors:* + ErrorCaptureStackTrace(err); + ^ + +TypeError [ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED]: Import assertion type "unsupported" is unsupported + at new NodeError (node:internal/errors:*:*) + at handleInvalidType (node:internal/modules/esm/assert:*:*) + at validateAssertions (node:internal/modules/esm/assert:*:*) + at defaultLoad (node:internal/modules/esm/load:*:*) + at ESMLoader.load (node:internal/modules/esm/loader:*:*) + at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) + at new ModuleJob (node:internal/modules/esm/module_job:*:*) + at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:*:*) + at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*) + at async ModuleWrap. (node:internal/modules/esm/module_job:*:*) { + code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' +} diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 64cc8a5f165a..4177b3756e07 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 6f985faccd92..000000000000 --- 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 2273497d2767..cd3185114014 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 9805d8fe3eee..16694d5d8460 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 9a0127184372..babf8535dbb3 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"; From 0d728a3ce503c87a8f47cb1bcf414723e2cf28a7 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sat, 6 Nov 2021 10:06:29 +0100 Subject: [PATCH 2/5] test: disable warnings to fix flaky test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/nodejs/node/issues/40738 PR-URL: https://github.com/nodejs/node/pull/40739 Reviewed-By: Michaël Zasso Reviewed-By: Luigi Pinca --- .../esm_display_syntax_error_import_json_named_export.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/message/esm_display_syntax_error_import_json_named_export.mjs b/test/message/esm_display_syntax_error_import_json_named_export.mjs index 3b7c721daf16..fc1434046785 100644 --- a/test/message/esm_display_syntax_error_import_json_named_export.mjs +++ b/test/message/esm_display_syntax_error_import_json_named_export.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-json-modules +// Flags: --experimental-json-modules --no-warnings /* eslint-disable no-unused-vars */ import '../common/index.mjs'; import { ofLife } from '../fixtures/experimental.json' assert { type: 'json' }; From 7ad30284542ec76cf6de09c7a425d25bb108cbae Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 9 Nov 2021 20:39:29 -0800 Subject: [PATCH 3/5] doc: fix spelling of 'WebAssembly' PR-URL: https://github.com/nodejs/node/pull/40785 Reviewed-By: James M Snell Reviewed-By: Bradley Farias Reviewed-By: Myles Borins Reviewed-By: Guy Bedford Reviewed-By: Antoine du Hamel --- doc/api/esm.md | 6 +++--- lib/internal/modules/esm/translators.js | 2 +- test/es-module/test-esm-wasm.mjs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index bfaa04af923b..71873519eefb 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -554,12 +554,12 @@ node --experimental-json-modules index.mjs # works > 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: @@ -1405,7 +1405,7 @@ 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 proposal]: https://github.com/tc39/proposal-import-assertions [JSON modules]: #json-modules [Node.js Module Resolution Algorithm]: #resolver-algorithm-specification diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 157e23044b07..fdeaba0549ae 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -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/test/es-module/test-esm-wasm.mjs b/test/es-module/test-esm-wasm.mjs index 877a841850dc..01717c47714f 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' )); }); From 55fdfb28743ce9434a302bf9a287ba8cfaf096de Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 9 Nov 2021 23:23:47 -0800 Subject: [PATCH 4/5] module: import assertions improvements PR-URL: https://github.com/nodejs/node/pull/40785 Reviewed-By: James M Snell Reviewed-By: Bradley Farias Reviewed-By: Myles Borins Reviewed-By: Guy Bedford Reviewed-By: Antoine du Hamel --- doc/api/esm.md | 14 +++++-- lib/internal/modules/esm/assert.js | 38 +++++++++++-------- lib/internal/modules/esm/module_map.js | 9 ++--- .../test-esm-import-assertion-errors.js | 2 +- .../test-esm-import-assertion-validation.js | 4 +- test/es-module/test-esm-loader-modulemap.js | 5 +-- 6 files changed, 41 insertions(+), 31 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 71873519eefb..4c784da560fb 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -225,6 +225,8 @@ import fs from 'node:fs/promises'; added: REPLACEME --> +> Stability: 1 - Experimental + The [Import Assertions proposal][] adds an inline syntax for module import statements to pass on more information alongside the module specifier. @@ -235,11 +237,12 @@ const { default: barData } = await import('./bar.json', { assert: { type: 'json' } }); ``` -Node.js supports the following `type` values: +Node.js supports the following `type` values, for which the assertion is +mandatory: -| `type` | Resolves to | -| -------- | ---------------- | -| `'json'` | [JSON modules][] | +| Assertion `type` | Needed for | +| ---------------- | ---------------- | +| `'json'` | [JSON modules][] | ## Builtin modules @@ -548,6 +551,8 @@ node index.mjs # fails node --experimental-json-modules index.mjs # works ``` +The `assert { type: 'json' }` syntax is mandatory; see [Import Assertions][]. + ## Wasm modules @@ -1406,6 +1411,7 @@ success! [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 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 diff --git a/lib/internal/modules/esm/assert.js b/lib/internal/modules/esm/assert.js index e7d8cbb519fb..f0f8387bea1c 100644 --- a/lib/internal/modules/esm/assert.js +++ b/lib/internal/modules/esm/assert.js @@ -1,11 +1,11 @@ 'use strict'; const { + ArrayPrototypeFilter, ArrayPrototypeIncludes, ObjectCreate, ObjectValues, ObjectPrototypeHasOwnProperty, - Symbol, } = primordials; const { validateString } = require('internal/validators'); @@ -15,12 +15,13 @@ const { ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED, } = require('internal/errors').codes; -const kImplicitAssertType = Symbol('implicit assert type'); +// 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, @@ -28,11 +29,18 @@ const formatTypeMap = { 'commonjs': kImplicitAssertType, 'json': 'json', 'module': kImplicitAssertType, - 'wasm': kImplicitAssertType, // Should probably be 'webassembly' per https://github.com/tc39/proposal-import-assertions + '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 }; -/** @type {Array} + */ +const supportedAssertionTypes = ArrayPrototypeFilter( + ObjectValues(formatTypeMap), + (type) => type !== kImplicitAssertType); /** @@ -50,14 +58,10 @@ function validateAssertions(url, format, switch (validType) { case undefined: - // Ignore assertions for module types we don't recognize, to allow new + // Ignore assertions for module formats we don't recognize, to allow new // formats in the future. return true; - case importAssertions.type: - // The asserted type is the valid type for this format. - 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. @@ -66,9 +70,13 @@ function validateAssertions(url, format, } 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` was not it. + // `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); @@ -86,7 +94,7 @@ function handleInvalidType(url, type) { // `type` might have not been a string. validateString(type, 'type'); - // `type` was not one of the types we understand. + // `type` might not have been one of the types we understand. if (!ArrayPrototypeIncludes(supportedAssertionTypes, type)) { throw new ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED(type); } diff --git a/lib/internal/modules/esm/module_map.js b/lib/internal/modules/esm/module_map.js index d51986dd700c..df02ebd70851 100644 --- a/lib/internal/modules/esm/module_map.js +++ b/lib/internal/modules/esm/module_map.js @@ -12,20 +12,17 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes; const { validateString } = require('internal/validators'); -const validateAssertType = (type) => - type === kImplicitAssertType || validateString(type, 'type'); - // 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, type = kImplicitAssertType) { validateString(url, 'url'); - validateAssertType(type); + validateString(type, 'type'); return super.get(url)?.[type]; } set(url, type = kImplicitAssertType, job) { validateString(url, 'url'); - validateAssertType(type); + validateString(type, 'type'); if (job instanceof ModuleJob !== true && typeof job !== 'function') { throw new ERR_INVALID_ARG_TYPE('job', 'ModuleJob', job); @@ -39,7 +36,7 @@ class ModuleMap extends SafeMap { } has(url, type = kImplicitAssertType) { validateString(url, 'url'); - validateAssertType(type); + validateString(type, 'type'); return super.get(url)?.[type] !== undefined; } } diff --git a/test/es-module/test-esm-import-assertion-errors.js b/test/es-module/test-esm-import-assertion-errors.js index 3a55c23fbfbd..c7d5abee6939 100644 --- a/test/es-module/test-esm-import-assertion-errors.js +++ b/test/es-module/test-esm-import-assertion-errors.js @@ -25,7 +25,7 @@ async function test() { ); await rejects( - import('data:text/javascript,', { assert: { type: 'unsupported' } }), + import(jsModuleDataUrl, { 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 index 7e64bd47392a..3792ad7ff161 100644 --- a/test/es-module/test-esm-import-assertion-validation.js +++ b/test/es-module/test-esm-import-assertion-validation.js @@ -22,8 +22,8 @@ assert.throws(() => validateAssertions(url, 'module', { type: 'json' }), { code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED', }); -// This should be allowed according to HTML spec. Let's keep it disabled -// until WASM module import is sorted out. +// 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', }); diff --git a/test/es-module/test-esm-loader-modulemap.js b/test/es-module/test-esm-loader-modulemap.js index dbfda7549243..190676ec725c 100644 --- a/test/es-module/test-esm-loader-modulemap.js +++ b/test/es-module/test-esm-loader-modulemap.js @@ -7,7 +7,6 @@ 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 { kImplicitAssertType } = require('internal/modules/esm/assert'); const createDynamicModule = require( 'internal/modules/esm/create_dynamic_module'); @@ -38,14 +37,14 @@ const jsonModuleJob = new ModuleJob(loader, stubJsonModule.module, strictEqual(moduleMap.get(jsonModuleDataUrl, 'json'), jsonModuleJob); strictEqual(moduleMap.has(jsModuleDataUrl), true); - strictEqual(moduleMap.has(jsModuleDataUrl, kImplicitAssertType), 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, kImplicitAssertType), false); + strictEqual(moduleMap.has(jsonModuleDataUrl, 'javascript'), false); strictEqual(moduleMap.has(jsonModuleDataUrl), false); strictEqual(moduleMap.has(jsModuleDataUrl, 'unknown'), false); strictEqual(moduleMap.has(jsonModuleDataUrl, 'unknown'), false); From e79707e2704f03faa694ff6583b55cbc3926d755 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth <456802+GeoffreyBooth@users.noreply.github.com> Date: Thu, 6 Jan 2022 03:07:52 -0800 Subject: [PATCH 5/5] esm: refactor esm tests out of test/message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/41352 Reviewed-By: Antoine du Hamel Reviewed-By: Michaël Zasso --- test/common/fixtures.js | 6 +++ test/common/fixtures.mjs | 2 + test/es-module/test-esm-export-not-found.mjs | 39 +++++++++++++++++++ .../test-esm-import-json-named-export.mjs | 25 ++++++++++++ test/es-module/test-esm-loader-not-found.mjs | 27 +++++++++++++ .../test-esm-loader-obsolete-hooks.mjs | 30 ++++++++++++++ .../test-esm-loader-with-syntax-error.mjs | 24 ++++++++++++ ...est-esm-module-not-found-commonjs-hint.mjs | 35 +++++++++++++++++ test/es-module/test-esm-syntax-error.mjs | 19 +++++++++ .../es-module-loaders/syntax-error-import.mjs | 1 - .../es-modules/import-json-named-export.mjs | 2 + .../esm_loader_not_found_cjs_hint_bare.mjs | 5 --- test/fixtures/print-error-message.js | 1 + .../esm_display_syntax_error_import.mjs | 6 --- .../esm_display_syntax_error_import.out | 10 ----- ..._syntax_error_import_json_named_export.mjs | 4 -- ..._syntax_error_import_json_named_export.out | 10 ----- ...esm_display_syntax_error_import_module.mjs | 2 - ...esm_display_syntax_error_import_module.out | 10 ----- .../esm_display_syntax_error_module.mjs | 2 - .../esm_display_syntax_error_module.out | 7 ---- test/message/esm_import_assertion_failed.mjs | 2 - test/message/esm_import_assertion_failed.out | 17 -------- test/message/esm_import_assertion_missing.mjs | 3 -- test/message/esm_import_assertion_missing.out | 17 -------- .../esm_import_assertion_unsupported.mjs | 2 - .../esm_import_assertion_unsupported.out | 17 -------- test/message/esm_loader_not_found.mjs | 3 -- test/message/esm_loader_not_found.out | 18 --------- .../esm_loader_not_found_cjs_hint_bare.js | 17 -------- .../esm_loader_not_found_cjs_hint_bare.out | 16 -------- ...esm_loader_not_found_cjs_hint_relative.mjs | 3 -- ...esm_loader_not_found_cjs_hint_relative.out | 20 ---------- test/message/esm_loader_syntax_error.mjs | 3 -- test/message/esm_loader_syntax_error.out | 10 ----- .../test-esm-loader-obsolete-hooks.mjs | 4 -- .../test-esm-loader-obsolete-hooks.out | 10 ----- 37 files changed, 210 insertions(+), 219 deletions(-) create mode 100644 test/es-module/test-esm-export-not-found.mjs create mode 100644 test/es-module/test-esm-import-json-named-export.mjs create mode 100644 test/es-module/test-esm-loader-not-found.mjs create mode 100644 test/es-module/test-esm-loader-obsolete-hooks.mjs create mode 100644 test/es-module/test-esm-loader-with-syntax-error.mjs create mode 100644 test/es-module/test-esm-module-not-found-commonjs-hint.mjs create mode 100644 test/es-module/test-esm-syntax-error.mjs delete mode 100644 test/fixtures/es-module-loaders/syntax-error-import.mjs create mode 100644 test/fixtures/es-modules/import-json-named-export.mjs delete mode 100644 test/fixtures/esm_loader_not_found_cjs_hint_bare.mjs create mode 100644 test/fixtures/print-error-message.js delete mode 100644 test/message/esm_display_syntax_error_import.mjs delete mode 100644 test/message/esm_display_syntax_error_import.out delete mode 100644 test/message/esm_display_syntax_error_import_json_named_export.mjs delete mode 100644 test/message/esm_display_syntax_error_import_json_named_export.out delete mode 100644 test/message/esm_display_syntax_error_import_module.mjs delete mode 100644 test/message/esm_display_syntax_error_import_module.out delete mode 100644 test/message/esm_display_syntax_error_module.mjs delete mode 100644 test/message/esm_display_syntax_error_module.out delete mode 100644 test/message/esm_import_assertion_failed.mjs delete mode 100644 test/message/esm_import_assertion_failed.out delete mode 100644 test/message/esm_import_assertion_missing.mjs delete mode 100644 test/message/esm_import_assertion_missing.out delete mode 100644 test/message/esm_import_assertion_unsupported.mjs delete mode 100644 test/message/esm_import_assertion_unsupported.out delete mode 100644 test/message/esm_loader_not_found.mjs delete mode 100644 test/message/esm_loader_not_found.out delete mode 100644 test/message/esm_loader_not_found_cjs_hint_bare.js delete mode 100644 test/message/esm_loader_not_found_cjs_hint_bare.out delete mode 100644 test/message/esm_loader_not_found_cjs_hint_relative.mjs delete mode 100644 test/message/esm_loader_not_found_cjs_hint_relative.out delete mode 100644 test/message/esm_loader_syntax_error.mjs delete mode 100644 test/message/esm_loader_syntax_error.out delete mode 100644 test/message/test-esm-loader-obsolete-hooks.mjs delete mode 100644 test/message/test-esm-loader-obsolete-hooks.out diff --git a/test/common/fixtures.js b/test/common/fixtures.js index e5e1d887df52..3ee87e8b2d7b 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 06564de6fa3b..d6f7f6c092aa 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-export-not-found.mjs b/test/es-module/test-esm-export-not-found.mjs new file mode 100644 index 000000000000..cdfe6df0fcde --- /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-json-named-export.mjs b/test/es-module/test-esm-import-json-named-export.mjs new file mode 100644 index 000000000000..f70b927329b6 --- /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-loader-not-found.mjs b/test/es-module/test-esm-loader-not-found.mjs new file mode 100644 index 000000000000..275f0b0f1e85 --- /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 000000000000..eff4104fc265 --- /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 000000000000..d973e72975e8 --- /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 000000000000..58f70d0b6853 --- /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 000000000000..a8c019171717 --- /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/fixtures/es-module-loaders/syntax-error-import.mjs b/test/fixtures/es-module-loaders/syntax-error-import.mjs deleted file mode 100644 index 3a6bc5effc19..000000000000 --- 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 000000000000..f491e8c252d4 --- /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/esm_loader_not_found_cjs_hint_bare.mjs b/test/fixtures/esm_loader_not_found_cjs_hint_bare.mjs deleted file mode 100644 index 4eb5f190af43..000000000000 --- 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 000000000000..0650bfbd52ba --- /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 2173cb2b2e3a..000000000000 --- 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 62607b93c7c5..000000000000 --- 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_json_named_export.mjs b/test/message/esm_display_syntax_error_import_json_named_export.mjs deleted file mode 100644 index fc1434046785..000000000000 --- a/test/message/esm_display_syntax_error_import_json_named_export.mjs +++ /dev/null @@ -1,4 +0,0 @@ -// Flags: --experimental-json-modules --no-warnings -/* eslint-disable no-unused-vars */ -import '../common/index.mjs'; -import { ofLife } from '../fixtures/experimental.json' assert { type: 'json' }; diff --git a/test/message/esm_display_syntax_error_import_json_named_export.out b/test/message/esm_display_syntax_error_import_json_named_export.out deleted file mode 100644 index 249f27f7e5fa..000000000000 --- a/test/message/esm_display_syntax_error_import_json_named_export.out +++ /dev/null @@ -1,10 +0,0 @@ -file:///*/test/message/esm_display_syntax_error_import_json_named_export.mjs:* -import { ofLife } from '../fixtures/experimental.json' assert { type: 'json' }; - ^^^^^^ -SyntaxError: The requested module '../fixtures/experimental.json' does not provide an export named 'ofLife' - 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 c0345c44fb3f..000000000000 --- 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 0ecb87a952e8..000000000000 --- 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 da40a4ead8d3..000000000000 --- 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 26dbf480239b..000000000000 --- 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_import_assertion_failed.mjs b/test/message/esm_import_assertion_failed.mjs deleted file mode 100644 index 30ea65c3e34e..000000000000 --- a/test/message/esm_import_assertion_failed.mjs +++ /dev/null @@ -1,2 +0,0 @@ -import '../common/index.mjs'; -import 'data:text/javascript,export{}' assert {type:'json'}; diff --git a/test/message/esm_import_assertion_failed.out b/test/message/esm_import_assertion_failed.out deleted file mode 100644 index 545f8cc0620f..000000000000 --- a/test/message/esm_import_assertion_failed.out +++ /dev/null @@ -1,17 +0,0 @@ -node:internal/errors:* - ErrorCaptureStackTrace(err); - ^ - -TypeError [ERR_IMPORT_ASSERTION_TYPE_FAILED]: Module "data:text/javascript,export{}" is not of type "json" - at new NodeError (node:internal/errors:*:*) - at handleInvalidType (node:internal/modules/esm/assert:*:*) - at validateAssertions (node:internal/modules/esm/assert:*:*) - at defaultLoad (node:internal/modules/esm/load:*:*) - at ESMLoader.load (node:internal/modules/esm/loader:*:*) - at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) - at new ModuleJob (node:internal/modules/esm/module_job:*:*) - at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:*:*) - at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*) - at async ModuleWrap. (node:internal/modules/esm/module_job:*:*) { - code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' -} diff --git a/test/message/esm_import_assertion_missing.mjs b/test/message/esm_import_assertion_missing.mjs deleted file mode 100644 index 0b402d9e7ff9..000000000000 --- a/test/message/esm_import_assertion_missing.mjs +++ /dev/null @@ -1,3 +0,0 @@ -// Flags: --experimental-json-modules -import '../common/index.mjs'; -import 'data:application/json,{}'; diff --git a/test/message/esm_import_assertion_missing.out b/test/message/esm_import_assertion_missing.out deleted file mode 100644 index 0d25d948d847..000000000000 --- a/test/message/esm_import_assertion_missing.out +++ /dev/null @@ -1,17 +0,0 @@ -node:internal/errors:* - ErrorCaptureStackTrace(err); - ^ - -TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "data:application/json,{}" needs an import assertion of type "json" - at new NodeError (node:internal/errors:*:*) - at validateAssertions (node:internal/modules/esm/assert:*:*) - at defaultLoad (node:internal/modules/esm/load:*:*) - at ESMLoader.load (node:internal/modules/esm/loader:*:*) - at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) - at new ModuleJob (node:internal/modules/esm/module_job:*:*) - at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:*:*) - at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*) - at async ModuleWrap. (node:internal/modules/esm/module_job:*:*) - at async Promise.all (index *) { - code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' -} diff --git a/test/message/esm_import_assertion_unsupported.mjs b/test/message/esm_import_assertion_unsupported.mjs deleted file mode 100644 index 86e594ce02ae..000000000000 --- a/test/message/esm_import_assertion_unsupported.mjs +++ /dev/null @@ -1,2 +0,0 @@ -import '../common/index.mjs'; -import '../fixtures/empty.js' assert { type: 'unsupported' }; diff --git a/test/message/esm_import_assertion_unsupported.out b/test/message/esm_import_assertion_unsupported.out deleted file mode 100644 index c35ba5bfe07c..000000000000 --- a/test/message/esm_import_assertion_unsupported.out +++ /dev/null @@ -1,17 +0,0 @@ -node:internal/errors:* - ErrorCaptureStackTrace(err); - ^ - -TypeError [ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED]: Import assertion type "unsupported" is unsupported - at new NodeError (node:internal/errors:*:*) - at handleInvalidType (node:internal/modules/esm/assert:*:*) - at validateAssertions (node:internal/modules/esm/assert:*:*) - at defaultLoad (node:internal/modules/esm/load:*:*) - at ESMLoader.load (node:internal/modules/esm/loader:*:*) - at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) - at new ModuleJob (node:internal/modules/esm/module_job:*:*) - at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:*:*) - at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*) - at async ModuleWrap. (node:internal/modules/esm/module_job:*:*) { - code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' -} diff --git a/test/message/esm_loader_not_found.mjs b/test/message/esm_loader_not_found.mjs deleted file mode 100644 index 2b47e5a03ec9..000000000000 --- 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 21d7ab3e44f2..000000000000 --- 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 437fa2d3d430..000000000000 --- 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 4a255ebf0b69..000000000000 --- 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 928186318bb0..000000000000 --- 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 817b4aa0724e..000000000000 --- 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 68cde42e5856..000000000000 --- 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 fb8a2030828a..000000000000 --- 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 9a6a9c48057b..000000000000 --- 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 5099c9c8d861..000000000000 --- 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:*:*)