From 7da6afc25a998bfec0d38046c5830d43d0ecdbf8 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 24 Sep 2021 23:53:10 +0200 Subject: [PATCH] esm: add `--experimental-import-non-javascript-without-assertion` flag Restricts import of JSON modules to the assertion form only, unless the `--experimental-import-non-javascript-without-assertion` CLI flag is provided. --- doc/api/cli.md | 8 ++++++++ doc/api/errors.md | 9 +++++++++ lib/internal/errors.js | 3 +++ lib/internal/modules/esm/loader.js | 13 +++++++++++++ src/node_options.cc | 5 +++++ src/node_options.h | 1 + test/es-module/test-esm-data-urls.js | 2 +- test/es-module/test-esm-dynamic-import-assertion.js | 2 +- .../es-module/test-esm-dynamic-import-assertion.mjs | 2 +- test/es-module/test-esm-import-assertion-3.mjs | 2 +- test/es-module/test-esm-import-assertion-4.mjs | 2 +- test/es-module/test-esm-json-cache.mjs | 2 +- test/es-module/test-esm-json.mjs | 3 ++- test/message/esm_import_assertion_missing.mjs | 3 +++ test/message/esm_import_assertion_missing.out | 11 +++++++++++ 15 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 test/message/esm_import_assertion_missing.mjs create mode 100644 test/message/esm_import_assertion_missing.out diff --git a/doc/api/cli.md b/doc/api/cli.md index 9776a0f6efae7f..578f08d16f45e2 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -251,6 +251,14 @@ added: Enable experimental `import.meta.resolve()` support. +### `--experimental-import-non-javascript-without-assertion` + + +Enable experimental support importing non-JS modules without using an import +assertion. + ### `--experimental-json-modules` + +An attempt was made to impor without an assertion a module that requires a +specific import assertion to be loaded. + ### `ERR_MISSING_OPTION` diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 5801de21b50778..6ac58d4b85a370 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1400,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/esm/loader.js b/lib/internal/modules/esm/loader.js index dc3fcefb03cf19..04aa6f06c3ea6c 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -29,6 +29,7 @@ const { 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'); @@ -48,6 +49,9 @@ const { translators } = require( 'internal/modules/esm/translators'); const { getOptionValue } = require('internal/options'); +const experimentalAssertionlessNonJsImports = + getOptionValue('--experimental-import-non-javascript-without-assertion'); + const importAssertionTypeCache = new SafeWeakMap(); const finalFormatCache = new SafeWeakMap(); const supportedTypes = ObjectFreeze([undefined, 'json']); @@ -252,6 +256,11 @@ class ESMLoader { if (job !== undefined) { const finalFormat = finalFormatCache.get(job); + if (!experimentalAssertionlessNonJsImports && + import_assertions.type == null && finalFormat === 'json') { + throw new ERR_MISSING_IMPORT_ASSERTION(url, finalFormat, + 'type', 'json'); + } if ( import_assertions.type == null || (import_assertions.type === 'json' && finalFormat === 'json') @@ -268,6 +277,10 @@ class ESMLoader { throw new ERR_FAILED_IMPORT_ASSERTION( url, 'type', import_assertions.type, finalFormat); } + if (!experimentalAssertionlessNonJsImports && finalFormat === 'json') { + throw new ERR_MISSING_IMPORT_ASSERTION(url, finalFormat, + 'type', 'json'); + } finalFormatCache.set(job, finalFormat); const translator = translators.get(finalFormat); diff --git a/src/node_options.cc b/src/node_options.cc index c778de58dc8fa8..3382fa40640e86 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -315,6 +315,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "experimental JSON interop support for the ES Module loader", &EnvironmentOptions::experimental_json_modules, kAllowedInEnvironment); + AddOption("--experimental-import-non-javascript-without-assertion", + "experimental support for importing non-JS modules without using " + "an import assertion", + &EnvironmentOptions::experimental_assertionless_non_js_imports, + kAllowedInEnvironment); AddOption("--experimental-loader", "use the specified module as a custom loader", &EnvironmentOptions::userland_loader, diff --git a/src/node_options.h b/src/node_options.h index 02511d520802fc..987c6ba0aaf80e 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -104,6 +104,7 @@ class EnvironmentOptions : public Options { std::string dns_result_order; bool enable_source_maps = false; bool experimental_json_modules = false; + bool experimental_assertionless_non_js_imports = false; bool experimental_modules = false; std::string experimental_specifier_resolution; bool experimental_wasm_modules = false; diff --git a/test/es-module/test-esm-data-urls.js b/test/es-module/test-esm-data-urls.js index 78cd01b4d55a12..5b1d03cd0cf6e8 100644 --- a/test/es-module/test-esm-data-urls.js +++ b/test/es-module/test-esm-data-urls.js @@ -1,4 +1,4 @@ -// Flags: --experimental-json-modules +// Flags: --experimental-json-modules --experimental-import-non-javascript-without-assertion 'use strict'; const common = require('../common'); const assert = require('assert'); diff --git a/test/es-module/test-esm-dynamic-import-assertion.js b/test/es-module/test-esm-dynamic-import-assertion.js index c5555336b91413..aaf363abbd828a 100644 --- a/test/es-module/test-esm-dynamic-import-assertion.js +++ b/test/es-module/test-esm-dynamic-import-assertion.js @@ -1,4 +1,4 @@ -// Flags: --experimental-json-modules +// Flags: --experimental-json-modules --experimental-import-non-javascript-without-assertion 'use strict'; const common = require('../common'); const { rejects, strictEqual } = require('assert'); diff --git a/test/es-module/test-esm-dynamic-import-assertion.mjs b/test/es-module/test-esm-dynamic-import-assertion.mjs index f509042b8a46f1..aac654298d762b 100644 --- a/test/es-module/test-esm-dynamic-import-assertion.mjs +++ b/test/es-module/test-esm-dynamic-import-assertion.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-json-modules +// Flags: --experimental-json-modules --experimental-import-non-javascript-without-assertion import '../common/index.mjs'; import { rejects, strictEqual } from 'assert'; diff --git a/test/es-module/test-esm-import-assertion-3.mjs b/test/es-module/test-esm-import-assertion-3.mjs index 79d6cf05a1d1e6..6d9408b4999e1a 100644 --- a/test/es-module/test-esm-import-assertion-3.mjs +++ b/test/es-module/test-esm-import-assertion-3.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-json-modules +// Flags: --experimental-json-modules --experimental-import-non-javascript-without-assertion import '../common/index.mjs'; import { strictEqual } from 'assert'; diff --git a/test/es-module/test-esm-import-assertion-4.mjs b/test/es-module/test-esm-import-assertion-4.mjs index b7400bca9d42f9..351173226c6ac4 100644 --- a/test/es-module/test-esm-import-assertion-4.mjs +++ b/test/es-module/test-esm-import-assertion-4.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-json-modules +// Flags: --experimental-json-modules --experimental-import-non-javascript-without-assertion import '../common/index.mjs'; import { strictEqual } from 'assert'; diff --git a/test/es-module/test-esm-json-cache.mjs b/test/es-module/test-esm-json-cache.mjs index 68ea832ab69585..3a316c55ce58c3 100644 --- a/test/es-module/test-esm-json-cache.mjs +++ b/test/es-module/test-esm-json-cache.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-json-modules +// Flags: --experimental-json-modules --experimental-import-non-javascript-without-assertion import '../common/index.mjs'; import { strictEqual, deepStrictEqual } from 'assert'; diff --git a/test/es-module/test-esm-json.mjs b/test/es-module/test-esm-json.mjs index df4f75fbd6e067..12c2c4afc050c4 100644 --- a/test/es-module/test-esm-json.mjs +++ b/test/es-module/test-esm-json.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-json-modules +// Flags: --experimental-json-modules --experimental-import-non-javascript-without-assertion import '../common/index.mjs'; import { path } from '../common/fixtures.mjs'; import { strictEqual, ok } from 'assert'; @@ -11,6 +11,7 @@ strictEqual(secret.ofLife, 42); // Test warning message const child = spawn(process.execPath, [ '--experimental-json-modules', + '--experimental-import-non-javascript-without-assertion', path('/es-modules/json-modules.mjs'), ]); 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