From c7b6685a8cdd35bbf20e62a9fe95430c66180d27 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Fri, 15 Sep 2023 20:27:04 -0700 Subject: [PATCH 01/24] esm: --experimental-default-type flag to flip module defaults --- doc/api/cli.md | 28 ++++++++++++++++++++++++++++ doc/api/esm.md | 10 ++++++---- doc/api/packages.md | 42 +++++++++++++++++++++++++++++------------- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 013256d776bac7..0450600b61fcb0 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -701,6 +701,34 @@ generated as part of the test runner output. If no tests are run, a coverage report is not generated. See the documentation on [collecting code coverage from tests][] for more details. +### `--experimental-type=type` + + + +> Stability: 1.0 - Early development + +Define which module system, `module` or `commonjs`, to use for the following: + +* String input provided via `--eval` or STDIN, if `--input-type` is unspecified. + +* Files ending in `.js` or with no extension, if there is no `package.json` file + present in the same folder or any parent folder. + +* Files ending in `.js` or with no extension, if the nearest parent + `package.json` field lacks a `"type"` field; unless the folder is inside a + `node_modules` folder. + +In other words, `--experimental-type=module` flips all the places where Node.js +currently defaults to CommonJS to instead default to ECMAScript modules, with +the exception of packages inside `node_modules`. + +Under `--experimental-type=module` and `--experimental-wasm-modules`, files with +no extension will be treated as WebAssembly if they begin with the WebAssembly +magic number (`\0asm`); otherwise they will be treated as ES module JavaScript. + ### `--experimental-vm-modules` @@ -1059,6 +1060,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. [URL]: https://url.spec.whatwg.org/ [`"exports"`]: packages.md#exports [`"type"`]: packages.md#type +[`--experimental-type`]: cli.md#--experimental-typetype [`--input-type`]: cli.md#--input-typetype [`data:` URLs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs [`export`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export diff --git a/doc/api/packages.md b/doc/api/packages.md index a42e1e041a00b6..ef0b8bf8f1ad1d 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -55,6 +55,8 @@ along with a reference for the [`package.json`][] fields defined by Node.js. ## Determining module system +### Introduction + Node.js will treat the following as [ES modules][] when passed to `node` as the initial input, or when referenced by `import` statements or `import()` expressions: @@ -67,14 +69,9 @@ expressions: * Strings passed in as an argument to `--eval`, or piped to `node` via `STDIN`, with the flag `--input-type=module`. -Node.js will treat as [CommonJS][] all other forms of input, such as `.js` files -where the nearest parent `package.json` file contains no top-level `"type"` -field, or string input without the flag `--input-type`. This behavior is to -preserve backward compatibility. However, now that Node.js supports both -CommonJS and ES modules, it is best to be explicit whenever possible. Node.js -will treat the following as CommonJS when passed to `node` as the initial input, -or when referenced by `import` statements, `import()` expressions, or -`require()` expressions: +Node.js will treat the following as [CommonJS][] when passed to `node` as the +initial input, or when referenced by `import` statements or `import()` +expressions: * Files with a `.cjs` extension. @@ -84,11 +81,29 @@ or when referenced by `import` statements, `import()` expressions, or * Strings passed in as an argument to `--eval` or `--print`, or piped to `node` via `STDIN`, with the flag `--input-type=commonjs`. -Package authors should include the [`"type"`][] field, even in packages where -all sources are CommonJS. Being explicit about the `type` of the package will -future-proof the package in case the default type of Node.js ever changes, and -it will also make things easier for build tools and loaders to determine how the -files in the package should be interpreted. +Aside from these explicit cases, there are other cases where Node.js defaults to +one module system or the other based on the value of the +[`--experimental-type`][] flag: + +* Files ending in `.js` or with no extension, if there is no `package.json` file + present in the same folder or any parent folder. + +* Files ending in `.js` or with no extension, if the nearest parent + `package.json` field lacks a `"type"` field; unless the folder is inside a + `node_modules` folder. (Package scopes under `node_modules` are always treated + as CommonJS when the `package.json` file lacks a `"type"` field, regardless + of `--experimental-type`, for backward compatibility.) + +* Strings passed in as an argument to `--eval` or piped to `node` via `STDIN`. + +This flag currently defaults to `"commonjs"`, but it may change in the future to +default to `"module"`. For this reason it is best to be explicit wherever +possible; in particular, package authors should always include the [`"type"`][] +field in their `package.json` files, even in packages where all sources are +CommonJS. Being explicit about the `type` of the package will future-proof the +package in case the default type of Node.js ever changes, and it will also make +things easier for build tools and loaders to determine how the files in the +package should be interpreted. ### Modules loaders @@ -1337,6 +1352,7 @@ This field defines [subpath imports][] for the current package. [`"packageManager"`]: #packagemanager [`"type"`]: #type [`--conditions` / `-C` flag]: #resolving-user-conditions +[`--experimental-type`]: cli.md#--experimental-typetype [`--no-addons` flag]: cli.md#--no-addons [`ERR_PACKAGE_PATH_NOT_EXPORTED`]: errors.md#err_package_path_not_exported [`esm`]: https://github.com/standard-things/esm#readme From e2bfe8a1cb1cb54360e60f1db49e94c1fc479424 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Fri, 15 Sep 2023 22:27:10 -0700 Subject: [PATCH 02/24] add flag, tests --- doc/api/cli.md | 1 + doc/node.1 | 5 ++ lib/internal/modules/esm/get_format.js | 3 +- src/node_options.cc | 4 ++ test/parallel/test-esm-no-extension.mjs | 26 +++++++ test/parallel/test-esm-unknown-main.mjs | 92 +++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-esm-no-extension.mjs create mode 100644 test/parallel/test-esm-unknown-main.mjs diff --git a/doc/api/cli.md b/doc/api/cli.md index 0450600b61fcb0..a32ef5044316b6 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -2281,6 +2281,7 @@ Node.js options that are allowed are: * `--experimental-shadow-realm` * `--experimental-specifier-resolution` * `--experimental-top-level-await` +* `--experimental-type` * `--experimental-vm-modules` * `--experimental-wasi-unstable-preview1` * `--experimental-wasm-modules` diff --git a/doc/node.1 b/doc/node.1 index dd10330d4f567a..caa6eaf6c74fc3 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -178,6 +178,11 @@ Use this flag to enable ShadowRealm support. .It Fl -experimental-test-coverage Enable code coverage in the test runner. . +.It Fl -experimental-type Ns = Ns Ar type +Interpret as either ES modules or CommonJS modules input via --eval, --print or STDIN; +.js or extensionless files with no sibling or parent package.json; +.js or extensionless files whose nearest parent package.json lacks a "type" field, unless under node_modules. +. .It Fl -experimental-websocket Enable experimental support for the WebSocket API. . diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index b3c8a56c06c1cc..8c99648b111228 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -16,6 +16,7 @@ const { const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); +const defaultType = getOptionValue('--experimental-type'); const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve'); const { fileURLToPath } = require('internal/url'); const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; @@ -74,7 +75,7 @@ function extname(url) { */ function getFileProtocolModuleFormat(url, context, ignoreErrors) { const ext = extname(url); - if (ext === '.js') { + if (ext === '.js' || (defaultType === 'module' && ext === '')) { return getPackageType(url) === 'module' ? 'module' : 'commonjs'; } diff --git a/src/node_options.cc b/src/node_options.cc index 0285f422dfd62c..dfb7e23c649829 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -646,6 +646,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "show stack traces on process warnings", &EnvironmentOptions::trace_warnings, kAllowedInEnvvar); + AddOption("--experimental-type", + "set module system to use by default", + &EnvironmentOptions::module_type, + kAllowedInEnvvar); AddOption("--extra-info-on-fatal-exception", "hide extra information on fatal exception that causes exit", &EnvironmentOptions::extra_info_on_fatal_exception, diff --git a/test/parallel/test-esm-no-extension.mjs b/test/parallel/test-esm-no-extension.mjs new file mode 100644 index 00000000000000..025306ab41ea93 --- /dev/null +++ b/test/parallel/test-esm-no-extension.mjs @@ -0,0 +1,26 @@ +// Flags: --experimental-extensionless-modules +import * as common from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { spawn } from 'node:child_process'; +import assert from 'node:assert'; + +const entry = fixtures.path('/es-modules/package-type-module/noext-esm'); + +// Run a module that does not have extension. +// This is to ensure that "type": "module" applies to extensionless files. + +const child = spawn(process.execPath, [ + '--experimental-extensionless-modules', + entry, +]); + +let stdout = ''; +child.stdout.setEncoding('utf8'); +child.stdout.on('data', (data) => { + stdout += data; +}); +child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + assert.strictEqual(stdout, 'executed\n'); +})); diff --git a/test/parallel/test-esm-unknown-main.mjs b/test/parallel/test-esm-unknown-main.mjs new file mode 100644 index 00000000000000..5adc9ec582831f --- /dev/null +++ b/test/parallel/test-esm-unknown-main.mjs @@ -0,0 +1,92 @@ +// Flags: --experimental-extensionless-modules +import * as common from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { spawn } from 'node:child_process'; +import assert from 'node:assert'; + +{ + const entry = fixtures.path( + '/es-modules/package-type-module/extension.unknown' + ); + const child = spawn(process.execPath, [ + '--experimental-extensionless-modules', + entry, + ]); + let stdout = ''; + let stderr = ''; + child.stderr.setEncoding('utf8'); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + stdout += data; + }); + child.stderr.on('data', (data) => { + stderr += data; + }); + child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + assert.strictEqual(stdout, ''); + assert.ok(stderr.indexOf('ERR_UNKNOWN_FILE_EXTENSION') !== -1); + })); +} +{ + const entry = fixtures.path( + '/es-modules/package-type-module/imports-unknownext.mjs' + ); + const child = spawn(process.execPath, [ + '--experimental-extensionless-modules', + entry, + ]); + let stdout = ''; + let stderr = ''; + child.stderr.setEncoding('utf8'); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + stdout += data; + }); + child.stderr.on('data', (data) => { + stderr += data; + }); + child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + assert.strictEqual(stdout, ''); + assert.ok(stderr.indexOf('ERR_UNKNOWN_FILE_EXTENSION') !== -1); + })); +} +{ + const entry = fixtures.path('/es-modules/package-type-module/noext-esm'); + const child = spawn(process.execPath, [ + '--experimental-extensionless-modules', + entry, + ]); + let stdout = ''; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + stdout += data; + }); + child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + assert.strictEqual(stdout, 'executed\n'); + })); +} +{ + const entry = fixtures.path( + '/es-modules/package-type-module/imports-noext.mjs' + ); + const child = spawn(process.execPath, [ + '--experimental-extensionless-modules', + entry, + ]); + let stdout = ''; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + stdout += data; + }); + child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + assert.strictEqual(stdout, 'executed\n'); + })); +} From d51cabce375bc7f6d16aa83dbc5e57184b6be601 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 16 Sep 2023 20:35:50 -0700 Subject: [PATCH 03/24] Rename files --- ...test-esm-type-flag-errors.js => test-esm-type-field-errors.js} | 0 .../es-module/{test-esm-type-flag.mjs => test-esm-type-field.mjs} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/es-module/{test-esm-type-flag-errors.js => test-esm-type-field-errors.js} (100%) rename test/es-module/{test-esm-type-flag.mjs => test-esm-type-field.mjs} (100%) diff --git a/test/es-module/test-esm-type-flag-errors.js b/test/es-module/test-esm-type-field-errors.js similarity index 100% rename from test/es-module/test-esm-type-flag-errors.js rename to test/es-module/test-esm-type-field-errors.js diff --git a/test/es-module/test-esm-type-flag.mjs b/test/es-module/test-esm-type-field.mjs similarity index 100% rename from test/es-module/test-esm-type-flag.mjs rename to test/es-module/test-esm-type-field.mjs From e253231cb2fcd78a552187a13b1bea6fab28c3a9 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Sep 2023 09:59:16 -0700 Subject: [PATCH 04/24] Modernize, add to tests --- ...-esm-type-flag-unknown-or-no-extension.mjs | 79 ++++++++++++++++ test/es-module/test-esm-unknown-main.mjs | 92 +++++++++++++++++++ test/fixtures/es-modules/imports-noext.mjs | 1 + test/fixtures/es-modules/noext-esm | 2 + 4 files changed, 174 insertions(+) create mode 100644 test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs create mode 100644 test/es-module/test-esm-unknown-main.mjs create mode 100644 test/fixtures/es-modules/imports-noext.mjs create mode 100644 test/fixtures/es-modules/noext-esm diff --git a/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs b/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs new file mode 100644 index 00000000000000..8e53d600f01e9d --- /dev/null +++ b/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs @@ -0,0 +1,79 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { describe, it } from 'node:test'; +import assert from 'node:assert'; + +describe('--experimental-type=module', { concurrency: true }, () => { + it('should run an extensionless module that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/noext-esm'), + ]); + + assert.strictEqual(stderr, ''); + assert.strictEqual(stdout, 'executed\n'); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + }); + + it('should run an extensionless module within a "type": "module" scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/noext-esm'), + ]); + + assert.strictEqual(stderr, ''); + assert.strictEqual(stdout, 'executed\n'); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + }); + + it('should import an extensionless module that is outside of any scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/imports-noext.mjs'), + ]); + + assert.strictEqual(stderr, ''); + assert.strictEqual(stdout, 'executed\n'); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + }); + + it('should import an extensionless module within a "type": "module" scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/imports-noext.mjs'), + ]); + + assert.strictEqual(stderr, ''); + assert.strictEqual(stdout, 'executed\n'); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + }); + + it('should error on an entry point with an unknown extension', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/extension.unknown'), + ]); + + assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); + assert.strictEqual(stdout, ''); + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + }); + + it('should error on an import with an unknown extension', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/imports-unknownext.mjs'), + ]); + + assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); + assert.strictEqual(stdout, ''); + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + + }); +}); diff --git a/test/es-module/test-esm-unknown-main.mjs b/test/es-module/test-esm-unknown-main.mjs new file mode 100644 index 00000000000000..5adc9ec582831f --- /dev/null +++ b/test/es-module/test-esm-unknown-main.mjs @@ -0,0 +1,92 @@ +// Flags: --experimental-extensionless-modules +import * as common from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { spawn } from 'node:child_process'; +import assert from 'node:assert'; + +{ + const entry = fixtures.path( + '/es-modules/package-type-module/extension.unknown' + ); + const child = spawn(process.execPath, [ + '--experimental-extensionless-modules', + entry, + ]); + let stdout = ''; + let stderr = ''; + child.stderr.setEncoding('utf8'); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + stdout += data; + }); + child.stderr.on('data', (data) => { + stderr += data; + }); + child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + assert.strictEqual(stdout, ''); + assert.ok(stderr.indexOf('ERR_UNKNOWN_FILE_EXTENSION') !== -1); + })); +} +{ + const entry = fixtures.path( + '/es-modules/package-type-module/imports-unknownext.mjs' + ); + const child = spawn(process.execPath, [ + '--experimental-extensionless-modules', + entry, + ]); + let stdout = ''; + let stderr = ''; + child.stderr.setEncoding('utf8'); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + stdout += data; + }); + child.stderr.on('data', (data) => { + stderr += data; + }); + child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 1); + assert.strictEqual(signal, null); + assert.strictEqual(stdout, ''); + assert.ok(stderr.indexOf('ERR_UNKNOWN_FILE_EXTENSION') !== -1); + })); +} +{ + const entry = fixtures.path('/es-modules/package-type-module/noext-esm'); + const child = spawn(process.execPath, [ + '--experimental-extensionless-modules', + entry, + ]); + let stdout = ''; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + stdout += data; + }); + child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + assert.strictEqual(stdout, 'executed\n'); + })); +} +{ + const entry = fixtures.path( + '/es-modules/package-type-module/imports-noext.mjs' + ); + const child = spawn(process.execPath, [ + '--experimental-extensionless-modules', + entry, + ]); + let stdout = ''; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + stdout += data; + }); + child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + assert.strictEqual(stdout, 'executed\n'); + })); +} diff --git a/test/fixtures/es-modules/imports-noext.mjs b/test/fixtures/es-modules/imports-noext.mjs new file mode 100644 index 00000000000000..96eca54521b9d3 --- /dev/null +++ b/test/fixtures/es-modules/imports-noext.mjs @@ -0,0 +1 @@ +import './noext-esm'; diff --git a/test/fixtures/es-modules/noext-esm b/test/fixtures/es-modules/noext-esm new file mode 100644 index 00000000000000..251d6e538a1fcf --- /dev/null +++ b/test/fixtures/es-modules/noext-esm @@ -0,0 +1,2 @@ +export default 'module'; +console.log('executed'); From 7feafa56ac84bee1667b8dc73320768c56a4bbbb Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Sep 2023 17:32:54 -0700 Subject: [PATCH 05/24] Get flag working for extensionless entry points and imports --- lib/internal/modules/esm/get_format.js | 6 ++++-- lib/internal/modules/esm/resolve.js | 5 +++-- lib/internal/modules/run_main.js | 8 +++++--- src/node_options.cc | 20 +++++++++++++++---- src/node_options.h | 3 ++- ...-esm-type-flag-unknown-or-no-extension.mjs | 5 +++-- 6 files changed, 33 insertions(+), 14 deletions(-) diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 8c99648b111228..2dec508fd5fc59 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -16,7 +16,8 @@ const { const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); -const defaultType = getOptionValue('--experimental-type'); +const typeFlag = getOptionValue('--experimental-type'); +const defaultType = typeFlag === 'module' ? 'module' : 'commonjs'; // ! This is where we flip the default to ES modules someday const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve'); const { fileURLToPath } = require('internal/url'); const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; @@ -76,7 +77,8 @@ function extname(url) { function getFileProtocolModuleFormat(url, context, ignoreErrors) { const ext = extname(url); if (ext === '.js' || (defaultType === 'module' && ext === '')) { - return getPackageType(url) === 'module' ? 'module' : 'commonjs'; + const packageType = getPackageType(url); + return packageType === 'none' ? defaultType : packageType; } const format = extensionFormatMap[ext]; diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 0d277915b3a01f..acba2adcaee7a8 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -35,7 +35,8 @@ const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); -const typeFlag = getOptionValue('--input-type'); +const typeFlag = getOptionValue('--experimental-type'); +const inputTypeFlag = getOptionValue('--input-type'); const { URL, pathToFileURL, fileURLToPath, isURL } = require('internal/url'); const { getCWDURL } = require('internal/util'); const { canParse: URLCanParse } = internalBinding('url'); @@ -1112,7 +1113,7 @@ function defaultResolve(specifier, context = {}) { // input, to avoid user confusion over how expansive the effect of the // flag should be (i.e. entry point only, package scope surrounding the // entry point, etc.). - if (typeFlag) { throw new ERR_INPUT_TYPE_NOT_ALLOWED(); } + if (inputTypeFlag) { throw new ERR_INPUT_TYPE_NOT_ALLOWED(); } } conditions = getConditionsSet(conditions); diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index ac1ffef0412b17..080e56510fbe26 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -43,12 +43,14 @@ function shouldUseESMLoader(mainPath) { */ const userImports = getOptionValue('--import'); if (userLoaders.length > 0 || userImports.length > 0) { return true; } - const { readPackageScope } = require('internal/modules/cjs/loader'); - // Determine the module format of the main + + // Determine the module format of the entry point. if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; } if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) { return false; } + + const { readPackageScope } = require('internal/modules/cjs/loader'); const pkg = readPackageScope(mainPath); - return pkg && pkg.data.type === 'module'; + return pkg.data?.type === 'module' || getOptionValue('--experimental-type') === 'module'; } /** diff --git a/src/node_options.cc b/src/node_options.cc index dfb7e23c649829..2fb11564455405 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -114,12 +114,24 @@ void EnvironmentOptions::CheckOptions(std::vector* errors, errors->push_back("--policy-integrity cannot be empty"); } - if (!module_type.empty()) { - if (module_type != "commonjs" && module_type != "module") { + if (!input_type.empty()) { + if (input_type != "commonjs" && input_type != "module") { errors->push_back("--input-type must be \"module\" or \"commonjs\""); } } + if (!type.empty()) { + if (type != "commonjs" && type != "module") { + errors->push_back("--experimental-type must be " + "\"module\" or \"commonjs\""); + } + } + + if (!input_type.empty() && !type.empty()) { + errors->push_back("--input-type and --experimental-type cannot be used " + "together"); + } + if (syntax_check_only && has_eval_string) { errors->push_back("either --check or --eval can be used, not both"); } @@ -474,7 +486,7 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { kAllowedInEnvvar); AddOption("--input-type", "set module type for string input", - &EnvironmentOptions::module_type, + &EnvironmentOptions::input_type, kAllowedInEnvvar); AddOption( "--experimental-specifier-resolution", "", NoOp{}, kAllowedInEnvvar); @@ -648,7 +660,7 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { kAllowedInEnvvar); AddOption("--experimental-type", "set module system to use by default", - &EnvironmentOptions::module_type, + &EnvironmentOptions::type, kAllowedInEnvvar); AddOption("--extra-info-on-fatal-exception", "hide extra information on fatal exception that causes exit", diff --git a/src/node_options.h b/src/node_options.h index 2b9f3d3084651a..bc3c15caeffa61 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -117,7 +117,8 @@ class EnvironmentOptions : public Options { bool experimental_https_modules = false; bool experimental_wasm_modules = false; bool experimental_import_meta_resolve = false; - std::string module_type; + std::string input_type; + std::string type; std::string experimental_policy; std::string experimental_policy_integrity; bool has_policy_integrity_string = false; diff --git a/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs b/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs index 8e53d600f01e9d..a6b554678a61aa 100644 --- a/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs +++ b/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs @@ -3,7 +3,7 @@ import * as fixtures from '../common/fixtures.mjs'; import { describe, it } from 'node:test'; import assert from 'node:assert'; -describe('--experimental-type=module', { concurrency: true }, () => { +describe('--experimental-type=module should affect the behavior of files without extensions', { concurrency: true }, () => { it('should run an extensionless module that is outside of any package scope', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', @@ -51,7 +51,9 @@ describe('--experimental-type=module', { concurrency: true }, () => { assert.strictEqual(code, 0); assert.strictEqual(signal, null); }); +}); +describe('--experimental-type=module should not affect the behavior of files with unknown extensions', { concurrency: true }, () => { it('should error on an entry point with an unknown extension', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', @@ -74,6 +76,5 @@ describe('--experimental-type=module', { concurrency: true }, () => { assert.strictEqual(stdout, ''); assert.strictEqual(code, 1); assert.strictEqual(signal, null); - }); }); From ebc579516c1ce2f3072f4e5f20ae18ab2b261bff Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Sep 2023 18:44:15 -0700 Subject: [PATCH 06/24] Move Wasm tests --- test/{parallel => es-module}/test-wasm-memory-out-of-bound.js | 0 test/{parallel => es-module}/test-wasm-simple.js | 0 test/{parallel => es-module}/test-wasm-web-api.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename test/{parallel => es-module}/test-wasm-memory-out-of-bound.js (100%) rename test/{parallel => es-module}/test-wasm-simple.js (100%) rename test/{parallel => es-module}/test-wasm-web-api.js (100%) diff --git a/test/parallel/test-wasm-memory-out-of-bound.js b/test/es-module/test-wasm-memory-out-of-bound.js similarity index 100% rename from test/parallel/test-wasm-memory-out-of-bound.js rename to test/es-module/test-wasm-memory-out-of-bound.js diff --git a/test/parallel/test-wasm-simple.js b/test/es-module/test-wasm-simple.js similarity index 100% rename from test/parallel/test-wasm-simple.js rename to test/es-module/test-wasm-simple.js diff --git a/test/parallel/test-wasm-web-api.js b/test/es-module/test-wasm-web-api.js similarity index 100% rename from test/parallel/test-wasm-web-api.js rename to test/es-module/test-wasm-web-api.js From f4d9367a5d13557995714314712a5cc26588ae84 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Sep 2023 19:53:34 -0700 Subject: [PATCH 07/24] Write tests for all intended functionality of the new flag --- test/es-module/test-esm-type-flag-errors.mjs | 30 +++ .../test-esm-type-flag-loose-files.mjs | 93 +++++++++ .../test-esm-type-flag-package-scopes.mjs | 188 ++++++++++++++++++ .../test-esm-type-flag-string-input.mjs | 31 +++ ...-esm-type-flag-unknown-or-no-extension.mjs | 80 -------- test/es-module/test-esm-unknown-main.mjs | 92 --------- test/fixtures/es-modules/imports-loose.mjs | 1 + test/fixtures/es-modules/loose.js | 3 + test/fixtures/es-modules/noext-wasm | Bin 0 -> 136 bytes .../node_modules/dep/noext-cjs | 3 + .../node_modules/dep/run.js | 3 + .../es-modules/package-type-module/noext-wasm | Bin 0 -> 136 bytes .../package-type-module/wasm-dep.mjs | 13 ++ .../es-modules/package-without-type/module.js | 3 + .../es-modules/package-without-type/noext-esm | 3 + test/parallel/test-esm-no-extension.mjs | 26 --- test/parallel/test-esm-unknown-main.mjs | 92 --------- 17 files changed, 371 insertions(+), 290 deletions(-) create mode 100644 test/es-module/test-esm-type-flag-errors.mjs create mode 100644 test/es-module/test-esm-type-flag-loose-files.mjs create mode 100644 test/es-module/test-esm-type-flag-package-scopes.mjs create mode 100644 test/es-module/test-esm-type-flag-string-input.mjs delete mode 100644 test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs delete mode 100644 test/es-module/test-esm-unknown-main.mjs create mode 100644 test/fixtures/es-modules/imports-loose.mjs create mode 100644 test/fixtures/es-modules/loose.js create mode 100644 test/fixtures/es-modules/noext-wasm create mode 100644 test/fixtures/es-modules/package-type-module/node_modules/dep/noext-cjs create mode 100644 test/fixtures/es-modules/package-type-module/node_modules/dep/run.js create mode 100644 test/fixtures/es-modules/package-type-module/noext-wasm create mode 100644 test/fixtures/es-modules/package-type-module/wasm-dep.mjs create mode 100644 test/fixtures/es-modules/package-without-type/module.js create mode 100644 test/fixtures/es-modules/package-without-type/noext-esm delete mode 100644 test/parallel/test-esm-no-extension.mjs delete mode 100644 test/parallel/test-esm-unknown-main.mjs diff --git a/test/es-module/test-esm-type-flag-errors.mjs b/test/es-module/test-esm-type-flag-errors.mjs new file mode 100644 index 00000000000000..90cd30abd42f60 --- /dev/null +++ b/test/es-module/test-esm-type-flag-errors.mjs @@ -0,0 +1,30 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { describe, it } from 'node:test'; +import { match, strictEqual } from 'node:assert'; + +describe('--experimental-type=module should not affect the interpretation of files with unknown extensions', { concurrency: true }, () => { + it('should error on an entry point with an unknown extension', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/extension.unknown'), + ]); + + match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); + strictEqual(stdout, ''); + strictEqual(code, 1); + strictEqual(signal, null); + }); + + it('should error on an import with an unknown extension', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/imports-unknownext.mjs'), + ]); + + match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); + strictEqual(stdout, ''); + strictEqual(code, 1); + strictEqual(signal, null); + }); +}); diff --git a/test/es-module/test-esm-type-flag-loose-files.mjs b/test/es-module/test-esm-type-flag-loose-files.mjs new file mode 100644 index 00000000000000..181002a24f57cd --- /dev/null +++ b/test/es-module/test-esm-type-flag-loose-files.mjs @@ -0,0 +1,93 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { describe, it } from 'node:test'; +import { strictEqual } from 'node:assert'; + +describe('the type flag should change the interpretation of certain files outside of any package scope', { concurrency: true }, () => { + it('should run as ESM a .js file that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/loose.js'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should run as ESM an extensionless JavaScript file that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/noext-esm'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should run as Wasm an extensionless Wasm file that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/noext-wasm'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as ESM a .js file that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/imports-loose.mjs'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as ESM an extensionless JavaScript file that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/imports-noext.mjs'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as Wasm an extensionless Wasm file that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--eval', + `import { add } from ${JSON.stringify(fixtures.fileURL('es-modules/noext-wasm'))}; + console.log(add(1, 2));`, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, '3\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should check as ESM input passed via --check', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--check', + fixtures.path('es-modules/loose.js'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + strictEqual(signal, null); + }); +}); diff --git a/test/es-module/test-esm-type-flag-package-scopes.mjs b/test/es-module/test-esm-type-flag-package-scopes.mjs new file mode 100644 index 00000000000000..66b8cceb4404c3 --- /dev/null +++ b/test/es-module/test-esm-type-flag-package-scopes.mjs @@ -0,0 +1,188 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { describe, it } from 'node:test'; +import { strictEqual } from 'node:assert'; + +describe('the type flag should change the interpretation of certain files within a "type": "module" package scope', { concurrency: true }, () => { + it('should run as ESM an extensionless JavaScript file within a "type": "module" scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/noext-esm'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import an extensionless JavaScript file within a "type": "module" scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/imports-noext.mjs'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should run as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--experimental-wasm-modules', + fixtures.path('es-modules/package-type-module/noext-wasm'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--eval', + `import { add } from ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/noext-wasm'))}; + console.log(add(1, 2));`, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, '3\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); +}); + +describe('the type flag should change the interpretation of certain files within a package scope that lacks a "type" field and is not under node_modules', { concurrency: true }, () => { + it('should run as ESM a .js file within package scope that has no defined "type" and is not under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-without-type/module.js'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should run as ESM an extensionless JavaScript file within a package scope that has no defined "type" and is not under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-without-type/noext-esm'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should run as Wasm an extensionless Wasm file within a package scope that has no defined "type" and is not under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--experimental-wasm-modules', + fixtures.path('es-modules/noext-wasm'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as ESM a .js file within package scope that has no defined "type" and is not under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--eval', + `import ${JSON.stringify(fixtures.fileURL('es-modules/package-without-type/module.js'))};`, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as ESM an extensionless JavaScript file within a package scope that has no defined "type" and is not under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--eval', + `import ${JSON.stringify(fixtures.fileURL('es-modules/package-without-type/noext-esm'))};`, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as Wasm an extensionless Wasm file within a package scope that has no defined "type" and is not under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--eval', + `import { add } from ${JSON.stringify(fixtures.fileURL('es-modules/noext-wasm'))}; + console.log(add(1, 2));`, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, '3\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); +}); + +describe('the type flag should NOT change the interpretation of certain files within a package scope that lacks a "type" field and is under node_modules', { concurrency: true }, () => { + it('should run as CommonJS a .js file within package scope that has no defined "type" and is under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/node_modules/dep/run.js'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as CommonJS a .js file within a package scope that has no defined "type" and is under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--eval', + `import ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/node_modules/dep/run.js'))};`, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should run as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/node_modules/dep/noext-cjs'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is under node_modules', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--eval', + `import ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/node_modules/dep/noext-cjs'))};`, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); +}); diff --git a/test/es-module/test-esm-type-flag-string-input.mjs b/test/es-module/test-esm-type-flag-string-input.mjs new file mode 100644 index 00000000000000..cfdcab30217401 --- /dev/null +++ b/test/es-module/test-esm-type-flag-string-input.mjs @@ -0,0 +1,31 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { spawn } from 'node:child_process'; +import { describe, it } from 'node:test'; +import { strictEqual, match } from 'node:assert'; + +describe('the type flag should change the interpretation of string input', { concurrency: true }, () => { + it('should run as ESM input passed via --eval', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--eval', + `import "data:text/javascript,console.log(42)"`, + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, '42\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + // ESM is unsupported for --print via --input-type=module + + it('should run as ESM input passed via STDIN', async () => { + const child = spawn(process.execPath, [ + '--experimental-type=module', + ]); + child.stdin.end('console.log(typeof import.meta.resolve)'); + + match((await child.stdout.toArray()).toString(), /^function\r?\n$/); + }); +}); diff --git a/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs b/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs deleted file mode 100644 index a6b554678a61aa..00000000000000 --- a/test/es-module/test-esm-type-flag-unknown-or-no-extension.mjs +++ /dev/null @@ -1,80 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import { describe, it } from 'node:test'; -import assert from 'node:assert'; - -describe('--experimental-type=module should affect the behavior of files without extensions', { concurrency: true }, () => { - it('should run an extensionless module that is outside of any package scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/noext-esm'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'executed\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should run an extensionless module within a "type": "module" scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-type-module/noext-esm'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'executed\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should import an extensionless module that is outside of any scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/imports-noext.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'executed\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should import an extensionless module within a "type": "module" scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-type-module/imports-noext.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'executed\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); -}); - -describe('--experimental-type=module should not affect the behavior of files with unknown extensions', { concurrency: true }, () => { - it('should error on an entry point with an unknown extension', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-type-module/extension.unknown'), - ]); - - assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should error on an import with an unknown extension', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-type-module/imports-unknownext.mjs'), - ]); - - assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); -}); diff --git a/test/es-module/test-esm-unknown-main.mjs b/test/es-module/test-esm-unknown-main.mjs deleted file mode 100644 index 5adc9ec582831f..00000000000000 --- a/test/es-module/test-esm-unknown-main.mjs +++ /dev/null @@ -1,92 +0,0 @@ -// Flags: --experimental-extensionless-modules -import * as common from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import { spawn } from 'node:child_process'; -import assert from 'node:assert'; - -{ - const entry = fixtures.path( - '/es-modules/package-type-module/extension.unknown' - ); - const child = spawn(process.execPath, [ - '--experimental-extensionless-modules', - entry, - ]); - let stdout = ''; - let stderr = ''; - child.stderr.setEncoding('utf8'); - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - stdout += data; - }); - child.stderr.on('data', (data) => { - stderr += data; - }); - child.on('close', common.mustCall((code, signal) => { - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - assert.strictEqual(stdout, ''); - assert.ok(stderr.indexOf('ERR_UNKNOWN_FILE_EXTENSION') !== -1); - })); -} -{ - const entry = fixtures.path( - '/es-modules/package-type-module/imports-unknownext.mjs' - ); - const child = spawn(process.execPath, [ - '--experimental-extensionless-modules', - entry, - ]); - let stdout = ''; - let stderr = ''; - child.stderr.setEncoding('utf8'); - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - stdout += data; - }); - child.stderr.on('data', (data) => { - stderr += data; - }); - child.on('close', common.mustCall((code, signal) => { - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - assert.strictEqual(stdout, ''); - assert.ok(stderr.indexOf('ERR_UNKNOWN_FILE_EXTENSION') !== -1); - })); -} -{ - const entry = fixtures.path('/es-modules/package-type-module/noext-esm'); - const child = spawn(process.execPath, [ - '--experimental-extensionless-modules', - entry, - ]); - let stdout = ''; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', common.mustCall((code, signal) => { - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - assert.strictEqual(stdout, 'executed\n'); - })); -} -{ - const entry = fixtures.path( - '/es-modules/package-type-module/imports-noext.mjs' - ); - const child = spawn(process.execPath, [ - '--experimental-extensionless-modules', - entry, - ]); - let stdout = ''; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', common.mustCall((code, signal) => { - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - assert.strictEqual(stdout, 'executed\n'); - })); -} diff --git a/test/fixtures/es-modules/imports-loose.mjs b/test/fixtures/es-modules/imports-loose.mjs new file mode 100644 index 00000000000000..13831e5db03e82 --- /dev/null +++ b/test/fixtures/es-modules/imports-loose.mjs @@ -0,0 +1 @@ +import './loose.js'; diff --git a/test/fixtures/es-modules/loose.js b/test/fixtures/es-modules/loose.js new file mode 100644 index 00000000000000..cbee19f3e2fceb --- /dev/null +++ b/test/fixtures/es-modules/loose.js @@ -0,0 +1,3 @@ +// This file can be run or imported only if `--experimental-type=module` is set. +export default 'module'; +console.log('executed'); diff --git a/test/fixtures/es-modules/noext-wasm b/test/fixtures/es-modules/noext-wasm new file mode 100644 index 0000000000000000000000000000000000000000..9e035904b2e4d0a30ce5a63bcf05cc3a2c8449db GIT binary patch literal 136 zcmZ9COA5k35Jam#kl=s>MAx~130^|TEhaEo*f23T0he=in=IYDDqa=lk_iA^G=gdb zBG>AL9Q@$(Fn;}VPs=uBD{AGr0)Mu(GOe%O7ZMd>X|61DN|4~3^7j7hOM should still be CommonJS as it is in node_modules +module.exports = 42; +console.log('executed'); diff --git a/test/fixtures/es-modules/package-type-module/node_modules/dep/run.js b/test/fixtures/es-modules/package-type-module/node_modules/dep/run.js new file mode 100644 index 00000000000000..7712b3bad54497 --- /dev/null +++ b/test/fixtures/es-modules/package-type-module/node_modules/dep/run.js @@ -0,0 +1,3 @@ +// No package.json -> should still be CommonJS as it is in node_modules +module.exports = 42; +console.log('executed'); diff --git a/test/fixtures/es-modules/package-type-module/noext-wasm b/test/fixtures/es-modules/package-type-module/noext-wasm new file mode 100644 index 0000000000000000000000000000000000000000..9e035904b2e4d0a30ce5a63bcf05cc3a2c8449db GIT binary patch literal 136 zcmZ9COA5k35Jam#kl=s>MAx~130^|TEhaEo*f23T0he=in=IYDDqa=lk_iA^G=gdb zBG>AL9Q@$(Fn;}VPs=uBD{AGr0)Mu(GOe%O7ZMd>X|61DN|4~3^7j7hOM { - stdout += data; -}); -child.on('close', common.mustCall((code, signal) => { - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - assert.strictEqual(stdout, 'executed\n'); -})); diff --git a/test/parallel/test-esm-unknown-main.mjs b/test/parallel/test-esm-unknown-main.mjs deleted file mode 100644 index 5adc9ec582831f..00000000000000 --- a/test/parallel/test-esm-unknown-main.mjs +++ /dev/null @@ -1,92 +0,0 @@ -// Flags: --experimental-extensionless-modules -import * as common from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import { spawn } from 'node:child_process'; -import assert from 'node:assert'; - -{ - const entry = fixtures.path( - '/es-modules/package-type-module/extension.unknown' - ); - const child = spawn(process.execPath, [ - '--experimental-extensionless-modules', - entry, - ]); - let stdout = ''; - let stderr = ''; - child.stderr.setEncoding('utf8'); - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - stdout += data; - }); - child.stderr.on('data', (data) => { - stderr += data; - }); - child.on('close', common.mustCall((code, signal) => { - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - assert.strictEqual(stdout, ''); - assert.ok(stderr.indexOf('ERR_UNKNOWN_FILE_EXTENSION') !== -1); - })); -} -{ - const entry = fixtures.path( - '/es-modules/package-type-module/imports-unknownext.mjs' - ); - const child = spawn(process.execPath, [ - '--experimental-extensionless-modules', - entry, - ]); - let stdout = ''; - let stderr = ''; - child.stderr.setEncoding('utf8'); - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - stdout += data; - }); - child.stderr.on('data', (data) => { - stderr += data; - }); - child.on('close', common.mustCall((code, signal) => { - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - assert.strictEqual(stdout, ''); - assert.ok(stderr.indexOf('ERR_UNKNOWN_FILE_EXTENSION') !== -1); - })); -} -{ - const entry = fixtures.path('/es-modules/package-type-module/noext-esm'); - const child = spawn(process.execPath, [ - '--experimental-extensionless-modules', - entry, - ]); - let stdout = ''; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', common.mustCall((code, signal) => { - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - assert.strictEqual(stdout, 'executed\n'); - })); -} -{ - const entry = fixtures.path( - '/es-modules/package-type-module/imports-noext.mjs' - ); - const child = spawn(process.execPath, [ - '--experimental-extensionless-modules', - entry, - ]); - let stdout = ''; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', common.mustCall((code, signal) => { - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - assert.strictEqual(stdout, 'executed\n'); - })); -} From 74fed344622fdf6ce740f504a16b0639ecf5f83e Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Sep 2023 20:15:40 -0700 Subject: [PATCH 08/24] Fix --check --- lib/internal/main/check_syntax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/main/check_syntax.js b/lib/internal/main/check_syntax.js index 50e0b7d6de67c4..dc9de45420f692 100644 --- a/lib/internal/main/check_syntax.js +++ b/lib/internal/main/check_syntax.js @@ -60,7 +60,7 @@ function loadESMIfNeeded(cb) { async function checkSyntax(source, filename) { let isModule = true; if (filename === '[stdin]' || filename === '[eval]') { - isModule = getOptionValue('--input-type') === 'module'; + isModule = getOptionValue('--input-type') === 'module' || getOptionValue('--experimental-type') === 'module'; } else { const { defaultResolve } = require('internal/modules/esm/resolve'); const { defaultGetFormat } = require('internal/modules/esm/get_format'); From 2a9612509ea0c7d70b7bebba3afeb528f275e1a0 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Sep 2023 20:15:53 -0700 Subject: [PATCH 09/24] Fix --eval, STDIN --- lib/internal/main/eval_stdin.js | 2 +- lib/internal/main/eval_string.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/main/eval_stdin.js b/lib/internal/main/eval_stdin.js index d947af49a6a942..d457f4f3c1da8d 100644 --- a/lib/internal/main/eval_stdin.js +++ b/lib/internal/main/eval_stdin.js @@ -25,7 +25,7 @@ readStdin((code) => { const print = getOptionValue('--print'); const loadESM = getOptionValue('--import').length > 0; - if (getOptionValue('--input-type') === 'module') + if (getOptionValue('--input-type') === 'module' || getOptionValue('--experimental-type') === 'module') evalModule(code, print); else evalScript('[stdin]', diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js index dc59a2ce4f7709..f2b89ead38db3f 100644 --- a/lib/internal/main/eval_string.js +++ b/lib/internal/main/eval_string.js @@ -25,7 +25,7 @@ markBootstrapComplete(); const source = getOptionValue('--eval'); const print = getOptionValue('--print'); const loadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0; -if (getOptionValue('--input-type') === 'module') +if (getOptionValue('--input-type') === 'module' || getOptionValue('--experimental-type') === 'module') evalModule(source, print); else { // For backward compatibility, we want the identifier crypto to be the From 73ff39a180d8d05ec98398770bb0bba559c5c151 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Sep 2023 20:52:20 -0700 Subject: [PATCH 10/24] Support extensionless Wasm in explicit or default-via-flag "module" scope --- lib/internal/modules/esm/formats.js | 28 +++++++++++++++++++ lib/internal/modules/esm/get_format.js | 20 +++++++++---- .../test-esm-type-flag-loose-files.mjs | 4 +++ .../test-esm-type-flag-package-scopes.mjs | 6 ++++ .../test-esm-unknown-or-no-extension.js | 4 +-- 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/internal/modules/esm/formats.js b/lib/internal/modules/esm/formats.js index 4ab9aa6f032b7e..d92e82af74a4b4 100644 --- a/lib/internal/modules/esm/formats.js +++ b/lib/internal/modules/esm/formats.js @@ -2,9 +2,12 @@ const { RegExpPrototypeExec, + Uint8Array, } = primordials; const { getOptionValue } = require('internal/options'); +const { closeSync, openSync, readSync } = require('fs'); + const experimentalWasmModules = getOptionValue('--experimental-wasm-modules'); const extensionFormatMap = { @@ -35,7 +38,32 @@ function mimeToFormat(mime) { return null; } +/** + * For extensionless files in a `module` package scope, or a default `module` scope enabled by the `--experimental-type` + * flag, we check the file contents to disambiguate between ES module JavaScript and Wasm. + * We do this by taking advantage of the fact that all Wasm files start with the header `0x00 0x61 0x73 0x6d` (`_asm`). + * @param {URL} url + */ +function getFormatOfExtensionlessFile(url) { + if (!experimentalWasmModules) { return 'module'; } + + const magic = new Uint8Array(4); + let fd; + try { + fd = openSync(url); + readSync(fd, magic, 0, 4); // Only read the first four bytes + if (magic[0] === 0x00 && magic[1] === 0x61 && magic[2] === 0x73 && magic[3] === 0x6d) { + return 'wasm'; + } + } finally { + if (fd) { closeSync(fd) }; + } + + return 'module'; +} + module.exports = { extensionFormatMap, + getFormatOfExtensionlessFile, mimeToFormat, }; diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 2dec508fd5fc59..62856c2a13f8bd 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -1,4 +1,5 @@ 'use strict'; + const { RegExpPrototypeExec, ObjectPrototypeHasOwnProperty, @@ -11,6 +12,7 @@ const { basename, relative } = require('path'); const { getOptionValue } = require('internal/options'); const { extensionFormatMap, + getFormatOfExtensionlessFile, mimeToFormat, } = require('internal/modules/esm/formats'); @@ -76,11 +78,20 @@ function extname(url) { */ function getFileProtocolModuleFormat(url, context, ignoreErrors) { const ext = extname(url); - if (ext === '.js' || (defaultType === 'module' && ext === '')) { + + if (ext === '.js') { const packageType = getPackageType(url); return packageType === 'none' ? defaultType : packageType; } + if (ext === '') { + const packageType = getPackageType(url); + if (packageType === 'module' || (packageType === 'none' && defaultType === 'module')) { + return getFormatOfExtensionlessFile(url); + } + return 'commonjs'; + } + const format = extensionFormatMap[ext]; if (format) { return format; } @@ -92,10 +103,9 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) { const config = getPackageScopeConfig(url); const fileBasename = basename(filepath); const relativePath = StringPrototypeSlice(relative(config.pjsonPath, filepath), 1); - suggestion = 'Loading extensionless files is not supported inside of ' + - '"type":"module" package.json contexts. The package.json file ' + - `${config.pjsonPath} caused this "type":"module" context. Try ` + - `changing ${filepath} to have a file extension. Note the "bin" ` + + suggestion = 'Loading extensionless files is not supported inside of "type":"module" package.json contexts ' + + `without --experimental-type=module. The package.json file ${config.pjsonPath} caused this ` + + `"type":"module" context. Try changing ${filepath} to have a file extension. Note the "bin" ` + 'field of package.json can point to a file with an extension, for example ' + `{"type":"module","bin":{"${fileBasename}":"${relativePath}.js"}}`; } diff --git a/test/es-module/test-esm-type-flag-loose-files.mjs b/test/es-module/test-esm-type-flag-loose-files.mjs index 181002a24f57cd..95ac906f0c349c 100644 --- a/test/es-module/test-esm-type-flag-loose-files.mjs +++ b/test/es-module/test-esm-type-flag-loose-files.mjs @@ -31,6 +31,8 @@ describe('the type flag should change the interpretation of certain files outsid it('should run as Wasm an extensionless Wasm file that is outside of any package scope', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', + '--experimental-wasm-modules', + '--no-warnings', fixtures.path('es-modules/noext-wasm'), ]); @@ -67,6 +69,8 @@ describe('the type flag should change the interpretation of certain files outsid it('should import as Wasm an extensionless Wasm file that is outside of any package scope', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', + '--experimental-wasm-modules', + '--no-warnings', '--eval', `import { add } from ${JSON.stringify(fixtures.fileURL('es-modules/noext-wasm'))}; console.log(add(1, 2));`, diff --git a/test/es-module/test-esm-type-flag-package-scopes.mjs b/test/es-module/test-esm-type-flag-package-scopes.mjs index 66b8cceb4404c3..936a710d01d02a 100644 --- a/test/es-module/test-esm-type-flag-package-scopes.mjs +++ b/test/es-module/test-esm-type-flag-package-scopes.mjs @@ -32,6 +32,7 @@ describe('the type flag should change the interpretation of certain files within const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', '--experimental-wasm-modules', + '--no-warnings', fixtures.path('es-modules/package-type-module/noext-wasm'), ]); @@ -44,6 +45,8 @@ describe('the type flag should change the interpretation of certain files within it('should import as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', + '--experimental-wasm-modules', + '--no-warnings', '--eval', `import { add } from ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/noext-wasm'))}; console.log(add(1, 2));`, @@ -85,6 +88,7 @@ describe('the type flag should change the interpretation of certain files within const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', '--experimental-wasm-modules', + '--no-warnings', fixtures.path('es-modules/noext-wasm'), ]); @@ -123,6 +127,8 @@ describe('the type flag should change the interpretation of certain files within it('should import as Wasm an extensionless Wasm file within a package scope that has no defined "type" and is not under node_modules', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', + '--experimental-wasm-modules', + '--no-warnings', '--eval', `import { add } from ${JSON.stringify(fixtures.fileURL('es-modules/noext-wasm'))}; console.log(add(1, 2));`, diff --git a/test/es-module/test-esm-unknown-or-no-extension.js b/test/es-module/test-esm-unknown-or-no-extension.js index 3f0660e5aa9225..83ebcc6267bfc3 100644 --- a/test/es-module/test-esm-unknown-or-no-extension.js +++ b/test/es-module/test-esm-unknown-or-no-extension.js @@ -26,10 +26,10 @@ describe('ESM: extensionless and unknown specifiers', { concurrency: true }, () assert.strictEqual(code, 1); assert.strictEqual(signal, null); assert.strictEqual(stdout, ''); - assert.ok(stderr.includes('ERR_UNKNOWN_FILE_EXTENSION')); + assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); if (fixturePath.includes('noext')) { // Check for explanation to users - assert.ok(stderr.includes('extensionless')); + assert.match(stderr, /extensionless/); } }); } From 70366fbfd88a199091c9e7cf4e7d47e0053ca7cd Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Sep 2023 22:45:22 -0700 Subject: [PATCH 11/24] Fix package scopes --- lib/internal/modules/esm/get_format.js | 37 +++++++++++++++++-- .../test-esm-type-flag-package-scopes.mjs | 8 ++-- .../es-modules/package-type-module/index.js | 2 +- .../node_modules/dep-with-package-json/dep.js | 2 + .../{dep => dep-with-package-json}/noext-cjs | 0 .../dep-with-package-json/package.json | 7 ++++ .../{dep => dep-with-package-json}/run.js | 0 .../{dep => dep-without-package-json}/dep.js | 0 .../dep-without-package-json/noext-cjs | 3 ++ .../dep-without-package-json/run.js | 3 ++ 10 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/dep.js rename test/fixtures/es-modules/package-type-module/node_modules/{dep => dep-with-package-json}/noext-cjs (100%) create mode 100644 test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/package.json rename test/fixtures/es-modules/package-type-module/node_modules/{dep => dep-with-package-json}/run.js (100%) rename test/fixtures/es-modules/package-type-module/node_modules/{dep => dep-without-package-json}/dep.js (100%) create mode 100644 test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/noext-cjs create mode 100644 test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/run.js diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 62856c2a13f8bd..17801b7af10b6b 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -5,6 +5,7 @@ const { ObjectPrototypeHasOwnProperty, PromisePrototypeThen, PromiseResolve, + StringPrototypeIndexOf, StringPrototypeCharCodeAt, StringPrototypeSlice, } = primordials; @@ -70,6 +71,15 @@ function extname(url) { return ''; } +/** + * Determine whether the given file URL is under a `node_modules` folder. + * This function assumes that the input has already been verified to be a `file:` URL, and is a file rather than a folder. + * @param {URL} url + */ +function underNodeModules(url) { + return StringPrototypeIndexOf(url.pathname, '/node_modules/') !== -1; +} + /** * @param {URL} url * @param {{parentURL: string}} context @@ -81,15 +91,34 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) { if (ext === '.js') { const packageType = getPackageType(url); - return packageType === 'none' ? defaultType : packageType; + if (packageType !== 'none') { + return packageType; + } + // The controlling `package.json` file has no `type` field. + if (defaultType === 'module') { + // An exception to the type flag making ESM the default everywhere is that package scopes under `node_modules` + // should retain the assumption that a lack of a `type` field means CommonJS. + return underNodeModules(url) ? 'commonjs' : 'module'; + } + return 'commonjs'; } if (ext === '') { const packageType = getPackageType(url); - if (packageType === 'module' || (packageType === 'none' && defaultType === 'module')) { - return getFormatOfExtensionlessFile(url); + if (defaultType === 'commonjs') { // Legacy behavior + if (packageType === 'none' || packageType === 'commonjs') { + return 'commonjs'; + } + // If package type is `module`, fall through to the error case below + } else { // defaultType === 'module' + if (underNodeModules(url)) { // Exception for package scopes under `node_modules` + return 'commonjs'; + } + if (packageType === 'none' || packageType === 'module') { + return getFormatOfExtensionlessFile(url); + } // else packageType === 'commonjs' + return 'commonjs'; } - return 'commonjs'; } const format = extensionFormatMap[ext]; diff --git a/test/es-module/test-esm-type-flag-package-scopes.mjs b/test/es-module/test-esm-type-flag-package-scopes.mjs index 936a710d01d02a..6ce5dcb638ec54 100644 --- a/test/es-module/test-esm-type-flag-package-scopes.mjs +++ b/test/es-module/test-esm-type-flag-package-scopes.mjs @@ -145,7 +145,7 @@ describe('the type flag should NOT change the interpretation of certain files wi it('should run as CommonJS a .js file within package scope that has no defined "type" and is under node_modules', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', - fixtures.path('es-modules/package-type-module/node_modules/dep/run.js'), + fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json/run.js'), ]); strictEqual(stderr, ''); @@ -158,7 +158,7 @@ describe('the type flag should NOT change the interpretation of certain files wi const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/node_modules/dep/run.js'))};`, + `import ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/run.js'))};`, ]); strictEqual(stderr, ''); @@ -170,7 +170,7 @@ describe('the type flag should NOT change the interpretation of certain files wi it('should run as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is under node_modules', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', - fixtures.path('es-modules/package-type-module/node_modules/dep/noext-cjs'), + fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs'), ]); strictEqual(stderr, ''); @@ -183,7 +183,7 @@ describe('the type flag should NOT change the interpretation of certain files wi const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/node_modules/dep/noext-cjs'))};`, + `import ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs'))};`, ]); strictEqual(stderr, ''); diff --git a/test/fixtures/es-modules/package-type-module/index.js b/test/fixtures/es-modules/package-type-module/index.js index e8f4db3e164302..86d88056422197 100644 --- a/test/fixtures/es-modules/package-type-module/index.js +++ b/test/fixtures/es-modules/package-type-module/index.js @@ -1,4 +1,4 @@ -import 'dep/dep.js'; +import 'dep-without-package-json/dep.js'; const identifier = 'package-type-module'; console.log(identifier); export default identifier; diff --git a/test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/dep.js b/test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/dep.js new file mode 100644 index 00000000000000..0d702867cd5ccf --- /dev/null +++ b/test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/dep.js @@ -0,0 +1,2 @@ +// Controlling package.json has no "type" field -> should still be CommonJS as it is in node_modules +module.exports = 42; diff --git a/test/fixtures/es-modules/package-type-module/node_modules/dep/noext-cjs b/test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs similarity index 100% rename from test/fixtures/es-modules/package-type-module/node_modules/dep/noext-cjs rename to test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs diff --git a/test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/package.json b/test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/package.json new file mode 100644 index 00000000000000..5ee78b14c414b2 --- /dev/null +++ b/test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/package.json @@ -0,0 +1,7 @@ +{ + "name": "dep-with-package-json", + "version": "1.0.0", + "exports": { + "./*": "./*" + } +} diff --git a/test/fixtures/es-modules/package-type-module/node_modules/dep/run.js b/test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/run.js similarity index 100% rename from test/fixtures/es-modules/package-type-module/node_modules/dep/run.js rename to test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/run.js diff --git a/test/fixtures/es-modules/package-type-module/node_modules/dep/dep.js b/test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/dep.js similarity index 100% rename from test/fixtures/es-modules/package-type-module/node_modules/dep/dep.js rename to test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/dep.js diff --git a/test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/noext-cjs b/test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/noext-cjs new file mode 100644 index 00000000000000..7712b3bad54497 --- /dev/null +++ b/test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/noext-cjs @@ -0,0 +1,3 @@ +// No package.json -> should still be CommonJS as it is in node_modules +module.exports = 42; +console.log('executed'); diff --git a/test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/run.js b/test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/run.js new file mode 100644 index 00000000000000..7712b3bad54497 --- /dev/null +++ b/test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/run.js @@ -0,0 +1,3 @@ +// No package.json -> should still be CommonJS as it is in node_modules +module.exports = 42; +console.log('executed'); From 9b3b7c49e131346bc333d861eda27346d696ae4b Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Sep 2023 22:47:50 -0700 Subject: [PATCH 12/24] Move test --- test/{parallel => es-module}/test-esm-url-extname.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{parallel => es-module}/test-esm-url-extname.js (100%) diff --git a/test/parallel/test-esm-url-extname.js b/test/es-module/test-esm-url-extname.js similarity index 100% rename from test/parallel/test-esm-url-extname.js rename to test/es-module/test-esm-url-extname.js From 7075fcd2b6aa7d16204b5d36da1b709ee987db82 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 24 Sep 2023 13:45:54 -0700 Subject: [PATCH 13/24] Simplify tests --- test/common/package.json | 3 + .../test-esm-type-flag-loose-files.mjs | 38 ++------ .../test-esm-type-flag-package-scopes.mjs | 90 ++++--------------- 3 files changed, 25 insertions(+), 106 deletions(-) create mode 100644 test/common/package.json diff --git a/test/common/package.json b/test/common/package.json new file mode 100644 index 00000000000000..5bbefffbabee39 --- /dev/null +++ b/test/common/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/test/es-module/test-esm-type-flag-loose-files.mjs b/test/es-module/test-esm-type-flag-loose-files.mjs index 95ac906f0c349c..468e6338c5b197 100644 --- a/test/es-module/test-esm-type-flag-loose-files.mjs +++ b/test/es-module/test-esm-type-flag-loose-files.mjs @@ -1,3 +1,4 @@ +// Flags: --experimental-type=module --experimental-wasm-modules import { spawnPromisified } from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import { describe, it } from 'node:test'; @@ -43,43 +44,18 @@ describe('the type flag should change the interpretation of certain files outsid }); it('should import as ESM a .js file that is outside of any package scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/imports-loose.mjs'), - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/loose.js')); + strictEqual(defaultExport, 'module'); }); it('should import as ESM an extensionless JavaScript file that is outside of any package scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/imports-noext.mjs'), - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/noext-esm')); + strictEqual(defaultExport, 'module'); }); it('should import as Wasm an extensionless Wasm file that is outside of any package scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--experimental-wasm-modules', - '--no-warnings', - '--eval', - `import { add } from ${JSON.stringify(fixtures.fileURL('es-modules/noext-wasm'))}; - console.log(add(1, 2));`, - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, '3\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { add } = await import(fixtures.fileURL('es-modules/noext-wasm')); + strictEqual(add(1, 2), 3); }); it('should check as ESM input passed via --check', async () => { diff --git a/test/es-module/test-esm-type-flag-package-scopes.mjs b/test/es-module/test-esm-type-flag-package-scopes.mjs index 6ce5dcb638ec54..90b5f1ad2c5047 100644 --- a/test/es-module/test-esm-type-flag-package-scopes.mjs +++ b/test/es-module/test-esm-type-flag-package-scopes.mjs @@ -1,3 +1,4 @@ +// Flags: --experimental-type=module --experimental-wasm-modules import { spawnPromisified } from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import { describe, it } from 'node:test'; @@ -17,15 +18,8 @@ describe('the type flag should change the interpretation of certain files within }); it('should import an extensionless JavaScript file within a "type": "module" scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-type-module/imports-noext.mjs'), - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-type-module/noext-esm')); + strictEqual(defaultExport, 'module'); }); it('should run as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { @@ -43,19 +37,8 @@ describe('the type flag should change the interpretation of certain files within }); it('should import as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--experimental-wasm-modules', - '--no-warnings', - '--eval', - `import { add } from ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/noext-wasm'))}; - console.log(add(1, 2));`, - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, '3\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { add } = await import(fixtures.fileURL('es-modules/package-type-module/noext-wasm')); + strictEqual(add(1, 2), 3); }); }); @@ -99,45 +82,18 @@ describe('the type flag should change the interpretation of certain files within }); it('should import as ESM a .js file within package scope that has no defined "type" and is not under node_modules', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-modules/package-without-type/module.js'))};`, - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-without-type/module.js')); + strictEqual(defaultExport, 'module'); }); it('should import as ESM an extensionless JavaScript file within a package scope that has no defined "type" and is not under node_modules', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-modules/package-without-type/noext-esm'))};`, - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-without-type/noext-esm')); + strictEqual(defaultExport, 'module'); }); it('should import as Wasm an extensionless Wasm file within a package scope that has no defined "type" and is not under node_modules', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--experimental-wasm-modules', - '--no-warnings', - '--eval', - `import { add } from ${JSON.stringify(fixtures.fileURL('es-modules/noext-wasm'))}; - console.log(add(1, 2));`, - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, '3\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { add } = await import(fixtures.fileURL('es-modules/noext-wasm')); + strictEqual(add(1, 2), 3); }); }); @@ -155,16 +111,8 @@ describe('the type flag should NOT change the interpretation of certain files wi }); it('should import as CommonJS a .js file within a package scope that has no defined "type" and is under node_modules', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/run.js'))};`, - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/run.js')); + strictEqual(defaultExport, 42); }); it('should run as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is under node_modules', async () => { @@ -180,15 +128,7 @@ describe('the type flag should NOT change the interpretation of certain files wi }); it('should import as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is under node_modules', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs'))};`, - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs')); + strictEqual(defaultExport, 42); }); }); From b1c3ac21c853993e848ef931dddd6c9eef5125a0 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 24 Sep 2023 14:02:31 -0700 Subject: [PATCH 14/24] Lint --- lib/internal/modules/esm/formats.js | 2 +- lib/internal/modules/esm/get_format.js | 17 +- lib/internal/modules/esm/resolve.js | 1 - test/es-module/test-esm-type-flag-errors.mjs | 45 ++--- .../test-esm-type-flag-loose-files.mjs | 116 ++++++------ .../test-esm-type-flag-package-scopes.mjs | 170 ++++++++++-------- .../test-esm-type-flag-string-input.mjs | 3 +- 7 files changed, 186 insertions(+), 168 deletions(-) diff --git a/lib/internal/modules/esm/formats.js b/lib/internal/modules/esm/formats.js index d92e82af74a4b4..b5b59e2d9dc14b 100644 --- a/lib/internal/modules/esm/formats.js +++ b/lib/internal/modules/esm/formats.js @@ -56,7 +56,7 @@ function getFormatOfExtensionlessFile(url) { return 'wasm'; } } finally { - if (fd) { closeSync(fd) }; + if (fd) { closeSync(fd); } } return 'module'; diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 17801b7af10b6b..8b2908876ebdfa 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -20,7 +20,8 @@ const { const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); const typeFlag = getOptionValue('--experimental-type'); -const defaultType = typeFlag === 'module' ? 'module' : 'commonjs'; // ! This is where we flip the default to ES modules someday +// The next line is where we flip the default to ES modules someday. +const defaultType = typeFlag === 'module' ? 'module' : 'commonjs'; const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve'); const { fileURLToPath } = require('internal/url'); const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; @@ -73,7 +74,8 @@ function extname(url) { /** * Determine whether the given file URL is under a `node_modules` folder. - * This function assumes that the input has already been verified to be a `file:` URL, and is a file rather than a folder. + * This function assumes that the input has already been verified to be a `file:` URL, + * and is a file rather than a folder. * @param {URL} url */ function underNodeModules(url) { @@ -110,13 +112,13 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) { return 'commonjs'; } // If package type is `module`, fall through to the error case below - } else { // defaultType === 'module' + } else { // Else defaultType === 'module' if (underNodeModules(url)) { // Exception for package scopes under `node_modules` return 'commonjs'; } if (packageType === 'none' || packageType === 'module') { return getFormatOfExtensionlessFile(url); - } // else packageType === 'commonjs' + } // Else packageType === 'commonjs' return 'commonjs'; } } @@ -133,10 +135,9 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) { const fileBasename = basename(filepath); const relativePath = StringPrototypeSlice(relative(config.pjsonPath, filepath), 1); suggestion = 'Loading extensionless files is not supported inside of "type":"module" package.json contexts ' + - `without --experimental-type=module. The package.json file ${config.pjsonPath} caused this ` + - `"type":"module" context. Try changing ${filepath} to have a file extension. Note the "bin" ` + - 'field of package.json can point to a file with an extension, for example ' + - `{"type":"module","bin":{"${fileBasename}":"${relativePath}.js"}}`; + `without --experimental-type=module. The package.json file ${config.pjsonPath} caused this "type":"module" ` + + `context. Try changing ${filepath} to have a file extension. Note the "bin" field of package.json can point ` + + `to a file with an extension, for example {"type":"module","bin":{"${fileBasename}":"${relativePath}.js"}}`; } throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath, suggestion); } diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index acba2adcaee7a8..5aea5ca5460199 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -35,7 +35,6 @@ const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); -const typeFlag = getOptionValue('--experimental-type'); const inputTypeFlag = getOptionValue('--input-type'); const { URL, pathToFileURL, fileURLToPath, isURL } = require('internal/url'); const { getCWDURL } = require('internal/util'); diff --git a/test/es-module/test-esm-type-flag-errors.mjs b/test/es-module/test-esm-type-flag-errors.mjs index 90cd30abd42f60..270b54c8ab93ff 100644 --- a/test/es-module/test-esm-type-flag-errors.mjs +++ b/test/es-module/test-esm-type-flag-errors.mjs @@ -3,28 +3,29 @@ import * as fixtures from '../common/fixtures.mjs'; import { describe, it } from 'node:test'; import { match, strictEqual } from 'node:assert'; -describe('--experimental-type=module should not affect the interpretation of files with unknown extensions', { concurrency: true }, () => { - it('should error on an entry point with an unknown extension', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-type-module/extension.unknown'), - ]); +describe('--experimental-type=module should not affect the interpretation of files with unknown extensions', + { concurrency: true }, () => { + it('should error on an entry point with an unknown extension', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/extension.unknown'), + ]); - match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); - strictEqual(stdout, ''); - strictEqual(code, 1); - strictEqual(signal, null); - }); + match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); + strictEqual(stdout, ''); + strictEqual(code, 1); + strictEqual(signal, null); + }); - it('should error on an import with an unknown extension', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-type-module/imports-unknownext.mjs'), - ]); + it('should error on an import with an unknown extension', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/imports-unknownext.mjs'), + ]); - match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); - strictEqual(stdout, ''); - strictEqual(code, 1); - strictEqual(signal, null); - }); -}); + match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); + strictEqual(stdout, ''); + strictEqual(code, 1); + strictEqual(signal, null); + }); + }); diff --git a/test/es-module/test-esm-type-flag-loose-files.mjs b/test/es-module/test-esm-type-flag-loose-files.mjs index 468e6338c5b197..19b9613d55a517 100644 --- a/test/es-module/test-esm-type-flag-loose-files.mjs +++ b/test/es-module/test-esm-type-flag-loose-files.mjs @@ -4,70 +4,72 @@ import * as fixtures from '../common/fixtures.mjs'; import { describe, it } from 'node:test'; import { strictEqual } from 'node:assert'; -describe('the type flag should change the interpretation of certain files outside of any package scope', { concurrency: true }, () => { - it('should run as ESM a .js file that is outside of any package scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/loose.js'), - ]); +describe('the type flag should change the interpretation of certain files outside of any package scope', + { concurrency: true }, () => { + it('should run as ESM a .js file that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/loose.js'), + ]); - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); - }); + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); - it('should run as ESM an extensionless JavaScript file that is outside of any package scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/noext-esm'), - ]); + it('should run as ESM an extensionless JavaScript file that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/noext-esm'), + ]); - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); - }); + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); - it('should run as Wasm an extensionless Wasm file that is outside of any package scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--experimental-wasm-modules', - '--no-warnings', - fixtures.path('es-modules/noext-wasm'), - ]); + it('should run as Wasm an extensionless Wasm file that is outside of any package scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--experimental-wasm-modules', + '--no-warnings', + fixtures.path('es-modules/noext-wasm'), + ]); - strictEqual(stderr, ''); - strictEqual(stdout, ''); - strictEqual(code, 0); - strictEqual(signal, null); - }); + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + strictEqual(signal, null); + }); - it('should import as ESM a .js file that is outside of any package scope', async () => { - const { default: defaultExport } = await import(fixtures.fileURL('es-modules/loose.js')); - strictEqual(defaultExport, 'module'); - }); + it('should import as ESM a .js file that is outside of any package scope', async () => { + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/loose.js')); + strictEqual(defaultExport, 'module'); + }); - it('should import as ESM an extensionless JavaScript file that is outside of any package scope', async () => { - const { default: defaultExport } = await import(fixtures.fileURL('es-modules/noext-esm')); - strictEqual(defaultExport, 'module'); - }); + it('should import as ESM an extensionless JavaScript file that is outside of any package scope', + async () => { + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/noext-esm')); + strictEqual(defaultExport, 'module'); + }); - it('should import as Wasm an extensionless Wasm file that is outside of any package scope', async () => { - const { add } = await import(fixtures.fileURL('es-modules/noext-wasm')); - strictEqual(add(1, 2), 3); - }); + it('should import as Wasm an extensionless Wasm file that is outside of any package scope', async () => { + const { add } = await import(fixtures.fileURL('es-modules/noext-wasm')); + strictEqual(add(1, 2), 3); + }); - it('should check as ESM input passed via --check', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--check', - fixtures.path('es-modules/loose.js'), - ]); + it('should check as ESM input passed via --check', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--check', + fixtures.path('es-modules/loose.js'), + ]); - strictEqual(stderr, ''); - strictEqual(stdout, ''); - strictEqual(code, 0); - strictEqual(signal, null); - }); -}); + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + strictEqual(signal, null); + }); + }); diff --git a/test/es-module/test-esm-type-flag-package-scopes.mjs b/test/es-module/test-esm-type-flag-package-scopes.mjs index 90b5f1ad2c5047..989d6561f52bab 100644 --- a/test/es-module/test-esm-type-flag-package-scopes.mjs +++ b/test/es-module/test-esm-type-flag-package-scopes.mjs @@ -4,58 +4,63 @@ import * as fixtures from '../common/fixtures.mjs'; import { describe, it } from 'node:test'; import { strictEqual } from 'node:assert'; -describe('the type flag should change the interpretation of certain files within a "type": "module" package scope', { concurrency: true }, () => { - it('should run as ESM an extensionless JavaScript file within a "type": "module" scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-type-module/noext-esm'), - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); - }); - - it('should import an extensionless JavaScript file within a "type": "module" scope', async () => { - const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-type-module/noext-esm')); - strictEqual(defaultExport, 'module'); - }); - - it('should run as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - '--experimental-wasm-modules', - '--no-warnings', - fixtures.path('es-modules/package-type-module/noext-wasm'), - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, ''); - strictEqual(code, 0); - strictEqual(signal, null); - }); - - it('should import as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { - const { add } = await import(fixtures.fileURL('es-modules/package-type-module/noext-wasm')); - strictEqual(add(1, 2), 3); - }); -}); - -describe('the type flag should change the interpretation of certain files within a package scope that lacks a "type" field and is not under node_modules', { concurrency: true }, () => { - it('should run as ESM a .js file within package scope that has no defined "type" and is not under node_modules', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-without-type/module.js'), - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); - }); - - it('should run as ESM an extensionless JavaScript file within a package scope that has no defined "type" and is not under node_modules', async () => { +describe('the type flag should change the interpretation of certain files within a "type": "module" package scope', + { concurrency: true }, () => { + it('should run as ESM an extensionless JavaScript file within a "type": "module" scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/noext-esm'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import an extensionless JavaScript file within a "type": "module" scope', async () => { + const { default: defaultExport } = + await import(fixtures.fileURL('es-modules/package-type-module/noext-esm')); + strictEqual(defaultExport, 'module'); + }); + + it('should run as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--experimental-wasm-modules', + '--no-warnings', + fixtures.path('es-modules/package-type-module/noext-wasm'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, ''); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('should import as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { + const { add } = await import(fixtures.fileURL('es-modules/package-type-module/noext-wasm')); + strictEqual(add(1, 2), 3); + }); + }); + +describe(`the type flag should change the interpretation of certain files within a package scope that lacks a +"type" field and is not under node_modules`, { concurrency: true }, () => { + it('should run as ESM a .js file within package scope that has no defined "type" and is not under node_modules', + async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-without-type/module.js'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it(`should run as ESM an extensionless JavaScript file within a package scope that has no defined "type" and is not +under node_modules`, async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', fixtures.path('es-modules/package-without-type/noext-esm'), @@ -67,7 +72,8 @@ describe('the type flag should change the interpretation of certain files within strictEqual(signal, null); }); - it('should run as Wasm an extensionless Wasm file within a package scope that has no defined "type" and is not under node_modules', async () => { + it(`should run as Wasm an extensionless Wasm file within a package scope that has no defined "type" and is not under + node_modules`, async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', '--experimental-wasm-modules', @@ -81,41 +87,49 @@ describe('the type flag should change the interpretation of certain files within strictEqual(signal, null); }); - it('should import as ESM a .js file within package scope that has no defined "type" and is not under node_modules', async () => { - const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-without-type/module.js')); - strictEqual(defaultExport, 'module'); - }); + it('should import as ESM a .js file within package scope that has no defined "type" and is not under node_modules', + async () => { + const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-without-type/module.js')); + strictEqual(defaultExport, 'module'); + }); - it('should import as ESM an extensionless JavaScript file within a package scope that has no defined "type" and is not under node_modules', async () => { + it(`should import as ESM an extensionless JavaScript file within a package scope that has no defined "type" and is + not under node_modules`, async () => { const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-without-type/noext-esm')); strictEqual(defaultExport, 'module'); }); - it('should import as Wasm an extensionless Wasm file within a package scope that has no defined "type" and is not under node_modules', async () => { + it(`should import as Wasm an extensionless Wasm file within a package scope that has no defined "type" and is not + under node_modules`, async () => { const { add } = await import(fixtures.fileURL('es-modules/noext-wasm')); strictEqual(add(1, 2), 3); }); }); -describe('the type flag should NOT change the interpretation of certain files within a package scope that lacks a "type" field and is under node_modules', { concurrency: true }, () => { - it('should run as CommonJS a .js file within package scope that has no defined "type" and is under node_modules', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', - fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json/run.js'), - ]); - - strictEqual(stderr, ''); - strictEqual(stdout, 'executed\n'); - strictEqual(code, 0); - strictEqual(signal, null); - }); - - it('should import as CommonJS a .js file within a package scope that has no defined "type" and is under node_modules', async () => { - const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/run.js')); +describe(`the type flag should NOT change the interpretation of certain files within a package scope that lacks a +"type" field and is under node_modules`, { concurrency: true }, () => { + it('should run as CommonJS a .js file within package scope that has no defined "type" and is under node_modules', + async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json/run.js'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, 'executed\n'); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it(`should import as CommonJS a .js file within a package scope that has no defined "type" and is under + node_modules`, async () => { + const { default: defaultExport } = + await import(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/run.js')); strictEqual(defaultExport, 42); }); - it('should run as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is under node_modules', async () => { + it(`should run as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is + under node_modules`, async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs'), @@ -127,8 +141,10 @@ describe('the type flag should NOT change the interpretation of certain files wi strictEqual(signal, null); }); - it('should import as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is under node_modules', async () => { - const { default: defaultExport } = await import(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs')); + it(`should import as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and + is under node_modules`, async () => { + const { default: defaultExport } = + await import(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs')); strictEqual(defaultExport, 42); }); }); diff --git a/test/es-module/test-esm-type-flag-string-input.mjs b/test/es-module/test-esm-type-flag-string-input.mjs index cfdcab30217401..5895bdc549e259 100644 --- a/test/es-module/test-esm-type-flag-string-input.mjs +++ b/test/es-module/test-esm-type-flag-string-input.mjs @@ -1,5 +1,4 @@ import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; import { spawn } from 'node:child_process'; import { describe, it } from 'node:test'; import { strictEqual, match } from 'node:assert'; @@ -9,7 +8,7 @@ describe('the type flag should change the interpretation of string input', { con const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ '--experimental-type=module', '--eval', - `import "data:text/javascript,console.log(42)"`, + 'import "data:text/javascript,console.log(42)"', ]); strictEqual(stderr, ''); From d0939f01b42b94e6e02859fc3e9fcb281f019e46 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 25 Sep 2023 20:40:17 -0700 Subject: [PATCH 15/24] Review notes --- lib/internal/modules/run_main.js | 1 + src/node_options.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index 080e56510fbe26..0d8e24cb47a216 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -50,6 +50,7 @@ function shouldUseESMLoader(mainPath) { const { readPackageScope } = require('internal/modules/cjs/loader'); const pkg = readPackageScope(mainPath); + // No need to guard `pkg` as it can only be an object or `false`. return pkg.data?.type === 'module' || getOptionValue('--experimental-type') === 'module'; } diff --git a/src/node_options.h b/src/node_options.h index bc3c15caeffa61..97df4094897483 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -117,8 +117,8 @@ class EnvironmentOptions : public Options { bool experimental_https_modules = false; bool experimental_wasm_modules = false; bool experimental_import_meta_resolve = false; - std::string input_type; - std::string type; + std::string input_type; // Value of --input-type + std::string type; // Value of --experimental-type std::string experimental_policy; std::string experimental_policy_integrity; bool has_policy_integrity_string = false; From 6497dbed7abc7e1192f0c816092eab38298bfca5 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 26 Sep 2023 08:51:23 -0700 Subject: [PATCH 16/24] Apply suggestions from code review Co-authored-by: Matteo Collina Co-authored-by: Ben Noordhuis --- doc/api/cli.md | 2 +- lib/internal/modules/esm/formats.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index a32ef5044316b6..29ecdd3c854be2 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -723,7 +723,7 @@ Define which module system, `module` or `commonjs`, to use for the following: In other words, `--experimental-type=module` flips all the places where Node.js currently defaults to CommonJS to instead default to ECMAScript modules, with -the exception of packages inside `node_modules`. +the exception of packages inside `node_modules`, for backward compatibility reasons. Under `--experimental-type=module` and `--experimental-wasm-modules`, files with no extension will be treated as WebAssembly if they begin with the WebAssembly diff --git a/lib/internal/modules/esm/formats.js b/lib/internal/modules/esm/formats.js index b5b59e2d9dc14b..22517d829355d0 100644 --- a/lib/internal/modules/esm/formats.js +++ b/lib/internal/modules/esm/formats.js @@ -56,7 +56,7 @@ function getFormatOfExtensionlessFile(url) { return 'wasm'; } } finally { - if (fd) { closeSync(fd); } + if (fd !== undefined) { closeSync(fd); } } return 'module'; From 6b1a51541f54458cf0e96a2914d0c1873d039635 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 26 Sep 2023 09:12:16 -0700 Subject: [PATCH 17/24] Allow --input-type to be used with --experimental-type --- doc/api/packages.md | 2 +- doc/node.1 | 2 +- lib/internal/main/check_syntax.js | 3 ++- lib/internal/main/eval_stdin.js | 6 ++++-- lib/internal/main/eval_string.js | 5 +++-- src/node_options.cc | 5 ----- test/es-module/test-esm-type-flag-string-input.mjs | 14 ++++++++++++++ 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/doc/api/packages.md b/doc/api/packages.md index ef0b8bf8f1ad1d..2c3bf536970588 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -94,7 +94,7 @@ one module system or the other based on the value of the as CommonJS when the `package.json` file lacks a `"type"` field, regardless of `--experimental-type`, for backward compatibility.) -* Strings passed in as an argument to `--eval` or piped to `node` via `STDIN`. +* Strings passed in as an argument to `--eval` or piped to `node` via `STDIN`, when `--input-type` is unspecified. This flag currently defaults to `"commonjs"`, but it may change in the future to default to `"module"`. For this reason it is best to be explicit wherever diff --git a/doc/node.1 b/doc/node.1 index caa6eaf6c74fc3..1e1c9ade128dae 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -179,7 +179,7 @@ Use this flag to enable ShadowRealm support. Enable code coverage in the test runner. . .It Fl -experimental-type Ns = Ns Ar type -Interpret as either ES modules or CommonJS modules input via --eval, --print or STDIN; +Interpret as either ES modules or CommonJS modules input via --eval or STDIN, when --input-type is unspecified; .js or extensionless files with no sibling or parent package.json; .js or extensionless files whose nearest parent package.json lacks a "type" field, unless under node_modules. . diff --git a/lib/internal/main/check_syntax.js b/lib/internal/main/check_syntax.js index dc9de45420f692..636ee049b9e922 100644 --- a/lib/internal/main/check_syntax.js +++ b/lib/internal/main/check_syntax.js @@ -60,7 +60,8 @@ function loadESMIfNeeded(cb) { async function checkSyntax(source, filename) { let isModule = true; if (filename === '[stdin]' || filename === '[eval]') { - isModule = getOptionValue('--input-type') === 'module' || getOptionValue('--experimental-type') === 'module'; + isModule = getOptionValue('--input-type') === 'module' || + (getOptionValue('--experimental-type') === 'module' && getOptionValue('--input-type') !== 'commonjs'); } else { const { defaultResolve } = require('internal/modules/esm/resolve'); const { defaultGetFormat } = require('internal/modules/esm/get_format'); diff --git a/lib/internal/main/eval_stdin.js b/lib/internal/main/eval_stdin.js index d457f4f3c1da8d..51f14be0e1afef 100644 --- a/lib/internal/main/eval_stdin.js +++ b/lib/internal/main/eval_stdin.js @@ -25,12 +25,14 @@ readStdin((code) => { const print = getOptionValue('--print'); const loadESM = getOptionValue('--import').length > 0; - if (getOptionValue('--input-type') === 'module' || getOptionValue('--experimental-type') === 'module') + if (getOptionValue('--input-type') === 'module' || + (getOptionValue('--experimental-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) { evalModule(code, print); - else + } else { evalScript('[stdin]', code, getOptionValue('--inspect-brk'), print, loadESM); + } }); diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js index f2b89ead38db3f..90746047df6897 100644 --- a/lib/internal/main/eval_string.js +++ b/lib/internal/main/eval_string.js @@ -25,9 +25,10 @@ markBootstrapComplete(); const source = getOptionValue('--eval'); const print = getOptionValue('--print'); const loadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0; -if (getOptionValue('--input-type') === 'module' || getOptionValue('--experimental-type') === 'module') +if (getOptionValue('--input-type') === 'module' || + (getOptionValue('--experimental-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) { evalModule(source, print); -else { +} else { // For backward compatibility, we want the identifier crypto to be the // `node:crypto` module rather than WebCrypto. const isUsingCryptoIdentifier = diff --git a/src/node_options.cc b/src/node_options.cc index 2fb11564455405..51cd633c0eac86 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -127,11 +127,6 @@ void EnvironmentOptions::CheckOptions(std::vector* errors, } } - if (!input_type.empty() && !type.empty()) { - errors->push_back("--input-type and --experimental-type cannot be used " - "together"); - } - if (syntax_check_only && has_eval_string) { errors->push_back("either --check or --eval can be used, not both"); } diff --git a/test/es-module/test-esm-type-flag-string-input.mjs b/test/es-module/test-esm-type-flag-string-input.mjs index 5895bdc549e259..7a7cb199bea74d 100644 --- a/test/es-module/test-esm-type-flag-string-input.mjs +++ b/test/es-module/test-esm-type-flag-string-input.mjs @@ -27,4 +27,18 @@ describe('the type flag should change the interpretation of string input', { con match((await child.stdout.toArray()).toString(), /^function\r?\n$/); }); + + it('should be overridden by --input-type', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ + '--experimental-type=module', + '--input-type=commonjs', + '--eval', + 'console.log(require("process").version)', + ]); + + strictEqual(stderr, ''); + strictEqual(stdout, `${process.version}\n`); + strictEqual(code, 0); + strictEqual(signal, null); + }); }); From d68200333d5e2a5b335dfdad391a316bbf7c527f Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 26 Sep 2023 09:28:07 -0700 Subject: [PATCH 18/24] Add TODO --- lib/internal/modules/esm/formats.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/internal/modules/esm/formats.js b/lib/internal/modules/esm/formats.js index 22517d829355d0..2d57e74d8f26bd 100644 --- a/lib/internal/modules/esm/formats.js +++ b/lib/internal/modules/esm/formats.js @@ -50,6 +50,7 @@ function getFormatOfExtensionlessFile(url) { const magic = new Uint8Array(4); let fd; try { + // TODO(@anonrig): Optimize the following by having a single C++ call fd = openSync(url); readSync(fd, magic, 0, 4); // Only read the first four bytes if (magic[0] === 0x00 && magic[1] === 0x61 && magic[2] === 0x73 && magic[3] === 0x6d) { From 4c5238dd7725da4ed8c21b83bfe3ecb175d608d2 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 26 Sep 2023 09:40:29 -0700 Subject: [PATCH 19/24] Lint --- doc/api/cli.md | 2 +- doc/api/packages.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 29ecdd3c854be2..2a4f21b990631e 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -723,7 +723,7 @@ Define which module system, `module` or `commonjs`, to use for the following: In other words, `--experimental-type=module` flips all the places where Node.js currently defaults to CommonJS to instead default to ECMAScript modules, with -the exception of packages inside `node_modules`, for backward compatibility reasons. +the exception of packages inside `node_modules`, for backward compatibility. Under `--experimental-type=module` and `--experimental-wasm-modules`, files with no extension will be treated as WebAssembly if they begin with the WebAssembly diff --git a/doc/api/packages.md b/doc/api/packages.md index 2c3bf536970588..d0237b94924034 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -94,7 +94,8 @@ one module system or the other based on the value of the as CommonJS when the `package.json` file lacks a `"type"` field, regardless of `--experimental-type`, for backward compatibility.) -* Strings passed in as an argument to `--eval` or piped to `node` via `STDIN`, when `--input-type` is unspecified. +* Strings passed in as an argument to `--eval` or piped to `node` via `STDIN`, + when `--input-type` is unspecified. This flag currently defaults to `"commonjs"`, but it may change in the future to default to `"module"`. For this reason it is best to be explicit wherever From 3874795798ccc5a9c7e5be0ea78fc7f12e8958a2 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 26 Sep 2023 15:06:11 -0700 Subject: [PATCH 20/24] Ensure that JS that imports Wasm runs --- test/es-module/test-esm-type-flag-package-scopes.mjs | 2 +- test/fixtures/es-modules/package-type-module/wasm-dep.mjs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/es-module/test-esm-type-flag-package-scopes.mjs b/test/es-module/test-esm-type-flag-package-scopes.mjs index 989d6561f52bab..74faf4772ca59f 100644 --- a/test/es-module/test-esm-type-flag-package-scopes.mjs +++ b/test/es-module/test-esm-type-flag-package-scopes.mjs @@ -33,7 +33,7 @@ describe('the type flag should change the interpretation of certain files within ]); strictEqual(stderr, ''); - strictEqual(stdout, ''); + strictEqual(stdout, 'executed\n'); strictEqual(code, 0); strictEqual(signal, null); }); diff --git a/test/fixtures/es-modules/package-type-module/wasm-dep.mjs b/test/fixtures/es-modules/package-type-module/wasm-dep.mjs index 243e1b17d26294..a0e28aa17b6bd9 100644 --- a/test/fixtures/es-modules/package-type-module/wasm-dep.mjs +++ b/test/fixtures/es-modules/package-type-module/wasm-dep.mjs @@ -10,4 +10,6 @@ export let state = 'JS Function Executed'; export function jsInitFn () { strictEqual(state, 'JS Function Executed'); state = 'WASM Start Executed'; -} \ No newline at end of file +} + +console.log('executed'); From f79c9e170c399d203428c86a8dc83f0f65a51f3c Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 26 Sep 2023 15:08:51 -0700 Subject: [PATCH 21/24] Limit node_modules exception to files --- lib/internal/modules/esm/get_format.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 8b2908876ebdfa..fbc79be8dfb58a 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -79,6 +79,8 @@ function extname(url) { * @param {URL} url */ function underNodeModules(url) { + if (url.protocol !== 'file:') { return false; } // We determine module types for other protocols based on MIME header + return StringPrototypeIndexOf(url.pathname, '/node_modules/') !== -1; } From 8e354e0a495ea12fa71892d5d3c922acd5f29157 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 26 Sep 2023 15:43:20 -0700 Subject: [PATCH 22/24] More efficient --- lib/internal/modules/esm/get_format.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index fbc79be8dfb58a..9fa0239630abfd 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -5,7 +5,7 @@ const { ObjectPrototypeHasOwnProperty, PromisePrototypeThen, PromiseResolve, - StringPrototypeIndexOf, + StringPrototypeIncludes, StringPrototypeCharCodeAt, StringPrototypeSlice, } = primordials; @@ -81,7 +81,7 @@ function extname(url) { function underNodeModules(url) { if (url.protocol !== 'file:') { return false; } // We determine module types for other protocols based on MIME header - return StringPrototypeIndexOf(url.pathname, '/node_modules/') !== -1; + return StringPrototypeIncludes(url.pathname, '/node_modules/'); } /** From 3da7932a92251fd3a8fbd4b5f97d3e5088fb1bf2 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Wed, 27 Sep 2023 20:07:55 -0700 Subject: [PATCH 23/24] Rename from --experimental-type to --experimental-default-type --- doc/api/cli.md | 60 ++++++++++--------- doc/api/esm.md | 8 +-- doc/api/packages.md | 6 +- doc/node.1 | 10 ++-- lib/internal/main/check_syntax.js | 2 +- lib/internal/main/eval_stdin.js | 2 +- lib/internal/main/eval_string.js | 2 +- lib/internal/modules/esm/formats.js | 4 +- lib/internal/modules/esm/get_format.js | 6 +- lib/internal/modules/run_main.js | 2 +- src/node_options.cc | 4 +- src/node_options.h | 2 +- test/es-module/test-esm-type-flag-errors.mjs | 6 +- .../test-esm-type-flag-loose-files.mjs | 10 ++-- .../test-esm-type-flag-package-scopes.mjs | 16 ++--- .../test-esm-type-flag-string-input.mjs | 6 +- test/fixtures/es-modules/loose.js | 2 +- .../es-modules/package-without-type/module.js | 2 +- .../es-modules/package-without-type/noext-esm | 2 +- 19 files changed, 77 insertions(+), 75 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 2a4f21b990631e..067d7d1c427d65 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -587,6 +587,36 @@ On Windows, using `cmd.exe` a single quote will not work correctly because it only recognizes double `"` for quoting. In Powershell or Git bash, both `'` and `"` are usable. +### `--experimental-default-type=type` + + + +> Stability: 1.0 - Early development + +Define which module system, `module` or `commonjs`, to use for the following: + +* String input provided via `--eval` or STDIN, if `--input-type` is unspecified. + +* Files ending in `.js` or with no extension, if there is no `package.json` file + present in the same folder or any parent folder. + +* Files ending in `.js` or with no extension, if the nearest parent + `package.json` field lacks a `"type"` field; unless the folder is inside a + `node_modules` folder. + +In other words, `--experimental-default-type=module` flips all the places where +Node.js currently defaults to CommonJS to instead default to ECMAScript modules, +with the exception of packages inside `node_modules`, for backward +compatibility. + +Under `--experimental-default-type=module` and `--experimental-wasm-modules`, +files with no extension will be treated as WebAssembly if they begin with the +WebAssembly magic number (`\0asm`); otherwise they will be treated as ES module +JavaScript. + ### `--experimental-import-meta-resolve` - -> Stability: 1.0 - Early development - -Define which module system, `module` or `commonjs`, to use for the following: - -* String input provided via `--eval` or STDIN, if `--input-type` is unspecified. - -* Files ending in `.js` or with no extension, if there is no `package.json` file - present in the same folder or any parent folder. - -* Files ending in `.js` or with no extension, if the nearest parent - `package.json` field lacks a `"type"` field; unless the folder is inside a - `node_modules` folder. - -In other words, `--experimental-type=module` flips all the places where Node.js -currently defaults to CommonJS to instead default to ECMAScript modules, with -the exception of packages inside `node_modules`, for backward compatibility. - -Under `--experimental-type=module` and `--experimental-wasm-modules`, files with -no extension will be treated as WebAssembly if they begin with the WebAssembly -magic number (`\0asm`); otherwise they will be treated as ES module JavaScript. - ### `--experimental-vm-modules` @@ -1060,7 +1060,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. [URL]: https://url.spec.whatwg.org/ [`"exports"`]: packages.md#exports [`"type"`]: packages.md#type -[`--experimental-type`]: cli.md#--experimental-typetype +[`--experimental-default-type`]: cli.md#--experimental-default-typetype [`--input-type`]: cli.md#--input-typetype [`data:` URLs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs [`export`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export diff --git a/doc/api/packages.md b/doc/api/packages.md index d0237b94924034..9f55cbbb15939f 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -83,7 +83,7 @@ expressions: Aside from these explicit cases, there are other cases where Node.js defaults to one module system or the other based on the value of the -[`--experimental-type`][] flag: +[`--experimental-default-type`][] flag: * Files ending in `.js` or with no extension, if there is no `package.json` file present in the same folder or any parent folder. @@ -92,7 +92,7 @@ one module system or the other based on the value of the `package.json` field lacks a `"type"` field; unless the folder is inside a `node_modules` folder. (Package scopes under `node_modules` are always treated as CommonJS when the `package.json` file lacks a `"type"` field, regardless - of `--experimental-type`, for backward compatibility.) + of `--experimental-default-type`, for backward compatibility.) * Strings passed in as an argument to `--eval` or piped to `node` via `STDIN`, when `--input-type` is unspecified. @@ -1353,7 +1353,7 @@ This field defines [subpath imports][] for the current package. [`"packageManager"`]: #packagemanager [`"type"`]: #type [`--conditions` / `-C` flag]: #resolving-user-conditions -[`--experimental-type`]: cli.md#--experimental-typetype +[`--experimental-default-type`]: cli.md#--experimental-default-typetype [`--no-addons` flag]: cli.md#--no-addons [`ERR_PACKAGE_PATH_NOT_EXPORTED`]: errors.md#err_package_path_not_exported [`esm`]: https://github.com/standard-things/esm#readme diff --git a/doc/node.1 b/doc/node.1 index 1e1c9ade128dae..715cf0fdc4bc2b 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -152,6 +152,11 @@ Requires Node.js to be built with .It Fl -enable-source-maps Enable Source Map V3 support for stack traces. . +.It Fl -experimental-default-type Ns = Ns Ar type +Interpret as either ES modules or CommonJS modules input via --eval or STDIN, when --input-type is unspecified; +.js or extensionless files with no sibling or parent package.json; +.js or extensionless files whose nearest parent package.json lacks a "type" field, unless under node_modules. +. .It Fl -experimental-global-webcrypto Expose the Web Crypto API on the global scope. . @@ -178,11 +183,6 @@ Use this flag to enable ShadowRealm support. .It Fl -experimental-test-coverage Enable code coverage in the test runner. . -.It Fl -experimental-type Ns = Ns Ar type -Interpret as either ES modules or CommonJS modules input via --eval or STDIN, when --input-type is unspecified; -.js or extensionless files with no sibling or parent package.json; -.js or extensionless files whose nearest parent package.json lacks a "type" field, unless under node_modules. -. .It Fl -experimental-websocket Enable experimental support for the WebSocket API. . diff --git a/lib/internal/main/check_syntax.js b/lib/internal/main/check_syntax.js index 636ee049b9e922..9a19c1809fe102 100644 --- a/lib/internal/main/check_syntax.js +++ b/lib/internal/main/check_syntax.js @@ -61,7 +61,7 @@ async function checkSyntax(source, filename) { let isModule = true; if (filename === '[stdin]' || filename === '[eval]') { isModule = getOptionValue('--input-type') === 'module' || - (getOptionValue('--experimental-type') === 'module' && getOptionValue('--input-type') !== 'commonjs'); + (getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs'); } else { const { defaultResolve } = require('internal/modules/esm/resolve'); const { defaultGetFormat } = require('internal/modules/esm/get_format'); diff --git a/lib/internal/main/eval_stdin.js b/lib/internal/main/eval_stdin.js index 51f14be0e1afef..d71751e781b9b5 100644 --- a/lib/internal/main/eval_stdin.js +++ b/lib/internal/main/eval_stdin.js @@ -26,7 +26,7 @@ readStdin((code) => { const print = getOptionValue('--print'); const loadESM = getOptionValue('--import').length > 0; if (getOptionValue('--input-type') === 'module' || - (getOptionValue('--experimental-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) { + (getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) { evalModule(code, print); } else { evalScript('[stdin]', diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js index 90746047df6897..908532b0b1865a 100644 --- a/lib/internal/main/eval_string.js +++ b/lib/internal/main/eval_string.js @@ -26,7 +26,7 @@ const source = getOptionValue('--eval'); const print = getOptionValue('--print'); const loadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0; if (getOptionValue('--input-type') === 'module' || - (getOptionValue('--experimental-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) { + (getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) { evalModule(source, print); } else { // For backward compatibility, we want the identifier crypto to be the diff --git a/lib/internal/modules/esm/formats.js b/lib/internal/modules/esm/formats.js index 2d57e74d8f26bd..b4e8d7a69d306b 100644 --- a/lib/internal/modules/esm/formats.js +++ b/lib/internal/modules/esm/formats.js @@ -39,8 +39,8 @@ function mimeToFormat(mime) { } /** - * For extensionless files in a `module` package scope, or a default `module` scope enabled by the `--experimental-type` - * flag, we check the file contents to disambiguate between ES module JavaScript and Wasm. + * For extensionless files in a `module` package scope, or a default `module` scope enabled by the + * `--experimental-default-type` flag, we check the file contents to disambiguate between ES module JavaScript and Wasm. * We do this by taking advantage of the fact that all Wasm files start with the header `0x00 0x61 0x73 0x6d` (`_asm`). * @param {URL} url */ diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 9fa0239630abfd..59ab89f6f76377 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -19,9 +19,9 @@ const { const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); -const typeFlag = getOptionValue('--experimental-type'); +const defaultTypeFlag = getOptionValue('--experimental-default-type'); // The next line is where we flip the default to ES modules someday. -const defaultType = typeFlag === 'module' ? 'module' : 'commonjs'; +const defaultType = defaultTypeFlag === 'module' ? 'module' : 'commonjs'; const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve'); const { fileURLToPath } = require('internal/url'); const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; @@ -137,7 +137,7 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) { const fileBasename = basename(filepath); const relativePath = StringPrototypeSlice(relative(config.pjsonPath, filepath), 1); suggestion = 'Loading extensionless files is not supported inside of "type":"module" package.json contexts ' + - `without --experimental-type=module. The package.json file ${config.pjsonPath} caused this "type":"module" ` + + `without --experimental-default-type=module. The package.json file ${config.pjsonPath} caused this "type":"module" ` + `context. Try changing ${filepath} to have a file extension. Note the "bin" field of package.json can point ` + `to a file with an extension, for example {"type":"module","bin":{"${fileBasename}":"${relativePath}.js"}}`; } diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index 0d8e24cb47a216..2e4dabd503a883 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -51,7 +51,7 @@ function shouldUseESMLoader(mainPath) { const { readPackageScope } = require('internal/modules/cjs/loader'); const pkg = readPackageScope(mainPath); // No need to guard `pkg` as it can only be an object or `false`. - return pkg.data?.type === 'module' || getOptionValue('--experimental-type') === 'module'; + return pkg.data?.type === 'module' || getOptionValue('--experimental-default-type') === 'module'; } /** diff --git a/src/node_options.cc b/src/node_options.cc index 51cd633c0eac86..cf44003538f42f 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -122,7 +122,7 @@ void EnvironmentOptions::CheckOptions(std::vector* errors, if (!type.empty()) { if (type != "commonjs" && type != "module") { - errors->push_back("--experimental-type must be " + errors->push_back("--experimental-default-type must be " "\"module\" or \"commonjs\""); } } @@ -653,7 +653,7 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "show stack traces on process warnings", &EnvironmentOptions::trace_warnings, kAllowedInEnvvar); - AddOption("--experimental-type", + AddOption("--experimental-default-type", "set module system to use by default", &EnvironmentOptions::type, kAllowedInEnvvar); diff --git a/src/node_options.h b/src/node_options.h index 97df4094897483..eed6216deb67b2 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -118,7 +118,7 @@ class EnvironmentOptions : public Options { bool experimental_wasm_modules = false; bool experimental_import_meta_resolve = false; std::string input_type; // Value of --input-type - std::string type; // Value of --experimental-type + std::string type; // Value of --experimental-default-type std::string experimental_policy; std::string experimental_policy_integrity; bool has_policy_integrity_string = false; diff --git a/test/es-module/test-esm-type-flag-errors.mjs b/test/es-module/test-esm-type-flag-errors.mjs index 270b54c8ab93ff..6d54eff94763ef 100644 --- a/test/es-module/test-esm-type-flag-errors.mjs +++ b/test/es-module/test-esm-type-flag-errors.mjs @@ -3,11 +3,11 @@ import * as fixtures from '../common/fixtures.mjs'; import { describe, it } from 'node:test'; import { match, strictEqual } from 'node:assert'; -describe('--experimental-type=module should not affect the interpretation of files with unknown extensions', +describe('--experimental-default-type=module should not affect the interpretation of files with unknown extensions', { concurrency: true }, () => { it('should error on an entry point with an unknown extension', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', fixtures.path('es-modules/package-type-module/extension.unknown'), ]); @@ -19,7 +19,7 @@ describe('--experimental-type=module should not affect the interpretation of fil it('should error on an import with an unknown extension', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', fixtures.path('es-modules/package-type-module/imports-unknownext.mjs'), ]); diff --git a/test/es-module/test-esm-type-flag-loose-files.mjs b/test/es-module/test-esm-type-flag-loose-files.mjs index 19b9613d55a517..ed95e1807f57c7 100644 --- a/test/es-module/test-esm-type-flag-loose-files.mjs +++ b/test/es-module/test-esm-type-flag-loose-files.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-type=module --experimental-wasm-modules +// Flags: --experimental-default-type=module --experimental-wasm-modules import { spawnPromisified } from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import { describe, it } from 'node:test'; @@ -8,7 +8,7 @@ describe('the type flag should change the interpretation of certain files outsid { concurrency: true }, () => { it('should run as ESM a .js file that is outside of any package scope', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', fixtures.path('es-modules/loose.js'), ]); @@ -20,7 +20,7 @@ describe('the type flag should change the interpretation of certain files outsid it('should run as ESM an extensionless JavaScript file that is outside of any package scope', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', fixtures.path('es-modules/noext-esm'), ]); @@ -32,7 +32,7 @@ describe('the type flag should change the interpretation of certain files outsid it('should run as Wasm an extensionless Wasm file that is outside of any package scope', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', '--experimental-wasm-modules', '--no-warnings', fixtures.path('es-modules/noext-wasm'), @@ -62,7 +62,7 @@ describe('the type flag should change the interpretation of certain files outsid it('should check as ESM input passed via --check', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', '--check', fixtures.path('es-modules/loose.js'), ]); diff --git a/test/es-module/test-esm-type-flag-package-scopes.mjs b/test/es-module/test-esm-type-flag-package-scopes.mjs index 74faf4772ca59f..6b16e8a3ae223b 100644 --- a/test/es-module/test-esm-type-flag-package-scopes.mjs +++ b/test/es-module/test-esm-type-flag-package-scopes.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-type=module --experimental-wasm-modules +// Flags: --experimental-default-type=module --experimental-wasm-modules import { spawnPromisified } from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import { describe, it } from 'node:test'; @@ -8,7 +8,7 @@ describe('the type flag should change the interpretation of certain files within { concurrency: true }, () => { it('should run as ESM an extensionless JavaScript file within a "type": "module" scope', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', fixtures.path('es-modules/package-type-module/noext-esm'), ]); @@ -26,7 +26,7 @@ describe('the type flag should change the interpretation of certain files within it('should run as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', '--experimental-wasm-modules', '--no-warnings', fixtures.path('es-modules/package-type-module/noext-wasm'), @@ -49,7 +49,7 @@ describe(`the type flag should change the interpretation of certain files within it('should run as ESM a .js file within package scope that has no defined "type" and is not under node_modules', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', fixtures.path('es-modules/package-without-type/module.js'), ]); @@ -62,7 +62,7 @@ describe(`the type flag should change the interpretation of certain files within it(`should run as ESM an extensionless JavaScript file within a package scope that has no defined "type" and is not under node_modules`, async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', fixtures.path('es-modules/package-without-type/noext-esm'), ]); @@ -75,7 +75,7 @@ under node_modules`, async () => { it(`should run as Wasm an extensionless Wasm file within a package scope that has no defined "type" and is not under node_modules`, async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', '--experimental-wasm-modules', '--no-warnings', fixtures.path('es-modules/noext-wasm'), @@ -111,7 +111,7 @@ describe(`the type flag should NOT change the interpretation of certain files wi it('should run as CommonJS a .js file within package scope that has no defined "type" and is under node_modules', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json/run.js'), ]); @@ -131,7 +131,7 @@ describe(`the type flag should NOT change the interpretation of certain files wi it(`should run as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is under node_modules`, async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs'), ]); diff --git a/test/es-module/test-esm-type-flag-string-input.mjs b/test/es-module/test-esm-type-flag-string-input.mjs index 7a7cb199bea74d..c4236c00c4f28f 100644 --- a/test/es-module/test-esm-type-flag-string-input.mjs +++ b/test/es-module/test-esm-type-flag-string-input.mjs @@ -6,7 +6,7 @@ import { strictEqual, match } from 'node:assert'; describe('the type flag should change the interpretation of string input', { concurrency: true }, () => { it('should run as ESM input passed via --eval', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', '--eval', 'import "data:text/javascript,console.log(42)"', ]); @@ -21,7 +21,7 @@ describe('the type flag should change the interpretation of string input', { con it('should run as ESM input passed via STDIN', async () => { const child = spawn(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', ]); child.stdin.end('console.log(typeof import.meta.resolve)'); @@ -30,7 +30,7 @@ describe('the type flag should change the interpretation of string input', { con it('should be overridden by --input-type', async () => { const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--experimental-type=module', + '--experimental-default-type=module', '--input-type=commonjs', '--eval', 'console.log(require("process").version)', diff --git a/test/fixtures/es-modules/loose.js b/test/fixtures/es-modules/loose.js index cbee19f3e2fceb..69147a3b8ca027 100644 --- a/test/fixtures/es-modules/loose.js +++ b/test/fixtures/es-modules/loose.js @@ -1,3 +1,3 @@ -// This file can be run or imported only if `--experimental-type=module` is set. +// This file can be run or imported only if `--experimental-default-type=module` is set. export default 'module'; console.log('executed'); diff --git a/test/fixtures/es-modules/package-without-type/module.js b/test/fixtures/es-modules/package-without-type/module.js index cbee19f3e2fceb..69147a3b8ca027 100644 --- a/test/fixtures/es-modules/package-without-type/module.js +++ b/test/fixtures/es-modules/package-without-type/module.js @@ -1,3 +1,3 @@ -// This file can be run or imported only if `--experimental-type=module` is set. +// This file can be run or imported only if `--experimental-default-type=module` is set. export default 'module'; console.log('executed'); diff --git a/test/fixtures/es-modules/package-without-type/noext-esm b/test/fixtures/es-modules/package-without-type/noext-esm index cbee19f3e2fceb..69147a3b8ca027 100644 --- a/test/fixtures/es-modules/package-without-type/noext-esm +++ b/test/fixtures/es-modules/package-without-type/noext-esm @@ -1,3 +1,3 @@ -// This file can be run or imported only if `--experimental-type=module` is set. +// This file can be run or imported only if `--experimental-default-type=module` is set. export default 'module'; console.log('executed'); From 1f93d5e940b81758676e135c3cb8283b7c2c7eda Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Thu, 28 Sep 2023 09:49:49 -0700 Subject: [PATCH 24/24] Clarify --- doc/api/cli.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 067d7d1c427d65..2c4e26dc1e407b 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -604,12 +604,12 @@ Define which module system, `module` or `commonjs`, to use for the following: present in the same folder or any parent folder. * Files ending in `.js` or with no extension, if the nearest parent - `package.json` field lacks a `"type"` field; unless the folder is inside a - `node_modules` folder. + `package.json` field lacks a `"type"` field; unless the `package.json` folder + or any parent folder is inside a `node_modules` folder. In other words, `--experimental-default-type=module` flips all the places where Node.js currently defaults to CommonJS to instead default to ECMAScript modules, -with the exception of packages inside `node_modules`, for backward +with the exception of folders and subfolders below `node_modules`, for backward compatibility. Under `--experimental-default-type=module` and `--experimental-wasm-modules`,