esm: replace --entry-type with --input-type

New flag is for string input only

PR-URL: https://github.com/nodejs/node/pull/27184
Reviewed-By: Jan Krems <jan.krems@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
This commit is contained in:
Geoffrey Booth 2019-04-15 10:29:56 -07:00 committed by Myles Borins
parent f85ef977e6
commit 96e46d37c4
No known key found for this signature in database
GPG Key ID: 933B01F40B5CA946
17 changed files with 119 additions and 130 deletions

View File

@ -134,19 +134,6 @@ added: v6.0.0
Enable FIPS-compliant crypto at startup. (Requires Node.js to be built with Enable FIPS-compliant crypto at startup. (Requires Node.js to be built with
`./configure --openssl-fips`.) `./configure --openssl-fips`.)
### `--entry-type=type`
<!-- YAML
added: REPLACEME
-->
Used with `--experimental-modules`, this configures Node.js to interpret the
initial entry point as CommonJS or as an ES module.
Valid values are `"commonjs"` and `"module"`. The default is to infer from
the file extension and the `"type"` field in the nearest parent `package.json`.
Works for executing a file as well as `--eval`, `--print`, `STDIN`.
### `--es-module-specifier-resolution=mode` ### `--es-module-specifier-resolution=mode`
<!-- YAML <!-- YAML
added: REPLACEME added: REPLACEME
@ -261,6 +248,17 @@ added: v0.11.15
Specify ICU data load path. (Overrides `NODE_ICU_DATA`.) Specify ICU data load path. (Overrides `NODE_ICU_DATA`.)
### `--input-type=type`
<!-- YAML
added: REPLACEME
-->
Used with `--experimental-modules`, this configures Node.js to interpret string
input as CommonJS or as an ES module. String input is input via `--eval`,
`--print`, or `STDIN`.
Valid values are `"commonjs"` and `"module"`. The default is `"commonjs"`.
### `--inspect-brk[=[host:]port]` ### `--inspect-brk[=[host:]port]`
<!-- YAML <!-- YAML
added: v7.6.0 added: v7.6.0

View File

@ -854,18 +854,6 @@ provided.
Encoding provided to `TextDecoder()` API was not one of the Encoding provided to `TextDecoder()` API was not one of the
[WHATWG Supported Encodings][]. [WHATWG Supported Encodings][].
<a id="ERR_ENTRY_TYPE_MISMATCH"></a>
#### ERR_ENTRY_TYPE_MISMATCH
> Stability: 1 - Experimental
The `--entry-type=commonjs` flag was used to attempt to execute an `.mjs` file
or a `.js` file where the nearest parent `package.json` contains
`"type": "module"`; or
the `--entry-type=module` flag was used to attempt to execute a `.cjs` file or
a `.js` file where the nearest parent `package.json` either lacks a `"type"`
field or contains `"type": "commonjs"`.
<a id="ERR_FALSY_VALUE_REJECTION"></a> <a id="ERR_FALSY_VALUE_REJECTION"></a>
### ERR_FALSY_VALUE_REJECTION ### ERR_FALSY_VALUE_REJECTION
@ -1166,6 +1154,14 @@ is set for the `Http2Stream`.
An option pair is incompatible with each other and can not be used at the same An option pair is incompatible with each other and can not be used at the same
time. time.
<a id="ERR_INPUT_TYPE_NOT_ALLOWED"></a>
### ERR_INPUT_TYPE_NOT_ALLOWED
> Stability: 1 - Experimental
The `--input-type` flag was used to attempt to execute a file. This flag can
only be used with input via `--eval`, `--print` or `STDIN`.
<a id="ERR_INSPECTOR_ALREADY_CONNECTED"></a> <a id="ERR_INSPECTOR_ALREADY_CONNECTED"></a>
### ERR_INSPECTOR_ALREADY_CONNECTED ### ERR_INSPECTOR_ALREADY_CONNECTED
@ -2223,6 +2219,18 @@ closed.
These errors have never been released, but had been present on master between These errors have never been released, but had been present on master between
releases. releases.
<a id="ERR_ENTRY_TYPE_MISMATCH"></a>
#### ERR_ENTRY_TYPE_MISMATCH
> Stability: 1 - Experimental
The `--entry-type=commonjs` flag was used to attempt to execute an `.mjs` file
or a `.js` file where the nearest parent `package.json` contains
`"type": "module"`; or
the `--entry-type=module` flag was used to attempt to execute a `.cjs` file or
a `.js` file where the nearest parent `package.json` either lacks a `"type"`
field or contains `"type": "commonjs"`.
<a id="ERR_FS_WATCHER_ALREADY_STARTED"></a> <a id="ERR_FS_WATCHER_ALREADY_STARTED"></a>
#### ERR_FS_WATCHER_ALREADY_STARTED #### ERR_FS_WATCHER_ALREADY_STARTED

