mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
esm: --experimental-default-type flag to flip module defaults
PR-URL: https://github.com/nodejs/node/pull/49869 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
parent
5570c29780
commit
85301803e1
@ -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`
|
||||
|
||||
<!-- YAML
|
||||
added:
|
||||
- REPLACEME
|
||||
-->
|
||||
|
||||
> 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 `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 folders and subfolders below `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`
|
||||
|
||||
<!-- YAML
|
||||
@ -2243,6 +2273,7 @@ Node.js options that are allowed are:
|
||||
* `--enable-network-family-autoselection`
|
||||
* `--enable-source-maps`
|
||||
* `--experimental-abortcontroller`
|
||||
* `--experimental-default-type`
|
||||
* `--experimental-import-meta-resolve`
|
||||
* `--experimental-json-modules`
|
||||
* `--experimental-loader`
|
||||
|
@ -106,10 +106,11 @@ provides interoperability between them and its original module format,
|
||||
|
||||
Node.js has two module systems: [CommonJS][] modules and ECMAScript modules.
|
||||
|
||||
Authors can tell Node.js to use the ECMAScript modules loader
|
||||
via the `.mjs` file extension, the `package.json` [`"type"`][] field, or the
|
||||
[`--input-type`][] flag. Outside of those cases, Node.js will use the CommonJS
|
||||
module loader. See [Determining module system][] for more details.
|
||||
Authors can tell Node.js to use the ECMAScript modules loader via the `.mjs`
|
||||
file extension, the `package.json` [`"type"`][] field, the [`--input-type`][]
|
||||
flag, or the [`--experimental-default-type`][] flag. Outside of those cases,
|
||||
Node.js will use the CommonJS module loader. See [Determining module system][]
|
||||
for more details.
|
||||
|
||||
<!-- Anchors to make sure old links find a target -->
|
||||
|
||||
@ -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-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
|
||||
|
@ -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,30 @@ 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-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.
|
||||
|
||||
* 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-default-type`, for backward compatibility.)
|
||||
|
||||
* 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
|
||||
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 +1353,7 @@ This field defines [subpath imports][] for the current package.
|
||||
[`"packageManager"`]: #packagemanager
|
||||
[`"type"`]: #type
|
||||
[`--conditions` / `-C` flag]: #resolving-user-conditions
|
||||
[`--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
|
||||
|
@ -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.
|
||||
.
|
||||
|
@ -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';
|
||||
isModule = getOptionValue('--input-type') === 'module' ||
|
||||
(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');
|
||||
|
@ -25,12 +25,14 @@ readStdin((code) => {
|
||||
|
||||
const print = getOptionValue('--print');
|
||||
const loadESM = getOptionValue('--import').length > 0;
|
||||
if (getOptionValue('--input-type') === 'module')
|
||||
if (getOptionValue('--input-type') === 'module' ||
|
||||
(getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) {
|
||||
evalModule(code, print);
|
||||
else
|
||||
} else {
|
||||
evalScript('[stdin]',
|
||||
code,
|
||||
getOptionValue('--inspect-brk'),
|
||||
print,
|
||||
loadESM);
|
||||
}
|
||||
});
|
||||
|
@ -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')
|
||||
if (getOptionValue('--input-type') === 'module' ||
|
||||
(getOptionValue('--experimental-default-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 =
|
||||
|
@ -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,33 @@ function mimeToFormat(mime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function getFormatOfExtensionlessFile(url) {
|
||||
if (!experimentalWasmModules) { return 'module'; }
|
||||
|
||||
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) {
|
||||
return 'wasm';
|
||||
}
|
||||
} finally {
|
||||
if (fd !== undefined) { closeSync(fd); }
|
||||
}
|
||||
|
||||
return 'module';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extensionFormatMap,
|
||||
getFormatOfExtensionlessFile,
|
||||
mimeToFormat,
|
||||
};
|
||||
|
@ -1,9 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
RegExpPrototypeExec,
|
||||
ObjectPrototypeHasOwnProperty,
|
||||
PromisePrototypeThen,
|
||||
PromiseResolve,
|
||||
StringPrototypeIncludes,
|
||||
StringPrototypeCharCodeAt,
|
||||
StringPrototypeSlice,
|
||||
} = primordials;
|
||||
@ -11,11 +13,15 @@ const { basename, relative } = require('path');
|
||||
const { getOptionValue } = require('internal/options');
|
||||
const {
|
||||
extensionFormatMap,
|
||||
getFormatOfExtensionlessFile,
|
||||
mimeToFormat,
|
||||
} = require('internal/modules/esm/formats');
|
||||
|
||||
const experimentalNetworkImports =
|
||||
getOptionValue('--experimental-network-imports');
|
||||
const defaultTypeFlag = getOptionValue('--experimental-default-type');
|
||||
// The next line is where we flip the default to ES modules someday.
|
||||
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;
|
||||
@ -66,6 +72,18 @@ 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) {
|
||||
if (url.protocol !== 'file:') { return false; } // We determine module types for other protocols based on MIME header
|
||||
|
||||
return StringPrototypeIncludes(url.pathname, '/node_modules/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {URL} url
|
||||
* @param {{parentURL: string}} context
|
||||
@ -74,8 +92,37 @@ function extname(url) {
|
||||
*/
|
||||
function getFileProtocolModuleFormat(url, context, ignoreErrors) {
|
||||
const ext = extname(url);
|
||||
|
||||
if (ext === '.js') {
|
||||
return getPackageType(url) === 'module' ? 'module' : 'commonjs';
|
||||
const packageType = getPackageType(url);
|
||||
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 (defaultType === 'commonjs') { // Legacy behavior
|
||||
if (packageType === 'none' || packageType === 'commonjs') {
|
||||
return 'commonjs';
|
||||
}
|
||||
// If package type is `module`, fall through to the error case below
|
||||
} 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'
|
||||
return 'commonjs';
|
||||
}
|
||||
}
|
||||
|
||||
const format = extensionFormatMap[ext];
|
||||
@ -89,12 +136,10 @@ 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" ` +
|
||||
'field of package.json can point to a file with an extension, for example ' +
|
||||
`{"type":"module","bin":{"${fileBasename}":"${relativePath}.js"}}`;
|
||||
suggestion = 'Loading extensionless files is not supported inside of "type":"module" package.json contexts ' +
|
||||
`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"}}`;
|
||||
}
|
||||
throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath, suggestion);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ const preserveSymlinks = getOptionValue('--preserve-symlinks');
|
||||
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
|
||||
const experimentalNetworkImports =
|
||||
getOptionValue('--experimental-network-imports');
|
||||
const typeFlag = getOptionValue('--input-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 +1112,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);
|
||||
|
@ -43,12 +43,15 @@ 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';
|
||||
// No need to guard `pkg` as it can only be an object or `false`.
|
||||
return pkg.data?.type === 'module' || getOptionValue('--experimental-default-type') === 'module';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,12 +114,19 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* 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-default-type must be "
|
||||
"\"module\" or \"commonjs\"");
|
||||
}
|
||||
}
|
||||
|
||||
if (syntax_check_only && has_eval_string) {
|
||||
errors->push_back("either --check or --eval can be used, not both");
|
||||
}
|
||||
@ -474,7 +481,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);
|
||||
@ -646,6 +653,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
||||
"show stack traces on process warnings",
|
||||
&EnvironmentOptions::trace_warnings,
|
||||
kAllowedInEnvvar);
|
||||
AddOption("--experimental-default-type",
|
||||
"set module system to use by default",
|
||||
&EnvironmentOptions::type,
|
||||
kAllowedInEnvvar);
|
||||
AddOption("--extra-info-on-fatal-exception",
|
||||
"hide extra information on fatal exception that causes exit",
|
||||
&EnvironmentOptions::extra_info_on_fatal_exception,
|
||||
|
@ -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; // Value of --input-type
|
||||
std::string type; // Value of --experimental-default-type
|
||||
std::string experimental_policy;
|
||||
std::string experimental_policy_integrity;
|
||||
bool has_policy_integrity_string = false;
|
||||
|
3
test/common/package.json
Normal file
3
test/common/package.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
31
test/es-module/test-esm-type-flag-errors.mjs
Normal file
31
test/es-module/test-esm-type-flag-errors.mjs
Normal file
@ -0,0 +1,31 @@
|
||||
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-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-default-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-default-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);
|
||||
});
|
||||
});
|
75
test/es-module/test-esm-type-flag-loose-files.mjs
Normal file
75
test/es-module/test-esm-type-flag-loose-files.mjs
Normal file
@ -0,0 +1,75 @@
|
||||
// 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';
|
||||
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-default-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-default-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-default-type=module',
|
||||
'--experimental-wasm-modules',
|
||||
'--no-warnings',
|
||||
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 { 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 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-default-type=module',
|
||||
'--check',
|
||||
fixtures.path('es-modules/loose.js'),
|
||||
]);
|
||||
|
||||
strictEqual(stderr, '');
|
||||
strictEqual(stdout, '');
|
||||
strictEqual(code, 0);
|
||||
strictEqual(signal, null);
|
||||
});
|
||||
});
|
150
test/es-module/test-esm-type-flag-package-scopes.mjs
Normal file
150
test/es-module/test-esm-type-flag-package-scopes.mjs
Normal file
@ -0,0 +1,150 @@
|
||||
// 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';
|
||||
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-default-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-default-type=module',
|
||||
'--experimental-wasm-modules',
|
||||
'--no-warnings',
|
||||
fixtures.path('es-modules/package-type-module/noext-wasm'),
|
||||
]);
|
||||
|
||||
strictEqual(stderr, '');
|
||||
strictEqual(stdout, 'executed\n');
|
||||
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-default-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-default-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-default-type=module',
|
||||
'--experimental-wasm-modules',
|
||||
'--no-warnings',
|
||||
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 { 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 { 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 { 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-default-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 () => {
|
||||
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
|
||||
'--experimental-default-type=module',
|
||||
fixtures.path('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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
44
test/es-module/test-esm-type-flag-string-input.mjs
Normal file
44
test/es-module/test-esm-type-flag-string-input.mjs
Normal file
@ -0,0 +1,44 @@
|
||||
import { spawnPromisified } from '../common/index.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-default-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-default-type=module',
|
||||
]);
|
||||
child.stdin.end('console.log(typeof import.meta.resolve)');
|
||||
|
||||
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-default-type=module',
|
||||
'--input-type=commonjs',
|
||||
'--eval',
|
||||
'console.log(require("process").version)',
|
||||
]);
|
||||
|
||||
strictEqual(stderr, '');
|
||||
strictEqual(stdout, `${process.version}\n`);
|
||||
strictEqual(code, 0);
|
||||
strictEqual(signal, null);
|
||||
});
|
||||
});
|
@ -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/);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
1
test/fixtures/es-modules/imports-loose.mjs
vendored
Normal file
1
test/fixtures/es-modules/imports-loose.mjs
vendored
Normal file
@ -0,0 +1 @@
|
||||
import './loose.js';
|
1
test/fixtures/es-modules/imports-noext.mjs
vendored
Normal file
1
test/fixtures/es-modules/imports-noext.mjs
vendored
Normal file
@ -0,0 +1 @@
|
||||
import './noext-esm';
|
3
test/fixtures/es-modules/loose.js
vendored
Normal file
3
test/fixtures/es-modules/loose.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// This file can be run or imported only if `--experimental-default-type=module` is set.
|
||||
export default 'module';
|
||||
console.log('executed');
|
2
test/fixtures/es-modules/noext-esm
vendored
Normal file
2
test/fixtures/es-modules/noext-esm
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export default 'module';
|
||||
console.log('executed');
|
BIN
test/fixtures/es-modules/noext-wasm
vendored
Normal file
BIN
test/fixtures/es-modules/noext-wasm
vendored
Normal file
Binary file not shown.
@ -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;
|
||||
|
2
test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/dep.js
generated
vendored
Normal file
2
test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/dep.js
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
// Controlling package.json has no "type" field -> should still be CommonJS as it is in node_modules
|
||||
module.exports = 42;
|
3
test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs
generated
vendored
Normal file
3
test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// No package.json -> should still be CommonJS as it is in node_modules
|
||||
module.exports = 42;
|
||||
console.log('executed');
|
7
test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/package.json
generated
vendored
Normal file
7
test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/package.json
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "dep-with-package-json",
|
||||
"version": "1.0.0",
|
||||
"exports": {
|
||||
"./*": "./*"
|
||||
}
|
||||
}
|
3
test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/run.js
generated
vendored
Normal file
3
test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json/run.js
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// No package.json -> should still be CommonJS as it is in node_modules
|
||||
module.exports = 42;
|
||||
console.log('executed');
|
3
test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/noext-cjs
generated
vendored
Normal file
3
test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/noext-cjs
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// No package.json -> should still be CommonJS as it is in node_modules
|
||||
module.exports = 42;
|
||||
console.log('executed');
|
3
test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/run.js
generated
vendored
Normal file
3
test/fixtures/es-modules/package-type-module/node_modules/dep-without-package-json/run.js
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// No package.json -> should still be CommonJS as it is in node_modules
|
||||
module.exports = 42;
|
||||
console.log('executed');
|
BIN
test/fixtures/es-modules/package-type-module/noext-wasm
vendored
Normal file
BIN
test/fixtures/es-modules/package-type-module/noext-wasm
vendored
Normal file
Binary file not shown.
15
test/fixtures/es-modules/package-type-module/wasm-dep.mjs
vendored
Normal file
15
test/fixtures/es-modules/package-type-module/wasm-dep.mjs
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
import { strictEqual } from 'assert';
|
||||
|
||||
export function jsFn () {
|
||||
state = 'WASM JS Function Executed';
|
||||
return 42;
|
||||
}
|
||||
|
||||
export let state = 'JS Function Executed';
|
||||
|
||||
export function jsInitFn () {
|
||||
strictEqual(state, 'JS Function Executed');
|
||||
state = 'WASM Start Executed';
|
||||
}
|
||||
|
||||
console.log('executed');
|
3
test/fixtures/es-modules/package-without-type/module.js
vendored
Normal file
3
test/fixtures/es-modules/package-without-type/module.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// This file can be run or imported only if `--experimental-default-type=module` is set.
|
||||
export default 'module';
|
||||
console.log('executed');
|
3
test/fixtures/es-modules/package-without-type/noext-esm
vendored
Normal file
3
test/fixtures/es-modules/package-without-type/noext-esm
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// This file can be run or imported only if `--experimental-default-type=module` is set.
|
||||
export default 'module';
|
||||
console.log('executed');
|
Loading…
Reference in New Issue
Block a user