-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
Description
After landing #49869, I opened nodejs/TSC#1445 to discuss when to flip the default value of --experimental-default-type
from commonjs
to module
, which would make Node itself default to ESM whenever the user didn’t make it explicit that they wanted to work in CommonJS by using "type": "commonjs"
in package.json
, or a .cjs
extension, etc. There were concerns raised on that thread around breaking the “loose” files case, where there is no package.json
present, and breaking tutorials such as https://nodejs.org/en/docs/guides/getting-started-guide that assume a CommonJS default.
I propose we create --experimental-default-type=detect-module
that would function as follows:
- As with
--experimental-default-type=commonjs
and--experimental-default-type=module
, it would only apply to ambiguous cases: a file with no explicit.mjs
or.cjs
extension, either nopackage.json
or one that lacks atype
field, no--input-type
or--experimental-default-type=module
flags passed, not undernode_modules
. - The entry point would be parsed (not evaluated) and we would look for
import
orexport
statements. (Notimport()
expressions that are allowed in CommonJS, not the CommonJS wrapper variables which could be set as globals by user code.) If the file cannot be parsed, error. - If an
import
orexport
statement is found, run the entry point (and the rest of the app) as if--experimental-default-type=module
had been passed. Else run as if--experimental-default-type=commonjs
had been passed.
This would solve the “loose files” and tutorials cases: ambiguous files would continue to be run as CommonJS. Only files with import
or export
statements, which currently error if you try to run them, would start to run as ESM. This would solve the goal of flipping, which is to let people start using ESM syntax by default without first opting in somehow.
I implemented big parts of this in 2019 in nodejs/ecmascript-modules#55. It’s very similar to a feature request from 2021, #39353. The main difference between then and now is the existence of --experimental-default-type
. I can respond to some of the concerns raised on those earlier issues:
- People won’t understand the distinction between disambiguating CommonJS and ESM, and what this is doing. This is why I called the value
detect-module
, notauto
, and I think “it runs as ESM ifimport
orexport
statements are found, or as CommonJS otherwise” is simple enough that users will get it. - CommonJS and ES modules cannot be disambiguated. This is true, but I’m not trying to disambiguate them. There are three sets of files, basically: files that can only run as CommonJS modules, files that can only run as ES modules, and files that can run as either (ambiguous syntax, like
console.log(3)
). I can’t tell apart the “ambiguous syntax” files from either of the other two, but I don’t have to: those run as CommonJS today, and they can continue to run as CommonJS. That would be the status quo, avoiding a breaking change. I’m only changing how the “runs only as ESM” files are interpreted, which turns a guaranteed error (Unexpected token import
) into running code. - How would files imported by the entry point be interpreted? This is where the existence of
--experimental-default-type
today helps us out, because now I have a framework to use that defines how all files in various scopes are interpreted. The detection triggers either--experimental-default-type=module
or leaves us in the default--experimental-default-type=commonjs
, and that’s it. - What about future syntax, like whatever comes after import attributes? In my PR in 2019, I used Acorn since it’s already a dependency within Node. Acorn throws on any syntax that it doesn’t know, and I think we would likewise error. The error would be informative, instructing users to try again with an explicit marker (
type
field, file extension). - What about the performance impact? The entry point is parsed twice, yes, but only the entry point; and only when no explicit marker is present.
@nodejs/loaders @nodejs/tsc