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 2369e6eda72082..b2e6ad86f84f84 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1679,6 +1679,36 @@ is set for the `Http2Stream`. An attempt was made to construct an object using a non-public constructor. + + +### `ERR_IMPORT_ASSERTION_TYPE_FAILED` + + + +An import assertion has failed, preventing the specified module to be imported. + + + +### `ERR_IMPORT_ASSERTION_TYPE_MISSING` + + + +An import assertion is missing, preventing the specified module to be imported. + + + +### `ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED` + + + +An import assertion is not supported by this version of Node.js. + ### `ERR_INCOMPATIBLE_OPTION_PAIR` diff --git a/doc/api/esm.md b/doc/api/esm.md index f6d340c04ad4cd..bfaa04af923ba6 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 c510bd321512a0..a0b87013263754 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1083,6 +1083,12 @@ E('ERR_HTTP_SOCKET_ENCODING', E('ERR_HTTP_TRAILER_INVALID', 'Trailers are invalid with this transfer encoding', Error); E('ERR_ILLEGAL_CONSTRUCTOR', 'Illegal constructor', TypeError); +E('ERR_IMPORT_ASSERTION_TYPE_FAILED', + 'Module "%s" is not of type "%s"', TypeError); +E('ERR_IMPORT_ASSERTION_TYPE_MISSING', + 'Module "%s" needs an import assertion of type "%s"', TypeError); +E('ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED', + 'Import assertion type "%s" is unsupported', TypeError); E('ERR_INCOMPATIBLE_OPTION_PAIR', 'Option "%s" cannot be used in combination with option "%s"', TypeError); E('ERR_INPUT_TYPE_NOT_ALLOWED', '--input-type can only be used with string ' + diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index b8eff0440624a4..5c2de2d9c57ffd 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -1021,9 +1021,10 @@ function wrapSafe(filename, content, cjsModuleInstance) { filename, lineOffset: 0, displayErrors: true, - importModuleDynamically: async (specifier) => { + importModuleDynamically: async (specifier, _, importAssertions) => { const loader = asyncESM.esmLoader; - return loader.import(specifier, normalizeReferrerURL(filename)); + return loader.import(specifier, normalizeReferrerURL(filename), + importAssertions); }, }); } @@ -1036,9 +1037,10 @@ function wrapSafe(filename, content, cjsModuleInstance) { '__dirname', ], { filename, - importModuleDynamically(specifier) { + importModuleDynamically(specifier, _, importAssertions) { const loader = asyncESM.esmLoader; - return loader.import(specifier, normalizeReferrerURL(filename)); + return loader.import(specifier, normalizeReferrerURL(filename), + importAssertions); }, }); } catch (err) { diff --git a/lib/internal/modules/esm/assert.js b/lib/internal/modules/esm/assert.js new file mode 100644 index 00000000000000..e7d8cbb519fb79 --- /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 38785e78f338ce..67123792e8903a 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -3,14 +3,26 @@ const { defaultGetFormat } = require('internal/modules/esm/get_format'); const { defaultGetSource } = require('internal/modules/esm/get_source'); const { translators } = require('internal/modules/esm/translators'); +const { validateAssertions } = require('internal/modules/esm/assert'); +/** + * Node.js default load hook. + * @param {string} url + * @param {object} context + * @returns {object} + */ async function defaultLoad(url, context) { let { format, source, } = context; + const { importAssertions } = context; - if (!translators.has(format)) format = defaultGetFormat(url); + if (!format || !translators.has(format)) { + format = defaultGetFormat(url); + } + + validateAssertions(url, format, importAssertions); if ( format === 'builtin' || diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index b12a87a9021242..3b8d2ae158f930 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -10,6 +10,7 @@ const { ArrayPrototypePush, FunctionPrototypeBind, FunctionPrototypeCall, + ObjectAssign, ObjectCreate, ObjectSetPrototypeOf, PromiseAll, @@ -202,15 +203,16 @@ class ESMLoader { const { ModuleWrap, callbackMap } = internalBinding('module_wrap'); const module = new ModuleWrap(url, undefined, source, 0, 0); callbackMap.set(module, { - importModuleDynamically: (specifier, { url }) => { - return this.import(specifier, url); + importModuleDynamically: (specifier, { url }, importAssertions) => { + return this.import(specifier, url, importAssertions); } }); return module; }; - const job = new ModuleJob(this, url, evalInstance, false, false); - this.moduleMap.set(url, job); + const job = new ModuleJob( + this, url, undefined, evalInstance, false, false); + this.moduleMap.set(url, undefined, job); const { module } = await job.run(); return { @@ -218,20 +220,65 @@ class ESMLoader { }; } - async getModuleJob(specifier, parentURL) { - const { format, url } = await this.resolve(specifier, parentURL); - let job = this.moduleMap.get(url); + /** + * Get a (possibly still pending) module job from the cache, + * or create one and return its Promise. + * @param {string} specifier The string after `from` in an `import` statement, + * or the first parameter of an `import()` + * expression + * @param {string | undefined} parentURL The URL of the module importing this + * one, unless this is the Node.js entry + * point. + * @param {Record} importAssertions Validations for the + * module import. + * @returns {Promise} The (possibly pending) module job + */ + async getModuleJob(specifier, parentURL, importAssertions) { + let importAssertionsForResolve; + if (this.#loaders.length !== 1) { + // We can skip cloning if there are no user provided loaders because + // the Node.js default resolve hook does not use import assertions. + importAssertionsForResolve = + ObjectAssign(ObjectCreate(null), importAssertions); + } + const { format, url } = + await this.resolve(specifier, parentURL, importAssertionsForResolve); + + let job = this.moduleMap.get(url, importAssertions.type); + // CommonJS will set functions for lazy job evaluation. - if (typeof job === 'function') this.moduleMap.set(url, job = job()); + if (typeof job === 'function') { + this.moduleMap.set(url, undefined, job = job()); + } + + if (job === undefined) { + job = this.#createModuleJob(url, importAssertions, parentURL, format); + } - if (job !== undefined) return job; + return job; + } + /** + * Create and cache an object representing a loaded module. + * @param {string} url The absolute URL that was resolved for this module + * @param {Record} importAssertions Validations for the + * module import. + * @param {string} [parentURL] The absolute URL of the module importing this + * one, unless this is the Node.js entry point + * @param {string} [format] The format hint possibly returned by the + * `resolve` hook + * @returns {Promise} The (possibly pending) module job + */ + #createModuleJob(url, importAssertions, parentURL, format) { const moduleProvider = async (url, isMain) => { - const { format: finalFormat, source } = await this.load(url, { format }); + const { format: finalFormat, source } = await this.load( + url, { format, importAssertions }); const translator = translators.get(finalFormat); - if (!translator) throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat); + if (!translator) { + throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat); + } return FunctionPrototypeCall(translator, this, url, source, isMain); }; @@ -241,15 +288,16 @@ class ESMLoader { getOptionValue('--inspect-brk') ); - job = new ModuleJob( + const job = new ModuleJob( this, url, + importAssertions, moduleProvider, parentURL === undefined, inspectBrk ); - this.moduleMap.set(url, job); + this.moduleMap.set(url, importAssertions.type, job); return job; } @@ -261,11 +309,13 @@ class ESMLoader { * This method must NOT be renamed: it functions as a dynamic import on a * loader module. * - * @param {string | string[]} specifiers Path(s) to the module - * @param {string} [parentURL] Path of the parent importing the module - * @returns {object | object[]} A list of module export(s) + * @param {string | string[]} specifiers Path(s) to the module. + * @param {string} parentURL Path of the parent importing the module. + * @param {Record} importAssertions Validations for the + * module import. + * @returns {Promise} A list of module export(s). */ - async import(specifiers, parentURL) { + async import(specifiers, parentURL, importAssertions) { const wasArr = ArrayIsArray(specifiers); if (!wasArr) specifiers = [specifiers]; @@ -273,7 +323,7 @@ class ESMLoader { const jobs = new Array(count); for (let i = 0; i < count; i++) { - jobs[i] = this.getModuleJob(specifiers[i], parentURL) + jobs[i] = this.getModuleJob(specifiers[i], parentURL, importAssertions) .then((job) => job.run()) .then(({ module }) => module.getNamespace()); } @@ -393,13 +443,16 @@ class ESMLoader { * Resolve the location of the module. * * The internals of this WILL change when chaining is implemented, - * depending on the resolution/consensus from #36954 + * depending on the resolution/consensus from #36954. * @param {string} originalSpecifier The specified URL path of the module to - * be resolved - * @param {String} parentURL The URL path of the module's parent - * @returns {{ url: String }} + * be resolved. + * @param {string} [parentURL] The URL path of the module's parent. + * @param {ImportAssertions} [importAssertions] Assertions from the import + * statement or expression. + * @returns {{ url: string }} */ - async resolve(originalSpecifier, parentURL) { + async resolve(originalSpecifier, parentURL, + importAssertions = ObjectCreate(null)) { const isMain = parentURL === undefined; if ( @@ -423,6 +476,7 @@ class ESMLoader { originalSpecifier, { conditions, + importAssertions, parentURL, }, defaultResolver, diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 2f699376d6eaea..018d598796f153 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -6,6 +6,7 @@ const { ArrayPrototypePush, ArrayPrototypeSome, FunctionPrototype, + ObjectCreate, ObjectSetPrototypeOf, PromiseAll, PromiseResolve, @@ -52,8 +53,10 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) => class ModuleJob { // `loader` is the Loader instance used for loading dependencies. // `moduleProvider` is a function - constructor(loader, url, moduleProvider, isMain, inspectBrk) { + constructor(loader, url, importAssertions = ObjectCreate(null), + moduleProvider, isMain, inspectBrk) { this.loader = loader; + this.importAssertions = importAssertions; this.isMain = isMain; this.inspectBrk = inspectBrk; @@ -72,8 +75,8 @@ class ModuleJob { // so that circular dependencies can't cause a deadlock by two of // these `link` callbacks depending on each other. const dependencyJobs = []; - const promises = this.module.link(async (specifier) => { - const jobPromise = this.loader.getModuleJob(specifier, url); + const promises = this.module.link(async (specifier, assertions) => { + const jobPromise = this.loader.getModuleJob(specifier, url, assertions); ArrayPrototypePush(dependencyJobs, jobPromise); const job = await jobPromise; return job.modulePromise; @@ -144,7 +147,14 @@ class ModuleJob { const { url: childFileURL } = await this.loader.resolve( childSpecifier, parentFileUrl, ); - const { format } = await this.loader.load(childFileURL); + let format; + try { + // This might throw for non-CommonJS modules because we aren't passing + // in the import assertions and some formats require them; but we only + // care about CommonJS for the purposes of this error message. + ({ format } = + await this.loader.load(childFileURL)); + } catch {} if (format === 'commonjs') { const importStatement = splitStack[1]; diff --git a/lib/internal/modules/esm/module_map.js b/lib/internal/modules/esm/module_map.js index 9e1116a5647f5f..d51986dd700c85 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 ba00041c417706..157e23044b07fb 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 d0c08b75e7a524..9a0263024144fb 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -1,6 +1,7 @@ 'use strict'; const { + ObjectCreate, StringPrototypeEndsWith, } = primordials; const CJSLoader = require('internal/modules/cjs/loader'); @@ -46,9 +47,8 @@ function runMainESM(mainPath) { handleMainPromise(loadESM((esmLoader) => { const main = path.isAbsolute(mainPath) ? - pathToFileURL(mainPath).href : - mainPath; - return esmLoader.import(main); + pathToFileURL(mainPath).href : mainPath; + return esmLoader.import(main, undefined, ObjectCreate(null)); })); } diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index e10a4f413cdd0d..2bed5f3c762cf0 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -76,9 +76,9 @@ function evalScript(name, body, breakFirstLine, print) { filename: name, displayErrors: true, [kVmBreakFirstLineSymbol]: !!breakFirstLine, - async importModuleDynamically(specifier) { - const loader = await asyncESM.esmLoader; - return loader.import(specifier, baseUrl); + importModuleDynamically(specifier, _, importAssertions) { + const loader = asyncESM.esmLoader; + return loader.import(specifier, baseUrl, importAssertions); } })); if (print) { diff --git a/lib/repl.js b/lib/repl.js index 4ee8e24d47588c..c85ccbde5a44ac 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -454,8 +454,9 @@ function REPLServer(prompt, vm.createScript(fallbackCode, { filename: file, displayErrors: true, - importModuleDynamically: async (specifier) => { - return asyncESM.esmLoader.import(specifier, parentURL); + importModuleDynamically: (specifier, _, importAssertions) => { + return asyncESM.esmLoader.import(specifier, parentURL, + importAssertions); } }); } catch (fallbackError) { @@ -496,8 +497,9 @@ function REPLServer(prompt, script = vm.createScript(code, { filename: file, displayErrors: true, - importModuleDynamically: async (specifier) => { - return asyncESM.esmLoader.import(specifier, parentURL); + importModuleDynamically: (specifier, _, importAssertions) => { + return asyncESM.esmLoader.import(specifier, parentURL, + importAssertions); } }); } catch (e) { diff --git a/src/node.cc b/src/node.cc index 0fa51b269764f4..254181b6fbdb25 100644 --- a/src/node.cc +++ b/src/node.cc @@ -803,6 +803,13 @@ int ProcessGlobalArgs(std::vector* args, return 12; } + // TODO(aduh95): remove this when the harmony-import-assertions flag + // is removed in V8. + if (std::find(v8_args.begin(), v8_args.end(), + "--no-harmony-import-assertions") == v8_args.end()) { + v8_args.push_back("--harmony-import-assertions"); + } + auto env_opts = per_process::cli_options->per_isolate->per_env; if (std::find(v8_args.begin(), v8_args.end(), "--abort-on-uncaught-exception") != v8_args.end() || diff --git a/test/es-module/test-esm-assertionless-json-import.js b/test/es-module/test-esm-assertionless-json-import.js new file mode 100644 index 00000000000000..2f06508dd2e509 --- /dev/null +++ b/test/es-module/test-esm-assertionless-json-import.js @@ -0,0 +1,81 @@ +// Flags: --experimental-json-modules --experimental-loader ./test/fixtures/es-module-loaders/assertionless-json-import.mjs +'use strict'; +const common = require('../common'); +const { strictEqual } = require('assert'); + +async function test() { + { + const [secret0, secret1] = await Promise.all([ + import('../fixtures/experimental.json'), + import( + '../fixtures/experimental.json', + { assert: { type: 'json' } } + ), + ]); + + strictEqual(secret0.default.ofLife, 42); + strictEqual(secret1.default.ofLife, 42); + strictEqual(secret0.default, secret1.default); + strictEqual(secret0, secret1); + } + + { + const [secret0, secret1] = await Promise.all([ + import('../fixtures/experimental.json?test'), + import( + '../fixtures/experimental.json?test', + { assert: { type: 'json' } } + ), + ]); + + strictEqual(secret0.default.ofLife, 42); + strictEqual(secret1.default.ofLife, 42); + strictEqual(secret0.default, secret1.default); + strictEqual(secret0, secret1); + } + + { + const [secret0, secret1] = await Promise.all([ + import('../fixtures/experimental.json#test'), + import( + '../fixtures/experimental.json#test', + { assert: { type: 'json' } } + ), + ]); + + strictEqual(secret0.default.ofLife, 42); + strictEqual(secret1.default.ofLife, 42); + strictEqual(secret0.default, secret1.default); + strictEqual(secret0, secret1); + } + + { + const [secret0, secret1] = await Promise.all([ + import('../fixtures/experimental.json?test2#test'), + import( + '../fixtures/experimental.json?test2#test', + { assert: { type: 'json' } } + ), + ]); + + strictEqual(secret0.default.ofLife, 42); + strictEqual(secret1.default.ofLife, 42); + strictEqual(secret0.default, secret1.default); + strictEqual(secret0, secret1); + } + + { + const [secret0, secret1] = await Promise.all([ + import('data:application/json,{"ofLife":42}'), + import( + 'data:application/json,{"ofLife":42}', + { assert: { type: 'json' } } + ), + ]); + + strictEqual(secret0.default.ofLife, 42); + strictEqual(secret1.default.ofLife, 42); + } +} + +test().then(common.mustCall()); diff --git a/test/es-module/test-esm-data-urls.js b/test/es-module/test-esm-data-urls.js index 78cd01b4d55a12..3c0e276b2c0f44 100644 --- a/test/es-module/test-esm-data-urls.js +++ b/test/es-module/test-esm-data-urls.js @@ -59,21 +59,22 @@ function createBase64URL(mime, body) { assert.deepStrictEqual(ns.default, plainESMURL); } { - const ns = await import('data:application/json;foo="test,"this"'); + const ns = await import('data:application/json;foo="test,"this"', + { assert: { type: 'json' } }); assert.deepStrictEqual(Object.keys(ns), ['default']); assert.deepStrictEqual(ns.default, 'this'); } { const ns = await import(`data:application/json;foo=${ encodeURIComponent('test,') - },0`); + },0`, { assert: { type: 'json' } }); assert.deepStrictEqual(Object.keys(ns), ['default']); assert.deepStrictEqual(ns.default, 0); } { - await assert.rejects(async () => { - return import('data:application/json;foo="test,",0'); - }, { + await assert.rejects(async () => + import('data:application/json;foo="test,",0', + { assert: { type: 'json' } }), { name: 'SyntaxError', message: /Unexpected end of JSON input/ }); @@ -81,14 +82,14 @@ function createBase64URL(mime, body) { { const body = '{"x": 1}'; const plainESMURL = createURL('application/json', body); - const ns = await import(plainESMURL); + const ns = await import(plainESMURL, { assert: { type: 'json' } }); assert.deepStrictEqual(Object.keys(ns), ['default']); assert.deepStrictEqual(ns.default.x, 1); } { const body = '{"default": 2}'; const plainESMURL = createURL('application/json', body); - const ns = await import(plainESMURL); + const ns = await import(plainESMURL, { assert: { type: 'json' } }); assert.deepStrictEqual(Object.keys(ns), ['default']); assert.deepStrictEqual(ns.default.default, 2); } diff --git a/test/es-module/test-esm-dynamic-import-assertion.js b/test/es-module/test-esm-dynamic-import-assertion.js new file mode 100644 index 00000000000000..c6ff97d790a44c --- /dev/null +++ b/test/es-module/test-esm-dynamic-import-assertion.js @@ -0,0 +1,48 @@ +// Flags: --experimental-json-modules +'use strict'; +const common = require('../common'); +const { strictEqual } = require('assert'); + +async function test() { + { + const results = await Promise.allSettled([ + import('../fixtures/empty.js', { assert: { type: 'json' } }), + import('../fixtures/empty.js'), + ]); + + strictEqual(results[0].status, 'rejected'); + strictEqual(results[1].status, 'fulfilled'); + } + + { + const results = await Promise.allSettled([ + import('../fixtures/empty.js'), + import('../fixtures/empty.js', { assert: { type: 'json' } }), + ]); + + strictEqual(results[0].status, 'fulfilled'); + strictEqual(results[1].status, 'rejected'); + } + + { + const results = await Promise.allSettled([ + import('../fixtures/empty.json', { assert: { type: 'json' } }), + import('../fixtures/empty.json'), + ]); + + strictEqual(results[0].status, 'fulfilled'); + strictEqual(results[1].status, 'rejected'); + } + + { + const results = await Promise.allSettled([ + import('../fixtures/empty.json'), + import('../fixtures/empty.json', { assert: { type: 'json' } }), + ]); + + strictEqual(results[0].status, 'rejected'); + strictEqual(results[1].status, 'fulfilled'); + } +} + +test().then(common.mustCall()); diff --git a/test/es-module/test-esm-dynamic-import-assertion.mjs b/test/es-module/test-esm-dynamic-import-assertion.mjs new file mode 100644 index 00000000000000..a53ea145479eb5 --- /dev/null +++ b/test/es-module/test-esm-dynamic-import-assertion.mjs @@ -0,0 +1,43 @@ +// Flags: --experimental-json-modules +import '../common/index.mjs'; +import { strictEqual } from 'assert'; + +{ + const results = await Promise.allSettled([ + import('../fixtures/empty.js', { assert: { type: 'json' } }), + import('../fixtures/empty.js'), + ]); + + strictEqual(results[0].status, 'rejected'); + strictEqual(results[1].status, 'fulfilled'); +} + +{ + const results = await Promise.allSettled([ + import('../fixtures/empty.js'), + import('../fixtures/empty.js', { assert: { type: 'json' } }), + ]); + + strictEqual(results[0].status, 'fulfilled'); + strictEqual(results[1].status, 'rejected'); +} + +{ + const results = await Promise.allSettled([ + import('../fixtures/empty.json', { assert: { type: 'json' } }), + import('../fixtures/empty.json'), + ]); + + strictEqual(results[0].status, 'fulfilled'); + strictEqual(results[1].status, 'rejected'); +} + +{ + const results = await Promise.allSettled([ + import('../fixtures/empty.json'), + import('../fixtures/empty.json', { assert: { type: 'json' } }), + ]); + + strictEqual(results[0].status, 'rejected'); + strictEqual(results[1].status, 'fulfilled'); +} diff --git a/test/es-module/test-esm-import-assertion-1.mjs b/test/es-module/test-esm-import-assertion-1.mjs index 90ccadf5334f7f..f011c948d8edea 100644 --- a/test/es-module/test-esm-import-assertion-1.mjs +++ b/test/es-module/test-esm-import-assertion-1.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-json-modules --harmony-import-assertions +// Flags: --experimental-json-modules import '../common/index.mjs'; import { strictEqual } from 'assert'; diff --git a/test/es-module/test-esm-import-assertion-2.mjs b/test/es-module/test-esm-import-assertion-2.mjs new file mode 100644 index 00000000000000..3598f353a3f9d5 --- /dev/null +++ b/test/es-module/test-esm-import-assertion-2.mjs @@ -0,0 +1,8 @@ +// Flags: --experimental-json-modules +import '../common/index.mjs'; +import { strictEqual } from 'assert'; + +// eslint-disable-next-line max-len +import secret from '../fixtures/experimental.json' assert { type: 'json', unsupportedAssertion: 'should ignore' }; + +strictEqual(secret.ofLife, 42); diff --git a/test/es-module/test-esm-import-assertion-3.mjs b/test/es-module/test-esm-import-assertion-3.mjs new file mode 100644 index 00000000000000..0409095aec5d97 --- /dev/null +++ b/test/es-module/test-esm-import-assertion-3.mjs @@ -0,0 +1,11 @@ +// Flags: --experimental-json-modules +import '../common/index.mjs'; +import { strictEqual } from 'assert'; + +import secret0 from '../fixtures/experimental.json' assert { type: 'json' }; +const secret1 = await import('../fixtures/experimental.json', + { assert: { type: 'json' } }); + +strictEqual(secret0.ofLife, 42); +strictEqual(secret1.default.ofLife, 42); +strictEqual(secret1.default, secret0); diff --git a/test/es-module/test-esm-import-assertion-4.mjs b/test/es-module/test-esm-import-assertion-4.mjs new file mode 100644 index 00000000000000..4f3e33a6eefe2d --- /dev/null +++ b/test/es-module/test-esm-import-assertion-4.mjs @@ -0,0 +1,12 @@ +// Flags: --experimental-json-modules +import '../common/index.mjs'; +import { strictEqual } from 'assert'; + +import secret0 from '../fixtures/experimental.json' assert { type: 'json' }; +const secret1 = await import('../fixtures/experimental.json', { + assert: { type: 'json' }, + }); + +strictEqual(secret0.ofLife, 42); +strictEqual(secret1.default.ofLife, 42); +strictEqual(secret1.default, secret0); diff --git a/test/es-module/test-esm-import-assertion-errors.js b/test/es-module/test-esm-import-assertion-errors.js new file mode 100644 index 00000000000000..3a55c23fbfbdf2 --- /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 00000000000000..c96e8f3dd046b7 --- /dev/null +++ b/test/es-module/test-esm-import-assertion-errors.mjs @@ -0,0 +1,48 @@ +// Flags: --experimental-json-modules +import '../common/index.mjs'; +import { rejects } from 'assert'; + +const jsModuleDataUrl = 'data:text/javascript,export{}'; +const jsonModuleDataUrl = 'data:application/json,""'; + +await rejects( + // This rejects because of the unsupported MIME type, not because of the + // unsupported assertion. + import('data:text/css,', { assert: { type: 'css' } }), + { code: 'ERR_INVALID_MODULE_SPECIFIER' } +); + +await rejects( + import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`), + { code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' } +); + +await rejects( + import(jsModuleDataUrl, { assert: { type: 'json' } }), + { code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' } +); + +await rejects( + import(import.meta.url, { assert: { type: 'unsupported' } }), + { code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' } +); + +await rejects( + import(jsonModuleDataUrl), + { code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' } +); + +await rejects( + import(jsonModuleDataUrl, { assert: {} }), + { code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' } +); + +await rejects( + import(jsonModuleDataUrl, { assert: { foo: 'bar' } }), + { code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' } +); + +await rejects( + import(jsonModuleDataUrl, { assert: { type: 'unsupported' }}), + { code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' } +); diff --git a/test/es-module/test-esm-import-assertion-validation.js b/test/es-module/test-esm-import-assertion-validation.js new file mode 100644 index 00000000000000..7e64bd47392ab0 --- /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 68ea832ab69585..90694748c39e5f 100644 --- a/test/es-module/test-esm-json-cache.mjs +++ b/test/es-module/test-esm-json-cache.mjs @@ -7,7 +7,8 @@ import { createRequire } from 'module'; import mod from '../fixtures/es-modules/json-cache/mod.cjs'; import another from '../fixtures/es-modules/json-cache/another.cjs'; -import test from '../fixtures/es-modules/json-cache/test.json'; +import test from '../fixtures/es-modules/json-cache/test.json' assert + { type: 'json' }; const require = createRequire(import.meta.url); diff --git a/test/es-module/test-esm-json.mjs b/test/es-module/test-esm-json.mjs index df4f75fbd6e067..f33b4f9937ddb1 100644 --- a/test/es-module/test-esm-json.mjs +++ b/test/es-module/test-esm-json.mjs @@ -4,7 +4,7 @@ import { path } from '../common/fixtures.mjs'; import { strictEqual, ok } from 'assert'; import { spawn } from 'child_process'; -import secret from '../fixtures/experimental.json'; +import secret from '../fixtures/experimental.json' assert { type: 'json' }; strictEqual(secret.ofLife, 42); diff --git a/test/es-module/test-esm-loader-modulemap.js b/test/es-module/test-esm-loader-modulemap.js index 48443de4c270c6..dbfda754924372 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 00000000000000..0967ef424bce67 --- /dev/null +++ b/test/fixtures/empty.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/es-module-loaders/assertionless-json-import.mjs b/test/fixtures/es-module-loaders/assertionless-json-import.mjs new file mode 100644 index 00000000000000..c5c2fadf28fb58 --- /dev/null +++ b/test/fixtures/es-module-loaders/assertionless-json-import.mjs @@ -0,0 +1,17 @@ +const DATA_URL_PATTERN = /^data:application\/json(?:[^,]*?)(;base64)?,([\s\S]*)$/; +const JSON_URL_PATTERN = /\.json(\?[^#]*)?(#.*)?$/; + +export function resolve(url, context, next) { + // Mutation from resolve hook should be discarded. + context.importAssertions.type = 'whatever'; + return next(url, context); +} + +export function load(url, context, next) { + if (context.importAssertions.type == null && + (DATA_URL_PATTERN.test(url) || JSON_URL_PATTERN.test(url))) { + const { importAssertions } = context; + importAssertions.type = 'json'; + } + return next(url, context); +} diff --git a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs index f206d7635b3f63..82e64567494842 100644 --- a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs +++ b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs @@ -19,6 +19,7 @@ export function resolve(specifier, context, next) { if (def.url.startsWith('node:')) { return { url: `custom-${def.url}`, + importAssertions: context.importAssertions, }; } return def; diff --git a/test/fixtures/es-module-loaders/hooks-custom.mjs b/test/fixtures/es-module-loaders/hooks-custom.mjs index 59f49ff9e60c13..cd9d5020ad3234 100644 --- a/test/fixtures/es-module-loaders/hooks-custom.mjs +++ b/test/fixtures/es-module-loaders/hooks-custom.mjs @@ -63,6 +63,7 @@ export function resolve(specifier, context, next) { if (specifier.startsWith('esmHook')) return { format, url: specifier, + importAssertions: context.importAssertions, }; return next(specifier, context, next); diff --git a/test/fixtures/es-module-loaders/loader-invalid-format.mjs b/test/fixtures/es-module-loaders/loader-invalid-format.mjs index fc1b84658b76de..0210f73b554382 100644 --- a/test/fixtures/es-module-loaders/loader-invalid-format.mjs +++ b/test/fixtures/es-module-loaders/loader-invalid-format.mjs @@ -1,10 +1,10 @@ -export async function resolve(specifier, { parentURL }, defaultResolve) { +export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) { if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') { return { url: 'file:///asdf' }; } - return defaultResolve(specifier, {parentURL}, defaultResolve); + return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve); } export async function load(url, context, next) { diff --git a/test/fixtures/es-module-loaders/loader-invalid-url.mjs b/test/fixtures/es-module-loaders/loader-invalid-url.mjs index e7de0d4ed92378..ad69faff26d40f 100644 --- a/test/fixtures/es-module-loaders/loader-invalid-url.mjs +++ b/test/fixtures/es-module-loaders/loader-invalid-url.mjs @@ -1,9 +1,10 @@ /* eslint-disable node-core/required-modules */ -export async function resolve(specifier, { parentURL }, defaultResolve) { +export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) { if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') { return { - url: specifier + url: specifier, + importAssertions, }; } - return defaultResolve(specifier, {parentURL}, defaultResolve); + return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve); } diff --git a/test/fixtures/es-module-loaders/loader-shared-dep.mjs b/test/fixtures/es-module-loaders/loader-shared-dep.mjs index 3576c074d52cec..387575794c00dc 100644 --- a/test/fixtures/es-module-loaders/loader-shared-dep.mjs +++ b/test/fixtures/es-module-loaders/loader-shared-dep.mjs @@ -1,11 +1,11 @@ import assert from 'assert'; -import {createRequire} from '../../common/index.mjs'; +import { createRequire } from '../../common/index.mjs'; const require = createRequire(import.meta.url); const dep = require('./loader-dep.js'); -export function resolve(specifier, { parentURL }, defaultResolve) { +export function resolve(specifier, { parentURL, importAssertions }, defaultResolve) { assert.strictEqual(dep.format, 'module'); - return defaultResolve(specifier, {parentURL}, defaultResolve); + return defaultResolve(specifier, { parentURL, importAssertions }, defaultResolve); } diff --git a/test/fixtures/es-module-loaders/loader-with-dep.mjs b/test/fixtures/es-module-loaders/loader-with-dep.mjs index da7d44ae793e22..78a72cca6d9009 100644 --- a/test/fixtures/es-module-loaders/loader-with-dep.mjs +++ b/test/fixtures/es-module-loaders/loader-with-dep.mjs @@ -3,9 +3,9 @@ import {createRequire} from '../../common/index.mjs'; const require = createRequire(import.meta.url); const dep = require('./loader-dep.js'); -export function resolve (specifier, { parentURL }, defaultResolve) { +export function resolve (specifier, { parentURL, importAssertions }, defaultResolve) { return { - url: defaultResolve(specifier, {parentURL}, defaultResolve).url, + url: defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve).url, format: dep.format }; } diff --git a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs index 9a2cd735a2fd66..5213ddedb34e8d 100644 --- a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs +++ b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs @@ -3,18 +3,19 @@ import assert from 'assert'; // a loader that asserts that the defaultResolve will throw "not found" // (skipping the top-level main of course) let mainLoad = true; -export async function resolve(specifier, { parentURL }, defaultResolve) { +export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) { if (mainLoad) { mainLoad = false; - return defaultResolve(specifier, {parentURL}, defaultResolve); + return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve); } try { - await defaultResolve(specifier, {parentURL}, defaultResolve); + await defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve); } catch (e) { assert.strictEqual(e.code, 'ERR_MODULE_NOT_FOUND'); return { - url: 'node:fs' + url: 'node:fs', + importAssertions, }; } assert.fail(`Module resolution for ${specifier} should be throw ERR_MODULE_NOT_FOUND`); diff --git a/test/fixtures/es-module-loaders/string-sources.mjs b/test/fixtures/es-module-loaders/string-sources.mjs index 180a356bc81478..384098d6d9e822 100644 --- a/test/fixtures/es-module-loaders/string-sources.mjs +++ b/test/fixtures/es-module-loaders/string-sources.mjs @@ -22,7 +22,7 @@ const SOURCES = { } export function resolve(specifier, context, next) { if (specifier.startsWith('test:')) { - return { url: specifier }; + return { url: specifier, importAssertions: context.importAssertions }; } return next(specifier, context); } diff --git a/test/fixtures/es-modules/json-modules.mjs b/test/fixtures/es-modules/json-modules.mjs index fa3f936bac921e..607c09e51cda2b 100644 --- a/test/fixtures/es-modules/json-modules.mjs +++ b/test/fixtures/es-modules/json-modules.mjs @@ -1 +1 @@ -import secret from '../experimental.json'; +import secret from '../experimental.json' assert { type: 'json' }; diff --git a/test/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 00000000000000..3b7c721daf1601 --- /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 00000000000000..249f27f7e5face --- /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 00000000000000..30ea65c3e34ee3 --- /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 00000000000000..545f8cc0620f40 --- /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 00000000000000..0b402d9e7ff90a --- /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 00000000000000..0d25d948d84708 --- /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 00000000000000..86e594ce02ae5d --- /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 00000000000000..c35ba5bfe07c69 --- /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 64cc8a5f165a7c..4177b3756e07a4 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -68,6 +68,7 @@ const expectedModules = new Set([ 'NativeModule internal/modules/package_json_reader', 'NativeModule internal/modules/cjs/helpers', 'NativeModule internal/modules/cjs/loader', + 'NativeModule internal/modules/esm/assert', 'NativeModule internal/modules/esm/create_dynamic_module', 'NativeModule internal/modules/esm/get_format', 'NativeModule internal/modules/esm/get_source', diff --git a/test/parallel/test-internal-module-map-asserts.js b/test/parallel/test-internal-module-map-asserts.js deleted file mode 100644 index 6f985faccd92bb..00000000000000 --- a/test/parallel/test-internal-module-map-asserts.js +++ /dev/null @@ -1,42 +0,0 @@ -// Flags: --expose-internals -'use strict'; - -require('../common'); -const assert = require('assert'); -const ModuleMap = require('internal/modules/esm/module_map'); - -// ModuleMap.get, ModuleMap.has and ModuleMap.set should only accept string -// values as url argument. -{ - const errorObj = { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - message: /^The "url" argument must be of type string/ - }; - - const moduleMap = new ModuleMap(); - - // As long as the assertion of "job" argument is done after the assertion of - // "url" argument this test suite is ok. Tried to mock the "job" parameter, - // but I think it's useless, and was not simple to mock... - const job = undefined; - - [{}, [], true, 1].forEach((value) => { - assert.throws(() => moduleMap.get(value), errorObj); - assert.throws(() => moduleMap.has(value), errorObj); - assert.throws(() => moduleMap.set(value, job), errorObj); - }); -} - -// ModuleMap.set, job argument should only accept ModuleJob values. -{ - const moduleMap = new ModuleMap(); - - [{}, [], true, 1].forEach((value) => { - assert.throws(() => moduleMap.set('', value), { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - message: /^The "job" argument must be an instance of ModuleJob/ - }); - }); -} diff --git a/test/parallel/test-vm-module-dynamic-import.js b/test/parallel/test-vm-module-dynamic-import.js index 2273497d27677c..cd318511401412 100644 --- a/test/parallel/test-vm-module-dynamic-import.js +++ b/test/parallel/test-vm-module-dynamic-import.js @@ -1,6 +1,6 @@ 'use strict'; -// Flags: --experimental-vm-modules --harmony-import-assertions +// Flags: --experimental-vm-modules const common = require('../common'); diff --git a/test/parallel/test-vm-module-link.js b/test/parallel/test-vm-module-link.js index 9805d8fe3eee9c..16694d5d846075 100644 --- a/test/parallel/test-vm-module-link.js +++ b/test/parallel/test-vm-module-link.js @@ -1,6 +1,6 @@ 'use strict'; -// Flags: --experimental-vm-modules --harmony-import-assertions +// Flags: --experimental-vm-modules const common = require('../common'); diff --git a/tools/code_cache/mkcodecache.cc b/tools/code_cache/mkcodecache.cc index 9a0127184372bc..babf8535dbb3e7 100644 --- a/tools/code_cache/mkcodecache.cc +++ b/tools/code_cache/mkcodecache.cc @@ -28,6 +28,7 @@ int main(int argc, char* argv[]) { #endif // _WIN32 v8::V8::SetFlagsFromString("--random_seed=42"); + v8::V8::SetFlagsFromString("--harmony-import-assertions"); if (argc < 2) { std::cerr << "Usage: " << argv[0] << " \n"; 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 3b7c721daf1601..fc14340467856b 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 bfaa04af923ba6..71873519eefb02 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 157e23044b07fb..fdeaba0549ae9b 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 877a841850dc4f..01717c47714f6a 100644 --- a/test/es-module/test-esm-wasm.mjs +++ b/test/es-module/test-esm-wasm.mjs @@ -31,7 +31,7 @@ child.on('close', (code, signal) => { strictEqual(code, 0); strictEqual(signal, null); ok(stderr.toString().includes( - 'ExperimentalWarning: Importing Web Assembly modules is ' + + 'ExperimentalWarning: Importing WebAssembly modules is ' + 'an experimental feature. This feature could change at any time' )); }); 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 71873519eefb02..4c784da560fbaf 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 e7d8cbb519fb79..f0f8387bea1cbf 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 d51986dd700c85..df02ebd708517f 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 3a55c23fbfbdf2..c7d5abee693979 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 7e64bd47392ab0..3792ad7ff1617c 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 dbfda754924372..190676ec725cd2 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 e5e1d887df525e..3ee87e8b2d7b59 100644 --- a/test/common/fixtures.js +++ b/test/common/fixtures.js @@ -2,6 +2,7 @@ const path = require('path'); const fs = require('fs'); +const { pathToFileURL } = require('url'); const fixturesDir = path.join(__dirname, '..', 'fixtures'); @@ -9,6 +10,10 @@ function fixturesPath(...args) { return path.join(fixturesDir, ...args); } +function fixturesFileURL(...args) { + return pathToFileURL(fixturesPath(...args)); +} + function readFixtureSync(args, enc) { if (Array.isArray(args)) return fs.readFileSync(fixturesPath(...args), enc); @@ -26,6 +31,7 @@ function readFixtureKeys(enc, ...names) { module.exports = { fixturesDir, path: fixturesPath, + fileURL: fixturesFileURL, readSync: readFixtureSync, readKey: readFixtureKey, readKeys: readFixtureKeys, diff --git a/test/common/fixtures.mjs b/test/common/fixtures.mjs index 06564de6fa3bb9..d6f7f6c092aaa9 100644 --- a/test/common/fixtures.mjs +++ b/test/common/fixtures.mjs @@ -3,6 +3,7 @@ import fixtures from './fixtures.js'; const { fixturesDir, path, + fileURL, readSync, readKey, } = fixtures; @@ -10,6 +11,7 @@ const { export { fixturesDir, path, + fileURL, readSync, readKey, }; diff --git a/test/es-module/test-esm-export-not-found.mjs b/test/es-module/test-esm-export-not-found.mjs new file mode 100644 index 00000000000000..cdfe6df0fcde31 --- /dev/null +++ b/test/es-module/test-esm-export-not-found.mjs @@ -0,0 +1,39 @@ +import { mustCall } from '../common/index.mjs'; +import { path } from '../common/fixtures.mjs'; +import { match, notStrictEqual } from 'assert'; +import { spawn } from 'child_process'; +import { execPath } from 'process'; + +const importStatement = + 'import { foo, notfound } from \'./module-named-exports.mjs\';'; +const importStatementMultiline = `import { + foo, + notfound +} from './module-named-exports.mjs'; +`; + +[importStatement, importStatementMultiline].forEach((input) => { + const child = spawn(execPath, [ + '--input-type=module', + '--eval', + input, + ], { + cwd: path('es-module-loaders'), + }); + + let stderr = ''; + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { + stderr += data; + }); + child.on('close', mustCall((code, _signal) => { + notStrictEqual(code, 0); + + // SyntaxError: The requested module './module-named-exports.mjs' + // does not provide an export named 'notfound' + match(stderr, /SyntaxError:/); + // The quotes ensure that the path starts with ./ and not ../ + match(stderr, /'\.\/module-named-exports\.mjs'/); + match(stderr, /notfound/); + })); +}); diff --git a/test/es-module/test-esm-import-json-named-export.mjs b/test/es-module/test-esm-import-json-named-export.mjs new file mode 100644 index 00000000000000..f70b927329b6a6 --- /dev/null +++ b/test/es-module/test-esm-import-json-named-export.mjs @@ -0,0 +1,25 @@ +import { mustCall } from '../common/index.mjs'; +import { path } from '../common/fixtures.mjs'; +import { match, notStrictEqual } from 'assert'; +import { spawn } from 'child_process'; +import { execPath } from 'process'; + +const child = spawn(execPath, [ + '--experimental-json-modules', + path('es-modules', 'import-json-named-export.mjs'), +]); + +let stderr = ''; +child.stderr.setEncoding('utf8'); +child.stderr.on('data', (data) => { + stderr += data; +}); +child.on('close', mustCall((code, _signal) => { + notStrictEqual(code, 0); + + // SyntaxError: The requested module '../experimental.json' + // does not provide an export named 'ofLife' + match(stderr, /SyntaxError:/); + match(stderr, /'\.\.\/experimental\.json'/); + match(stderr, /'ofLife'/); +})); diff --git a/test/es-module/test-esm-loader-not-found.mjs b/test/es-module/test-esm-loader-not-found.mjs new file mode 100644 index 00000000000000..275f0b0f1e8515 --- /dev/null +++ b/test/es-module/test-esm-loader-not-found.mjs @@ -0,0 +1,27 @@ +import { mustCall } from '../common/index.mjs'; +import { path } from '../common/fixtures.mjs'; +import { match, ok, notStrictEqual } from 'assert'; +import { spawn } from 'child_process'; +import { execPath } from 'process'; + +const child = spawn(execPath, [ + '--experimental-loader', + 'i-dont-exist', + path('print-error-message.js'), +]); + +let stderr = ''; +child.stderr.setEncoding('utf8'); +child.stderr.on('data', (data) => { + stderr += data; +}); +child.on('close', mustCall((code, _signal) => { + notStrictEqual(code, 0); + + // Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'i-dont-exist' + // imported from + match(stderr, /ERR_MODULE_NOT_FOUND/); + match(stderr, /'i-dont-exist'/); + + ok(!stderr.includes('Bad command or file name')); +})); diff --git a/test/es-module/test-esm-loader-obsolete-hooks.mjs b/test/es-module/test-esm-loader-obsolete-hooks.mjs new file mode 100644 index 00000000000000..eff4104fc265ae --- /dev/null +++ b/test/es-module/test-esm-loader-obsolete-hooks.mjs @@ -0,0 +1,30 @@ +import { mustCall } from '../common/index.mjs'; +import { fileURL, path } from '../common/fixtures.mjs'; +import { match, notStrictEqual } from 'assert'; +import { spawn } from 'child_process'; +import { execPath } from 'process'; + +const child = spawn(execPath, [ + '--no-warnings', + '--throw-deprecation', + '--experimental-loader', + fileURL('es-module-loaders', 'hooks-obsolete.mjs').href, + path('print-error-message.js'), +]); + +let stderr = ''; +child.stderr.setEncoding('utf8'); +child.stderr.on('data', (data) => { + stderr += data; +}); +child.on('close', mustCall((code, _signal) => { + notStrictEqual(code, 0); + + // DeprecationWarning: Obsolete loader hook(s) supplied and will be ignored: + // dynamicInstantiate, getFormat, getSource, transformSource + match(stderr, /DeprecationWarning:/); + match(stderr, /dynamicInstantiate/); + match(stderr, /getFormat/); + match(stderr, /getSource/); + match(stderr, /transformSource/); +})); diff --git a/test/es-module/test-esm-loader-with-syntax-error.mjs b/test/es-module/test-esm-loader-with-syntax-error.mjs new file mode 100644 index 00000000000000..d973e72975e88f --- /dev/null +++ b/test/es-module/test-esm-loader-with-syntax-error.mjs @@ -0,0 +1,24 @@ +import { mustCall } from '../common/index.mjs'; +import { fileURL, path } from '../common/fixtures.mjs'; +import { match, ok, notStrictEqual } from 'assert'; +import { spawn } from 'child_process'; +import { execPath } from 'process'; + +const child = spawn(execPath, [ + '--experimental-loader', + fileURL('es-module-loaders', 'syntax-error.mjs').href, + path('print-error-message.js'), +]); + +let stderr = ''; +child.stderr.setEncoding('utf8'); +child.stderr.on('data', (data) => { + stderr += data; +}); +child.on('close', mustCall((code, _signal) => { + notStrictEqual(code, 0); + + match(stderr, /SyntaxError:/); + + ok(!stderr.includes('Bad command or file name')); +})); diff --git a/test/es-module/test-esm-module-not-found-commonjs-hint.mjs b/test/es-module/test-esm-module-not-found-commonjs-hint.mjs new file mode 100644 index 00000000000000..58f70d0b685391 --- /dev/null +++ b/test/es-module/test-esm-module-not-found-commonjs-hint.mjs @@ -0,0 +1,35 @@ +import { mustCall } from '../common/index.mjs'; +import { fixturesDir } from '../common/fixtures.mjs'; +import { match, notStrictEqual } from 'assert'; +import { spawn } from 'child_process'; +import { execPath } from 'process'; + +[ + { + input: 'import "./print-error-message"', + // Did you mean to import ../print-error-message.js? + expected: / \.\.\/print-error-message\.js\?/, + }, + { + input: 'import obj from "some_module/obj"', + expected: / some_module\/obj\.js\?/, + }, +].forEach(({ input, expected }) => { + const child = spawn(execPath, [ + '--input-type=module', + '--eval', + input, + ], { + cwd: fixturesDir, + }); + + let stderr = ''; + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { + stderr += data; + }); + child.on('close', mustCall((code, _signal) => { + notStrictEqual(code, 0); + match(stderr, expected); + })); +}); diff --git a/test/es-module/test-esm-syntax-error.mjs b/test/es-module/test-esm-syntax-error.mjs new file mode 100644 index 00000000000000..a8c019171717dd --- /dev/null +++ b/test/es-module/test-esm-syntax-error.mjs @@ -0,0 +1,19 @@ +import { mustCall } from '../common/index.mjs'; +import { path } from '../common/fixtures.mjs'; +import { match, notStrictEqual } from 'assert'; +import { spawn } from 'child_process'; +import { execPath } from 'process'; + +const child = spawn(execPath, [ + path('es-module-loaders', 'syntax-error.mjs'), +]); + +let stderr = ''; +child.stderr.setEncoding('utf8'); +child.stderr.on('data', (data) => { + stderr += data; +}); +child.on('close', mustCall((code, _signal) => { + notStrictEqual(code, 0); + match(stderr, /SyntaxError:/); +})); diff --git a/test/fixtures/es-module-loaders/syntax-error-import.mjs b/test/fixtures/es-module-loaders/syntax-error-import.mjs deleted file mode 100644 index 3a6bc5effc1940..00000000000000 --- a/test/fixtures/es-module-loaders/syntax-error-import.mjs +++ /dev/null @@ -1 +0,0 @@ -import { foo, notfound } from './module-named-exports.mjs'; diff --git a/test/fixtures/es-modules/import-json-named-export.mjs b/test/fixtures/es-modules/import-json-named-export.mjs new file mode 100644 index 00000000000000..f491e8c252d41a --- /dev/null +++ b/test/fixtures/es-modules/import-json-named-export.mjs @@ -0,0 +1,2 @@ +/* eslint-disable no-unused-vars */ +import { ofLife } from '../experimental.json' assert { type: 'json' }; diff --git a/test/fixtures/esm_loader_not_found_cjs_hint_bare.mjs b/test/fixtures/esm_loader_not_found_cjs_hint_bare.mjs deleted file mode 100644 index 4eb5f190af43e4..00000000000000 --- a/test/fixtures/esm_loader_not_found_cjs_hint_bare.mjs +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -import obj from 'some_module/obj'; - -throw new Error('Should have errored'); diff --git a/test/fixtures/print-error-message.js b/test/fixtures/print-error-message.js new file mode 100644 index 00000000000000..0650bfbd52bafa --- /dev/null +++ b/test/fixtures/print-error-message.js @@ -0,0 +1 @@ +console.error('Bad command or file name'); diff --git a/test/message/esm_display_syntax_error_import.mjs b/test/message/esm_display_syntax_error_import.mjs deleted file mode 100644 index 2173cb2b2e3a71..00000000000000 --- a/test/message/esm_display_syntax_error_import.mjs +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable no-unused-vars */ -import '../common/index.mjs'; -import { - foo, - notfound -} from '../fixtures/es-module-loaders/module-named-exports.mjs'; diff --git a/test/message/esm_display_syntax_error_import.out b/test/message/esm_display_syntax_error_import.out deleted file mode 100644 index 62607b93c7c593..00000000000000 --- a/test/message/esm_display_syntax_error_import.out +++ /dev/null @@ -1,10 +0,0 @@ -file:///*/test/message/esm_display_syntax_error_import.mjs:5 - notfound - ^^^^^^^^ -SyntaxError: The requested module '../fixtures/es-module-loaders/module-named-exports.mjs' does not provide an export named 'notfound' - at ModuleJob._instantiate (node:internal/modules/esm/module_job:*:*) - at async ModuleJob.run (node:internal/modules/esm/module_job:*:*) - at async Promise.all (index 0) - at async ESMLoader.import (node:internal/modules/esm/loader:*:*) - at async loadESM (node:internal/process/esm_loader:*:*) - at async handleMainPromise (node:internal/modules/run_main:*:*) diff --git a/test/message/esm_display_syntax_error_import_json_named_export.mjs b/test/message/esm_display_syntax_error_import_json_named_export.mjs deleted file mode 100644 index fc14340467856b..00000000000000 --- 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 249f27f7e5face..00000000000000 --- 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 c0345c44fb3fda..00000000000000 --- a/test/message/esm_display_syntax_error_import_module.mjs +++ /dev/null @@ -1,2 +0,0 @@ -import '../common/index.mjs'; -import '../fixtures/es-module-loaders/syntax-error-import.mjs'; diff --git a/test/message/esm_display_syntax_error_import_module.out b/test/message/esm_display_syntax_error_import_module.out deleted file mode 100644 index 0ecb87a952e891..00000000000000 --- a/test/message/esm_display_syntax_error_import_module.out +++ /dev/null @@ -1,10 +0,0 @@ -file:///*/test/fixtures/es-module-loaders/syntax-error-import.mjs:1 -import { foo, notfound } from './module-named-exports.mjs'; - ^^^^^^^^ -SyntaxError: The requested module './module-named-exports.mjs' does not provide an export named 'notfound' - at ModuleJob._instantiate (node:internal/modules/esm/module_job:*:*) - at async ModuleJob.run (node:internal/modules/esm/module_job:*:*) - at async Promise.all (index 0) - at async ESMLoader.import (node:internal/modules/esm/loader:*:*) - at async loadESM (node:internal/process/esm_loader:*:*) - at async handleMainPromise (node:internal/modules/run_main:*:*) diff --git a/test/message/esm_display_syntax_error_module.mjs b/test/message/esm_display_syntax_error_module.mjs deleted file mode 100644 index da40a4ead8d3c1..00000000000000 --- a/test/message/esm_display_syntax_error_module.mjs +++ /dev/null @@ -1,2 +0,0 @@ -import '../common/index.mjs'; -import '../fixtures/es-module-loaders/syntax-error.mjs'; diff --git a/test/message/esm_display_syntax_error_module.out b/test/message/esm_display_syntax_error_module.out deleted file mode 100644 index 26dbf480239b9c..00000000000000 --- a/test/message/esm_display_syntax_error_module.out +++ /dev/null @@ -1,7 +0,0 @@ -file:///*/test/fixtures/es-module-loaders/syntax-error.mjs:2 -await async () => 0; -^^^^^^^^^^^^^ - -SyntaxError: Malformed arrow function parameter list - at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:*:*) - at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) diff --git a/test/message/esm_import_assertion_failed.mjs b/test/message/esm_import_assertion_failed.mjs deleted file mode 100644 index 30ea65c3e34ee3..00000000000000 --- 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 545f8cc0620f40..00000000000000 --- 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 0b402d9e7ff90a..00000000000000 --- 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 0d25d948d84708..00000000000000 --- 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 86e594ce02ae5d..00000000000000 --- 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 c35ba5bfe07c69..00000000000000 --- 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 2b47e5a03ec9e6..00000000000000 --- a/test/message/esm_loader_not_found.mjs +++ /dev/null @@ -1,3 +0,0 @@ -// Flags: --experimental-loader i-dont-exist -import '../common/index.mjs'; -console.log('This should not be printed'); diff --git a/test/message/esm_loader_not_found.out b/test/message/esm_loader_not_found.out deleted file mode 100644 index 21d7ab3e44f205..00000000000000 --- a/test/message/esm_loader_not_found.out +++ /dev/null @@ -1,18 +0,0 @@ -(node:*) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time -(Use `* --trace-warnings ...` to show where the warning was created) -node:internal/errors:* - ErrorCaptureStackTrace(err); - ^ -Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'i-dont-exist' imported from * - at new NodeError (node:internal/errors:*:*) - at packageResolve (node:internal/modules/esm/resolve:*:*) - at moduleResolve (node:internal/modules/esm/resolve:*:*) - at defaultResolve (node:internal/modules/esm/resolve:*:*) - at ESMLoader.resolve (node:internal/modules/esm/loader:*:*) - at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*) - at ESMLoader.import (node:internal/modules/esm/loader:*:*) - at initializeLoader (node:internal/process/esm_loader:*:*) - at loadESM (node:internal/process/esm_loader:*:*) - at runMainESM (node:internal/modules/run_main:*:*) { - code: 'ERR_MODULE_NOT_FOUND' -} diff --git a/test/message/esm_loader_not_found_cjs_hint_bare.js b/test/message/esm_loader_not_found_cjs_hint_bare.js deleted file mode 100644 index 437fa2d3d430a1..00000000000000 --- a/test/message/esm_loader_not_found_cjs_hint_bare.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -require('../common'); -const { spawn } = require('child_process'); -const { join } = require('path'); -const { fixturesDir } = require('../common/fixtures'); - -spawn( - process.execPath, - [ - join(fixturesDir, 'esm_loader_not_found_cjs_hint_bare.mjs'), - ], - { - cwd: fixturesDir, - stdio: 'inherit' - } -); diff --git a/test/message/esm_loader_not_found_cjs_hint_bare.out b/test/message/esm_loader_not_found_cjs_hint_bare.out deleted file mode 100644 index 4a255ebf0b6972..00000000000000 --- a/test/message/esm_loader_not_found_cjs_hint_bare.out +++ /dev/null @@ -1,16 +0,0 @@ -node:internal/process/esm_loader:* - internalBinding('errors').triggerUncaughtException( - ^ - -Error [ERR_MODULE_NOT_FOUND]: Cannot find module '*test*fixtures*node_modules*some_module*obj' imported from *test*fixtures*esm_loader_not_found_cjs_hint_bare.mjs -Did you mean to import some_module/obj.js? - at new NodeError (node:internal/errors:*:*) - at finalizeResolution (node:internal/modules/esm/resolve:*:*) - at moduleResolve (node:internal/modules/esm/resolve:*:*) - at defaultResolve (node:internal/modules/esm/resolve:*:*) - at ESMLoader.resolve (node:internal/modules/esm/loader:*:*) - at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*) - at ModuleWrap. (node:internal/modules/esm/module_job:*:*) - at link (node:internal/modules/esm/module_job:*:*) { - code: 'ERR_MODULE_NOT_FOUND' -} diff --git a/test/message/esm_loader_not_found_cjs_hint_relative.mjs b/test/message/esm_loader_not_found_cjs_hint_relative.mjs deleted file mode 100644 index 928186318bb09a..00000000000000 --- a/test/message/esm_loader_not_found_cjs_hint_relative.mjs +++ /dev/null @@ -1,3 +0,0 @@ -// Flags: --experimental-loader ./test/common/fixtures -import '../common/index.mjs'; -console.log('This should not be printed'); diff --git a/test/message/esm_loader_not_found_cjs_hint_relative.out b/test/message/esm_loader_not_found_cjs_hint_relative.out deleted file mode 100644 index 817b4aa0724e8e..00000000000000 --- a/test/message/esm_loader_not_found_cjs_hint_relative.out +++ /dev/null @@ -1,20 +0,0 @@ -(node:*) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time -(Use `* --trace-warnings ...` to show where the warning was created) -node:internal/process/esm_loader:* - internalBinding('errors').triggerUncaughtException( - ^ - -Error [ERR_MODULE_NOT_FOUND]: Cannot find module '*test*common*fixtures' imported from * -Did you mean to import ./test/common/fixtures.js? - at new NodeError (node:internal/errors:*:*) - at finalizeResolution (node:internal/modules/esm/resolve:*:*) - at moduleResolve (node:internal/modules/esm/resolve:*:*) - at defaultResolve (node:internal/modules/esm/resolve:*:*) - at ESMLoader.resolve (node:internal/modules/esm/loader:*:*) - at ESMLoader.getModuleJob (node:internal/modules/esm/loader:*:*) - at ESMLoader.import (node:internal/modules/esm/loader:*:*) - at initializeLoader (node:internal/process/esm_loader:*:*) - at loadESM (node:internal/process/esm_loader:*:*) - at runMainESM (node:internal/modules/run_main:*:*) { - code: 'ERR_MODULE_NOT_FOUND' -} diff --git a/test/message/esm_loader_syntax_error.mjs b/test/message/esm_loader_syntax_error.mjs deleted file mode 100644 index 68cde42e585644..00000000000000 --- a/test/message/esm_loader_syntax_error.mjs +++ /dev/null @@ -1,3 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/syntax-error.mjs -import '../common/index.mjs'; -console.log('This should not be printed'); diff --git a/test/message/esm_loader_syntax_error.out b/test/message/esm_loader_syntax_error.out deleted file mode 100644 index fb8a2030828a9d..00000000000000 --- a/test/message/esm_loader_syntax_error.out +++ /dev/null @@ -1,10 +0,0 @@ -(node:*) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time -(Use `* --trace-warnings ...` to show where the warning was created) -file://*/test/fixtures/es-module-loaders/syntax-error.mjs:2 -await async () => 0; -^^^^^^^^^^^^^ - -SyntaxError: Malformed arrow function parameter list - at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:*:*) - at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) - at async link (node:internal/modules/esm/module_job:*:*) diff --git a/test/message/test-esm-loader-obsolete-hooks.mjs b/test/message/test-esm-loader-obsolete-hooks.mjs deleted file mode 100644 index 9a6a9c48057b40..00000000000000 --- a/test/message/test-esm-loader-obsolete-hooks.mjs +++ /dev/null @@ -1,4 +0,0 @@ -// Flags: --no-warnings --throw-deprecation --experimental-loader ./test/fixtures/es-module-loaders/hooks-obsolete.mjs -/* eslint-disable node-core/require-common-first, node-core/required-modules */ - -await import('whatever'); diff --git a/test/message/test-esm-loader-obsolete-hooks.out b/test/message/test-esm-loader-obsolete-hooks.out deleted file mode 100644 index 5099c9c8d8610b..00000000000000 --- a/test/message/test-esm-loader-obsolete-hooks.out +++ /dev/null @@ -1,10 +0,0 @@ -node:internal/process/warning:* - throw warning; - ^ - -DeprecationWarning: Obsolete loader hook(s) supplied and will be ignored: dynamicInstantiate, getFormat, getSource, transformSource - at Function.pluckHooks (node:internal/modules/esm/loader:*:*) - at ESMLoader.addCustomLoaders (node:internal/modules/esm/loader:*:*) - at initializeLoader (node:internal/process/esm_loader:*:*) - at async loadESM (node:internal/process/esm_loader:*:*) - at async handleMainPromise (node:internal/modules/run_main:*:*)