-
-
Notifications
You must be signed in to change notification settings - Fork 33.8k
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=commonjsand--experimental-default-type=module, it would only apply to ambiguous cases: a file with no explicit.mjsor.cjsextension, either nopackage.jsonor one that lacks atypefield, no--input-typeor--experimental-default-type=moduleflags passed, not undernode_modules. - The entry point would be parsed (not evaluated) and we would look for
importorexportstatements. (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
importorexportstatement is found, run the entry point (and the rest of the app) as if--experimental-default-type=modulehad been passed. Else run as if--experimental-default-type=commonjshad 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 ifimportorexportstatements 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-typetoday 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=moduleor 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 (
typefield, 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