diff --git a/doc/api/errors.md b/doc/api/errors.md index b0b0753f400401..0496d5f52d718e 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1111,6 +1111,14 @@ The JS execution context is not associated with a Node.js environment. This may occur when Node.js is used as an embedded library and some hooks for the JS engine are not set up properly. + +### `ERR_FAILED_IMPORT_ASSERTION` + + +An import assertion has failed, preventing the specified module to be imported. + ### `ERR_FALSY_VALUE_REJECTION` @@ -1662,6 +1670,14 @@ for more information. An invalid HTTP token was supplied. + +### `ERR_INVALID_IMPORT_ASSERTION` + + +An import assertion is not supported by this version of Node.js. + ### `ERR_INVALID_IP_ADDRESS` @@ -1913,6 +1929,15 @@ strict compliance with the API specification (which in some cases may accept `func(undefined)` and `func()` are treated identically, and the [`ERR_INVALID_ARG_TYPE`][] error code may be used instead. + +### `ERR_MISSING_IMPORT_ASSERTION` + + +An attempt was made to import a module without an assertion that requires +a specific import assertion to be loaded. + ### `ERR_MISSING_OPTION` diff --git a/doc/api/esm.md b/doc/api/esm.md index 9ec67330000907..f4662efd85499d 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 json from './foo.json' assert { type: "json" }; +await import('foo.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 @@ -522,9 +544,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 @@ -1355,6 +1376,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 755f1b2b86176d..418b4d747146ad 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -953,6 +953,9 @@ E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported', RangeError); E('ERR_EVAL_ESM_CANNOT_PRINT', '--print cannot be used with ESM input', Error); E('ERR_EVENT_RECURSION', 'The event "%s" is already being dispatched', Error); +E('ERR_FAILED_IMPORT_ASSERTION', (request, key, expectedValue, actualValue) => { + return `Failed to load module "${request}", expected ${key} to be ${JSONStringify(expectedValue)}, got ${JSONStringify(actualValue)} instead`; +}, TypeError); E('ERR_FALSY_VALUE_REJECTION', function(reason) { this.reason = reason; return 'Promise was rejected with falsy value'; @@ -1250,6 +1253,9 @@ E('ERR_INVALID_FILE_URL_HOST', E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s', TypeError); E('ERR_INVALID_HANDLE_TYPE', 'This handle type cannot be sent', TypeError); E('ERR_INVALID_HTTP_TOKEN', '%s must be a valid HTTP token ["%s"]', TypeError); +E('ERR_INVALID_IMPORT_ASSERTION', + (type, value) => `Invalid ${JSONStringify(type)} import assertion: ${JSONStringify(value)}`, + TypeError); E('ERR_INVALID_IP_ADDRESS', 'Invalid IP address: %s', TypeError); E('ERR_INVALID_MODULE_SPECIFIER', (request, reason, base = undefined) => { return `Invalid module "${request}" ${reason}${base ? @@ -1394,6 +1400,9 @@ E('ERR_MISSING_ARGS', } return `${msg} must be specified`; }, TypeError); +E('ERR_MISSING_IMPORT_ASSERTION', + 'Failed to load %s: Node.js requires modules of format "%s" to be loaded ' + + 'using an assertion "%s" with value "%s"', TypeError); E('ERR_MISSING_OPTION', '%s is required', TypeError); E('ERR_MODULE_NOT_FOUND', (path, base, type = 'package') => { return `Cannot find ${type} '${path}' imported from ${base}`; diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 622805ea78fd0c..e0f40ffa2ecf50 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -1015,9 +1015,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); }, }); } @@ -1030,9 +1031,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/loader.js b/lib/internal/modules/esm/loader.js index b12a87a9021242..4c79a0c8b124b4 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -6,11 +6,13 @@ require('internal/modules/cjs/loader'); const { Array, ArrayIsArray, + ArrayPrototypeIncludes, ArrayPrototypeJoin, ArrayPrototypePush, FunctionPrototypeBind, FunctionPrototypeCall, ObjectCreate, + ObjectFreeze, ObjectSetPrototypeOf, PromiseAll, RegExpPrototypeExec, @@ -20,11 +22,14 @@ const { } = primordials; const { + ERR_FAILED_IMPORT_ASSERTION, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, + ERR_INVALID_IMPORT_ASSERTION, ERR_INVALID_MODULE_SPECIFIER, ERR_INVALID_RETURN_PROPERTY_VALUE, ERR_INVALID_RETURN_VALUE, + ERR_MISSING_IMPORT_ASSERTION, ERR_UNKNOWN_MODULE_FORMAT } = require('internal/errors').codes; const { pathToFileURL, isURLInstance } = require('internal/url'); @@ -44,6 +49,10 @@ const { translators } = require( 'internal/modules/esm/translators'); const { getOptionValue } = require('internal/options'); +const importAssertionTypeCache = new SafeWeakMap(); +const finalFormatCache = new SafeWeakMap(); +const supportedTypes = ObjectFreeze([undefined, 'json']); + /** * An ESMLoader instance is used as the main entry point for loading ES modules. * Currently, this is a singleton -- there is only one used for loading @@ -202,8 +211,8 @@ 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); } }); @@ -211,6 +220,7 @@ class ESMLoader { }; const job = new ModuleJob(this, url, evalInstance, false, false); this.moduleMap.set(url, job); + finalFormatCache.set(job, 'module'); const { module } = await job.run(); return { @@ -218,17 +228,57 @@ class ESMLoader { }; } - async getModuleJob(specifier, parentURL) { + async getModuleJob(specifier, parentURL, importAssertions) { + if (!ArrayPrototypeIncludes(supportedTypes, importAssertions.type)) { + throw new ERR_INVALID_IMPORT_ASSERTION('type', importAssertions.type); + } + const { format, url } = await this.resolve(specifier, parentURL); let job = this.moduleMap.get(url); // CommonJS will set functions for lazy job evaluation. if (typeof job === 'function') this.moduleMap.set(url, job = job()); - if (job !== undefined) return job; + if (job != null) { + const currentImportAssertionType = importAssertionTypeCache.get(job); + if (currentImportAssertionType === importAssertions.type) return job; + + try { + // To avoid race conditions, wait for previous module to fulfill first. + await job.modulePromise; + } catch { + // If the other job failed with a different `type` assertion, we got + // another chance. + job = undefined; + } + + if (job !== undefined) { + const finalFormat = finalFormatCache.get(job); + if (importAssertions.type == null && finalFormat === 'json') { + throw new ERR_MISSING_IMPORT_ASSERTION(url, finalFormat, + 'type', 'json'); + } + if ( + importAssertions.type == null || + (importAssertions.type === 'json' && finalFormat === 'json') + ) return job; + throw new ERR_FAILED_IMPORT_ASSERTION( + url, 'type', importAssertions.type, finalFormat); + } + } const moduleProvider = async (url, isMain) => { const { format: finalFormat, source } = await this.load(url, { format }); + if (importAssertions.type === 'json' && finalFormat !== 'json') { + throw new ERR_FAILED_IMPORT_ASSERTION( + url, 'type', importAssertions.type, finalFormat); + } + if (importAssertions.type !== 'json' && finalFormat === 'json') { + throw new ERR_MISSING_IMPORT_ASSERTION(url, finalFormat, + 'type', 'json'); + } + finalFormatCache.set(job, finalFormat); + const translator = translators.get(finalFormat); if (!translator) throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat); @@ -249,6 +299,7 @@ class ESMLoader { inspectBrk ); + importAssertionTypeCache.set(job, importAssertions.type); this.moduleMap.set(url, job); return job; @@ -262,10 +313,11 @@ class ESMLoader { * loader module. * * @param {string | string[]} specifiers Path(s) to the module - * @param {string} [parentURL] Path of the parent importing the module - * @returns {object | object[]} A list of module export(s) + * @param {string} parentURL Path of the parent importing the module + * @param {Record>} importAssertions + * @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 +325,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()); } diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 2f699376d6eaea..c67b816657715a 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -72,8 +72,8 @@ class ModuleJob { // so that circular dependencies can't cause a deadlock by two of // these `link` callbacks depending on each other. const dependencyJobs = []; - const promises = this.module.link(async (specifier) => { - const jobPromise = this.loader.getModuleJob(specifier, url); + const promises = this.module.link(async (specifier, assertions) => { + const jobPromise = this.loader.getModuleJob(specifier, url, assertions); ArrayPrototypePush(dependencyJobs, jobPromise); const job = await jobPromise; return job.modulePromise; 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/esm_loader.js b/lib/internal/process/esm_loader.js index 0f04ca97751ef6..73385a85b4e106 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -1,5 +1,9 @@ 'use strict'; +const { + ObjectCreate, +} = primordials; + const { ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, } = require('internal/errors').codes; @@ -22,13 +26,14 @@ exports.initializeImportMetaObject = function(wrap, meta) { } }; -exports.importModuleDynamicallyCallback = async function(wrap, specifier) { +exports.importModuleDynamicallyCallback = +async function importModuleDynamicallyCallback(wrap, specifier, assertions) { const { callbackMap } = internalBinding('module_wrap'); if (callbackMap.has(wrap)) { const { importModuleDynamically } = callbackMap.get(wrap); if (importModuleDynamically !== undefined) { return importModuleDynamically( - specifier, getModuleFromWrap(wrap) || wrap); + specifier, getModuleFromWrap(wrap) || wrap, assertions); } } throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING(); @@ -69,6 +74,7 @@ async function initializeLoader() { const exports = await internalEsmLoader.import( customLoaders, pathToFileURL(cwd).href, + ObjectCreate(null), ); // Hooks must then be added to external/public loader diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index 48c525057f7477..f28baeb538a528 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -82,9 +82,9 @@ function evalScript(name, body, breakFirstLine, print) { filename: name, displayErrors: true, [kVmBreakFirstLineSymbol]: !!breakFirstLine, - async importModuleDynamically(specifier) { - const loader = await asyncESM.esmLoader; - return loader.import(specifier, baseUrl); + importModuleDynamically(specifier, _, 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/module_wrap.cc b/src/module_wrap.cc index f45ee7627b0a9b..a9defba217626d 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -602,9 +602,20 @@ static MaybeLocal ImportModuleDynamically( UNREACHABLE(); } + Local assertions = + Object::New(isolate, v8::Null(env->isolate()), nullptr, nullptr, 0); + for (int i = 0; i < import_assertions->Length(); i += 2) { + assertions + ->Set(env->context(), + Local::Cast(import_assertions->Get(env->context(), i)), + Local::Cast(import_assertions->Get(env->context(), i + 1))) + .ToChecked(); + } + Local import_args[] = { object, Local(specifier), + assertions, }; Local result; diff --git a/src/node.cc b/src/node.cc index acf4f0fac03c0b..19de2d6e68a411 100644 --- a/src/node.cc +++ b/src/node.cc @@ -803,11 +803,11 @@ int ProcessGlobalArgs(std::vector* args, return 12; } - // TODO(mylesborins): remove this when the harmony-top-level-await flag + // TODO(mylesborins): remove this when the harmony-import-assertions flag // is removed in V8 if (std::find(v8_args.begin(), v8_args.end(), - "--no-harmony-top-level-await") == v8_args.end()) { - v8_args.push_back("--harmony-top-level-await"); + "--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; 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..92f53a3b8bdd3d --- /dev/null +++ b/test/es-module/test-esm-dynamic-import-assertion.js @@ -0,0 +1,50 @@ +// Flags: --experimental-json-modules +'use strict'; +const common = require('../common'); +const { rejects, strictEqual } = require('assert'); + +const jsModuleDataUrl = 'data:text/javascript,export{}'; + +async function test() { + await rejects( + import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`), + { code: 'ERR_FAILED_IMPORT_ASSERTION' } + ); + + await rejects( + import(jsModuleDataUrl, { assert: { type: 'json' } }), + { code: 'ERR_FAILED_IMPORT_ASSERTION' } + ); + + await rejects( + import('specifier', { assert: { type: 'unsupported' } }), + { code: 'ERR_INVALID_IMPORT_ASSERTION' } + ); + + await rejects( + import('specifier', { assert: { type: 'undefined' } }), + { code: 'ERR_INVALID_IMPORT_ASSERTION' } + ); + + { + 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'); + } +} + +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..7cbbd2235edc67 --- /dev/null +++ b/test/es-module/test-esm-dynamic-import-assertion.mjs @@ -0,0 +1,45 @@ +// Flags: --experimental-json-modules +import '../common/index.mjs'; +import { rejects, strictEqual } from 'assert'; + +const jsModuleDataUrl = 'data:text/javascript,export{}'; + +await rejects( + import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`), + { code: 'ERR_FAILED_IMPORT_ASSERTION' } +); + +await rejects( + import(jsModuleDataUrl, { assert: { type: 'json' } }), + { code: 'ERR_FAILED_IMPORT_ASSERTION' } +); + +await rejects( + import('specifier', { assert: { type: 'unsupported' } }), + { code: 'ERR_INVALID_IMPORT_ASSERTION' } +); + +await rejects( + import('specifier', { assert: { type: 'undefined' } }), + { code: 'ERR_INVALID_IMPORT_ASSERTION' } +); + +{ + 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'); +} 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.mjs b/test/es-module/test-esm-import-assertion.mjs new file mode 100644 index 00000000000000..f011c948d8edea --- /dev/null +++ b/test/es-module/test-esm-import-assertion.mjs @@ -0,0 +1,7 @@ +// Flags: --experimental-json-modules +import '../common/index.mjs'; +import { strictEqual } from 'assert'; + +import secret from '../fixtures/experimental.json' assert { type: 'json' }; + +strictEqual(secret.ofLife, 42); 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/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_import_assertion_failing.mjs b/test/message/esm_import_assertion_failing.mjs new file mode 100644 index 00000000000000..30ea65c3e34ee3 --- /dev/null +++ b/test/message/esm_import_assertion_failing.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_failing.out b/test/message/esm_import_assertion_failing.out new file mode 100644 index 00000000000000..32024344e45581 --- /dev/null +++ b/test/message/esm_import_assertion_failing.out @@ -0,0 +1,10 @@ +node:internal/errors:* + ErrorCaptureStackTrace(err); + ^ + +TypeError [ERR_FAILED_IMPORT_ASSERTION]: Failed to load module "data:text/javascript,export{}", expected type to be "json", got "module" instead + at new NodeError (node:internal/errors:*:*) + at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) { + code: 'ERR_FAILED_IMPORT_ASSERTION' +} +Node.js * 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..ffd8c658fcd617 --- /dev/null +++ b/test/message/esm_import_assertion_missing.out @@ -0,0 +1,11 @@ +node:internal/errors:* + ErrorCaptureStackTrace(err); + ^ + +TypeError [ERR_MISSING_IMPORT_ASSERTION]: Failed to load data:application/json,{}: Node.js requires modules of format "json" to be loaded using an assertion "type" with value "json" + at new NodeError (node:internal/errors:*:*) + at ESMLoader.moduleProvider (node:internal/modules/esm/loader:*:*) { + code: 'ERR_MISSING_IMPORT_ASSERTION' +} + +Node.js * \ No newline at end of file diff --git a/test/message/esm_import_assertion_unsupported.mjs b/test/message/esm_import_assertion_unsupported.mjs new file mode 100644 index 00000000000000..4876aa263d1786 --- /dev/null +++ b/test/message/esm_import_assertion_unsupported.mjs @@ -0,0 +1,2 @@ +import '../common/index.mjs'; +import 'specifier' 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..65579f4dc7cbcd --- /dev/null +++ b/test/message/esm_import_assertion_unsupported.out @@ -0,0 +1,13 @@ +node:internal/errors:* + ErrorCaptureStackTrace(err); + ^ + +TypeError [ERR_INVALID_IMPORT_ASSERTION]: Invalid "type" import assertion: "unsupported" + at new NodeError (node:internal/errors:*:*) + 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_INVALID_IMPORT_ASSERTION' +} + +Node.js * 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 3a1d0ee50e5078..babf8535dbb3e7 100644 --- a/tools/code_cache/mkcodecache.cc +++ b/tools/code_cache/mkcodecache.cc @@ -28,7 +28,7 @@ int main(int argc, char* argv[]) { #endif // _WIN32 v8::V8::SetFlagsFromString("--random_seed=42"); - v8::V8::SetFlagsFromString("--harmony-top-level-await"); + v8::V8::SetFlagsFromString("--harmony-import-assertions"); if (argc < 2) { std::cerr << "Usage: " << argv[0] << " \n";