esm: detect ESM syntax in ambiguous JavaScript
Implements https://github.com/nodejs/node/issues/50064. This PR aims to allow Node.js to allow ES module syntax input by default, without the user needing to opt in by using a non-.js
extension, a package.json
"type"
field or a command-line flag. The goal is to make this enabled by default, unflagged, once we consider it stable.
For the following “ambiguous” inputs:
- Files with a
.js
extension or no extension; and either no controllingpackage.json
or one that lacks atype
field; and--experimental-default-type
is not specified - String input (
--eval
or STDIN) when neither--input-type
nor--experimental-default-type
are specified
Node will do the following:
- For ambiguous main entry point, either file or string input, the new
containsModuleSyntax
function from #50127 is used to determine whether the CommonJS or ESM loader should handle the main entry. (The ESM loader is used if the main entry contains module syntax.) - When the ESM loader is handling modules, within the
load
hook, once the source is loaded it will be passed tocontainsModuleSyntax
to determine whether theformat
should bemodule
orcommonjs
(again, only for the ambiguous inputs specified above).
This behavior applies to ambiguous files within node_modules
. This is to prevent the hazard where a package author writes a library relying on this detection behavior but consumers of the package would fail to load it if they didn’t also have the detection behavior. (Such a hazard will still exist for consumers running old versions of Node, but at least it’s avoided moving forward.)
This behavior applies to the main entry point and to files referenced via import
. It would not apply to files referenced via require
, because require
cannot load ES modules.
This behavior does not apply to modules with any marker of explicitness: an .mjs
or .cjs
extension, a package.json
with a type
field, specified --input-type
or --experimental-default-type
flags.
This behavior does not apply to --print
input, as --print
does not support ESM syntax.
When enabled, detection should have no impact on all-CommonJS apps (either written that way or transpiled into CommonJS) since detection isn’t run on files that are referenced via require
; such apps would incur only a single double parse for the entry point (and therefore never opt into the ESM loader). The largest impact would be for ESM apps with CommonJS dependencies that lack type
fields.
Before unflagging this we need to confirm that when enabled, this doesn’t affect any apps that run without erroring today. Or inversely it should have no effect on anything that already runs successfully, and therefore is not a breaking change.
cc @nodejs/performance @nodejs/loaders @nodejs/tsc
Notable change
The new flag --experimental-detect-module
can be used to automatically run ES modules when their syntax can be detected. For “ambiguous” files, which are .js
or extensionless files with no package.json
with a type
field, Node.js will parse the file to detect ES module syntax; if found, it will run the file as an ES module, otherwise it will run the file as a CommonJS module. The same applies to string input via --eval
or STDIN
.
We hope to make detection enabled by default in a future version of Node.js. Detection increases startup time, so we encourage everyone—especially package authors—to add a type
field to package.json
, even for the default "type": "commonjs"
. The presence of a type
field, or explicit extensions such as .mjs
or .cjs
, will opt out of detection.