View File

@ -30,45 +30,37 @@ specifier resolution, and default behavior.
The `--experimental-modules` flag can be used to enable support for The `--experimental-modules` flag can be used to enable support for
ECMAScript modules (ES modules). ECMAScript modules (ES modules).
## Running Node.js with an ECMAScript Module Once enabled, Node.js will treat the following as ES modules when passed to
`node` as the initial input, or when referenced by `import` statements within
ES module code:
There are a few ways to start Node.js with an ES module as its input. - Files ending in `.mjs`.
### Initial entry point with an <code>.mjs</code> extension - Files ending in `.js`, or extensionless files, when the nearest parent
`package.json` file contains a top-level field `"type"` with a value of
`"module"`.
A file ending with `.mjs` passed to Node.js as an initial entry point will be - Strings passed in as an argument to `--eval` or `--print`, or piped to
loaded as an ES module. `node` via `STDIN`, with the flag `--input-type=module`.
```sh Node.js will treat as CommonJS all other forms of input, such as `.js` files
node --experimental-modules my-app.mjs 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 within ES module code:
### <code>--entry-type=module</code> flag - Files ending in `.cjs`.
Files ending with `.js` or `.mjs`, or lacking any extension, - Files ending in `.js`, or extensionless files, when the nearest parent
will be loaded as ES modules when the `--entry-type=module` flag is set. `package.json` file contains a top-level field `"type"` with a value of
`"commonjs"`.
```sh - Strings passed in as an argument to `--eval` or `--print`, or piped to
node --experimental-modules --entry-type=module my-app.js `node` via `STDIN`, with the flag `--input-type=commonjs`.
```
For completeness there is also `--entry-type=commonjs`, for explicitly running ## <code>package.json</code> <code>"type"</code> field
a `.js` file as CommonJS. This is the default behavior if `--entry-type` is
unspecified.
The `--entry-type=module` flag can also be used to configure Node.js to treat
as an ES module input sent in via `--eval` or `--print` (or `-e` or `-p`) or
piped to Node.js via `STDIN`.
```sh
node --experimental-modules --entry-type=module --eval \
"import { sep } from 'path'; console.log(sep);"
echo "import { sep } from 'path'; console.log(sep);" | \
node --experimental-modules --entry-type=module
```
### <code>package.json</code> <code>"type"</code> field
Files ending with `.js` or `.mjs`, or lacking any extension, Files ending with `.js` or `.mjs`, or lacking any extension,
will be loaded as ES modules when the nearest parent `package.json` file will be loaded as ES modules when the nearest parent `package.json` file
@ -97,6 +89,14 @@ If the volume root is reached and no `package.json` is found,
Node.js defers to the default, a `package.json` with no `"type"` Node.js defers to the default, a `package.json` with no `"type"`
field. field.
`import` statements of `.js` and extensionless files are treated as ES modules
if the nearest parent `package.json` contains `"type": "module"`.
```js
// my-app.js, part of the same example as above
import './startup.js'; // Loaded as ES module because of package.json
```
## Package Scope and File Extensions ## Package Scope and File Extensions
A folder containing a `package.json` file, and all subfolders below that A folder containing a `package.json` file, and all subfolders below that
@ -156,6 +156,24 @@ package scope:
extension (since both `.js` and `.cjs` files are treated as CommonJS within a extension (since both `.js` and `.cjs` files are treated as CommonJS within a
`"commonjs"` package scope). `"commonjs"` package scope).
## <code>--input-type</code> flag
Strings passed in as an argument to `--eval` or `--print` (or `-e` or `-p`), or
piped to `node` via `STDIN`, will be treated as ES modules when the
`--input-type=module` flag is set.
```sh
node --experimental-modules --input-type=module --eval \
"import { sep } from 'path'; console.log(sep);"
echo "import { sep } from 'path'; console.log(sep);" | \
node --experimental-modules --input-type=module
```
For completeness there is also `--input-type=commonjs`, for explicitly running
string input as CommonJS. This is the default behavior if `--input-type` is
unspecified.
## Package Entry Points ## Package Entry Points
The `package.json` `"main"` field defines the entry point for a package, The `package.json` `"main"` field defines the entry point for a package,

View File

@ -119,9 +119,6 @@ Enable FIPS-compliant crypto at startup.
Requires Node.js to be built with Requires Node.js to be built with
.Sy ./configure --openssl-fips . .Sy ./configure --openssl-fips .
. .
.It Fl -entry-type Ns = Ns Ar type
Set the top-level module resolution type.
.
.It Fl -es-module-specifier-resolution .It Fl -es-module-specifier-resolution
Select extension resolution algorithm for ES Modules; either 'explicit' (default) or 'node' Select extension resolution algorithm for ES Modules; either 'explicit' (default) or 'node'
. .
@ -170,6 +167,9 @@ Specify ICU data load path.
Overrides Overrides
.Ev NODE_ICU_DATA . .Ev NODE_ICU_DATA .
. .
.It Fl -input-type Ns = Ns Ar type
Set the module resolution type for input via --eval, --print or STDIN.
.
.It Fl -inspect-brk Ns = Ns Ar [host:]port .It Fl -inspect-brk Ns = Ns Ar [host:]port
Activate inspector on Activate inspector on
.Ar host:port .Ar host:port

View File

@ -679,24 +679,6 @@ E('ERR_ENCODING_INVALID_ENCODED_DATA', function(encoding, ret) {
}, TypeError); }, TypeError);
E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported', E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported',
RangeError); RangeError);
E('ERR_ENTRY_TYPE_MISMATCH', (filename, ext, typeFlag, conflict) => {
const typeString =
typeFlag === 'module' ? '--entry-type=module' : '--entry-type=commonjs';
// --entry-type mismatches file extension
if (conflict === 'extension') {
return `Extension ${ext} is not supported for ` +
`${typeString} loading ${filename}`;
}
assert(
conflict === 'scope',
'"conflict" value unknown. Set this argument to "extension" or "scope"'
);
// --entry-type mismatches package.json "type"
return `Cannot use ${typeString} because nearest parent package.json ` +
((typeFlag === 'module') ?
'includes "type": "commonjs"' : 'includes "type": "module",') +
` which controls the type to use for ${filename}`;
}, TypeError);
E('ERR_FALSY_VALUE_REJECTION', function(reason) { E('ERR_FALSY_VALUE_REJECTION', function(reason) {
this.reason = reason; this.reason = reason;
return 'Promise was rejected with falsy value'; return 'Promise was rejected with falsy value';
@ -809,6 +791,8 @@ E('ERR_HTTP_TRAILER_INVALID',
'Trailers are invalid with this transfer encoding', Error); 'Trailers are invalid with this transfer encoding', Error);
E('ERR_INCOMPATIBLE_OPTION_PAIR', E('ERR_INCOMPATIBLE_OPTION_PAIR',
'Option "%s" can not be used in combination with option "%s"', TypeError); 'Option "%s" can not be used in combination with option "%s"', TypeError);
E('ERR_INPUT_TYPE_NOT_ALLOWED', '--input-type can only be used with string ' +
'input via --eval, --print, or STDIN', Error);
E('ERR_INSPECTOR_ALREADY_CONNECTED', '%s is already connected', Error); E('ERR_INSPECTOR_ALREADY_CONNECTED', '%s is already connected', Error);
E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error); E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error);
E('ERR_INSPECTOR_COMMAND', 'Inspector error %d: %s', Error); E('ERR_INSPECTOR_COMMAND', 'Inspector error %d: %s', Error);

View File

@ -60,7 +60,7 @@ function checkSyntax(source, filename) {
if (experimentalModules) { if (experimentalModules) {
let isModule = false; let isModule = false;
if (filename === '[stdin]' || filename === '[eval]') { if (filename === '[stdin]' || filename === '[eval]') {
isModule = getOptionValue('--entry-type') === 'module'; isModule = getOptionValue('--input-type') === 'module';
} else { } else {
const resolve = require('internal/modules/esm/default_resolve'); const resolve = require('internal/modules/esm/default_resolve');
const { format } = resolve(pathToFileURL(filename).toString()); const { format } = resolve(pathToFileURL(filename).toString());

View File

@ -17,7 +17,7 @@ markBootstrapComplete();
readStdin((code) => { readStdin((code) => {
process._eval = code; process._eval = code;
if (require('internal/options').getOptionValue('--entry-type') === 'module') if (require('internal/options').getOptionValue('--input-type') === 'module')
evalModule(process._eval); evalModule(process._eval);
else else
evalScript('[stdin]', process._eval, process._breakFirstLine); evalScript('[stdin]', process._eval, process._breakFirstLine);

View File

@ -14,7 +14,7 @@ const source = getOptionValue('--eval');
prepareMainThreadExecution(); prepareMainThreadExecution();
addBuiltinLibsToObject(global); addBuiltinLibsToObject(global);
markBootstrapComplete(); markBootstrapComplete();
if (getOptionValue('--entry-type') === 'module') if (getOptionValue('--input-type') === 'module')
evalModule(source); evalModule(source);
else else
evalScript('[eval]', source, process._breakFirstLine); evalScript('[eval]', source, process._breakFirstLine);

View File

@ -15,11 +15,11 @@ const console = require('internal/console/global');
prepareMainThreadExecution(); prepareMainThreadExecution();
// --entry-type flag not supported in REPL // --input-type flag not supported in REPL
if (require('internal/options').getOptionValue('--entry-type')) { if (require('internal/options').getOptionValue('--input-type')) {
// If we can't write to stderr, we'd like to make this a noop, // If we can't write to stderr, we'd like to make this a noop,
// so use console.error. // so use console.error.
console.error('Cannot specify --entry-type for REPL'); console.error('Cannot specify --input-type for REPL');
process.exit(1); process.exit(1);
} }

View File

@ -9,12 +9,12 @@ const { getOptionValue } = require('internal/options');
const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalJsonModules = getOptionValue('--experimental-json-modules'); const experimentalJsonModules = getOptionValue('--experimental-json-modules');
const typeFlag = getOptionValue('--entry-type'); const typeFlag = getOptionValue('--input-type');
const { resolve: moduleWrapResolve, const { resolve: moduleWrapResolve,
getPackageType } = internalBinding('module_wrap'); getPackageType } = internalBinding('module_wrap');
const { pathToFileURL, fileURLToPath } = require('internal/url'); const { pathToFileURL, fileURLToPath } = require('internal/url');
const { ERR_ENTRY_TYPE_MISMATCH, const { ERR_INPUT_TYPE_NOT_ALLOWED,
ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
const { const {
@ -25,7 +25,7 @@ const {
const realpathCache = new SafeMap(); const realpathCache = new SafeMap();
// const TYPE_NONE = 0; // const TYPE_NONE = 0;
const TYPE_COMMONJS = 1; // const TYPE_COMMONJS = 1;
const TYPE_MODULE = 2; const TYPE_MODULE = 2;
const extensionFormatMap = { const extensionFormatMap = {
@ -86,26 +86,16 @@ function resolve(specifier, parentURL) {
let format = extMap[ext]; let format = extMap[ext];
if (isMain && typeFlag) { if (isMain && typeFlag) {
// Conflict between explicit extension (.mjs, .cjs) and --entry-type // This is the initial entry point to the program, and --input-type has
if (ext === '.cjs' && typeFlag === 'module' || // been passed as an option; but --input-type can only be used with
ext === '.mjs' && typeFlag === 'commonjs') { // --eval, --print or STDIN string input. It is not allowed with file
throw new ERR_ENTRY_TYPE_MISMATCH( // input, to avoid user confusion over how expansive the effect of the
fileURLToPath(url), ext, typeFlag, 'extension'); // flag should be (i.e. entry point only, package scope surrounding the
} // entry point, etc.).
throw new ERR_INPUT_TYPE_NOT_ALLOWED();
// Conflict between package scope type and --entry-type
if (ext === '.js') {
if (type === TYPE_MODULE && typeFlag === 'commonjs' ||
type === TYPE_COMMONJS && typeFlag === 'module') {
throw new ERR_ENTRY_TYPE_MISMATCH(
fileURLToPath(url), ext, typeFlag, 'scope');
}
}
} }
if (!format) { if (!format) {
if (isMain && typeFlag) if (isMain)
format = typeFlag;
else if (isMain)
format = type === TYPE_MODULE ? 'module' : 'commonjs'; format = type === TYPE_MODULE ? 'module' : 'commonjs';
else else
throw new ERR_UNKNOWN_FILE_EXTENSION(fileURLToPath(url), throw new ERR_UNKNOWN_FILE_EXTENSION(fileURLToPath(url),

View File

@ -109,11 +109,11 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
if (!module_type.empty()) { if (!module_type.empty()) {
if (!experimental_modules) { if (!experimental_modules) {
errors->push_back("--entry-type requires " errors->push_back("--input-type requires "
"--experimental-modules to be enabled"); "--experimental-modules to be enabled");
} }
if (module_type != "commonjs" && module_type != "module") { if (module_type != "commonjs" && module_type != "module") {
errors->push_back("--entry-type must be \"module\" or \"commonjs\""); errors->push_back("--input-type must be \"module\" or \"commonjs\"");
} }
} }
@ -289,15 +289,15 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"(default: llhttp).", "(default: llhttp).",
&EnvironmentOptions::http_parser, &EnvironmentOptions::http_parser,
kAllowedInEnvironment); kAllowedInEnvironment);
AddOption("--input-type",
"set module type for string input",
&EnvironmentOptions::module_type,
kAllowedInEnvironment);
AddOption("--loader", AddOption("--loader",
"(with --experimental-modules) use the specified file as a " "(with --experimental-modules) use the specified file as a "
"custom loader", "custom loader",
&EnvironmentOptions::userland_loader, &EnvironmentOptions::userland_loader,
kAllowedInEnvironment); kAllowedInEnvironment);
AddOption("--entry-type",
"set module type name of the entry point",
&EnvironmentOptions::module_type,
kAllowedInEnvironment);
AddOption("--es-module-specifier-resolution", AddOption("--es-module-specifier-resolution",
"Select extension resolution algorithm for es modules; " "Select extension resolution algorithm for es modules; "
"either 'explicit' (default) or 'node'", "either 'explicit' (default) or 'node'",

View File

@ -5,14 +5,13 @@ const fixtures = require('../common/fixtures');
const { spawn } = require('child_process'); const { spawn } = require('child_process');
const assert = require('assert'); const assert = require('assert');
const entry = fixtures.path('/es-modules/noext-esm'); const entry = fixtures.path('/es-modules/package-type-module/noext-esm');
// Run a module that does not have extension // Run a module that does not have extension.
// This is to ensure the --entry-type works as expected // This is to ensure that "type": "module" applies to extensionless files.
const child = spawn(process.execPath, [ const child = spawn(process.execPath, [
'--experimental-modules', '--experimental-modules',
'--entry-type=module',
entry entry
]); ]);

View File

@ -19,17 +19,9 @@ expect('', packageTypeModuleMain, 'package-type-module');
expect('', packageTypeCommonJsMain, 'package-type-commonjs'); expect('', packageTypeCommonJsMain, 'package-type-commonjs');
expect('', packageWithoutTypeMain, 'package-without-type'); expect('', packageWithoutTypeMain, 'package-without-type');
// Check that running with --entry-type and no package.json "type" works // Check that --input-type isn't allowed for files
expect('--entry-type=commonjs', packageWithoutTypeMain, 'package-without-type'); expect('--input-type=module', packageTypeModuleMain,
expect('--entry-type=module', packageWithoutTypeMain, 'package-without-type'); 'ERR_INPUT_TYPE_NOT_ALLOWED', true);
// Check that running with conflicting --entry-type flags throws errors
expect('--entry-type=commonjs', mjsFile, 'ERR_ENTRY_TYPE_MISMATCH', true);
expect('--entry-type=module', cjsFile, 'ERR_ENTRY_TYPE_MISMATCH', true);
expect('--entry-type=commonjs', packageTypeModuleMain,
'ERR_ENTRY_TYPE_MISMATCH', true);
expect('--entry-type=module', packageTypeCommonJsMain,
'ERR_ENTRY_TYPE_MISMATCH', true);
function expect(opt = '', inputFile, want, wantsError = false) { function expect(opt = '', inputFile, want, wantsError = false) {
// TODO: Remove when --experimental-modules is unflagged // TODO: Remove when --experimental-modules is unflagged

View File

@ -1,4 +1,4 @@
// Flags: --experimental-modules --entry-type=module // Flags: --experimental-modules
/* eslint-disable node-core/required-modules */ /* eslint-disable node-core/required-modules */
import cjs from '../fixtures/baz.js'; import cjs from '../fixtures/baz.js';
import '../common/index.mjs'; import '../common/index.mjs';

View File

@ -34,12 +34,12 @@ syntaxArgs.forEach(function(arg) {
assert.strictEqual(c.status, 1); assert.strictEqual(c.status, 1);
}); });
// Check --entry-type=module // Check --input-type=module
syntaxArgs.forEach(function(arg) { syntaxArgs.forEach(function(arg) {
const stdin = 'export var p = 5; var foo bar;'; const stdin = 'export var p = 5; var foo bar;';
const c = spawnSync( const c = spawnSync(
node, node,
['--experimental-modules', '--entry-type=module', '--no-warnings', arg], ['--experimental-modules', '--input-type=module', '--no-warnings', arg],
{ encoding: 'utf8', input: stdin } { encoding: 'utf8', input: stdin }
); );

View File

@ -25,12 +25,12 @@ syntaxArgs.forEach(function(arg) {
assert.strictEqual(c.status, 0); assert.strictEqual(c.status, 0);
}); });
// Check --entry-type=module // Check --input-type=module
syntaxArgs.forEach(function(arg) { syntaxArgs.forEach(function(arg) {
const stdin = 'export var p = 5; throw new Error("should not get run");'; const stdin = 'export var p = 5; throw new Error("should not get run");';
const c = spawnSync( const c = spawnSync(
node, node,
['--experimental-modules', '--no-warnings', '--entry-type=module', arg], ['--experimental-modules', '--no-warnings', '--input-type=module', arg],
{ encoding: 'utf8', input: stdin } { encoding: 'utf8', input: stdin }
); );