- Notifications
You must be signed in to change notification settings - Fork 45
Flags functionality options #300
Description
I thought it might be useful to have a post laying out in neutral terms how the various ESM flag proposals work and the implications of each, to help in discussion for choosing the best approach. If anything in this initial post is incorrect or incomplete or biased, I will edit as appropriate.
There are three options discussed so far for how what was previously called --type should behave. I’ll refer to the options by proposed new names based on how the flag would behave. They all take either module or commonjs as the single accepted and required argument.
-
--input-typewould set the type of--eval,--printandSTDINinput—and that’s it. If the initial entry point is a file, an error is thrown. -
--entry-typewould set the type of the initial entry point to the program, whether that be a file or one of the non-file input types (--evaletc.); but setting of the type of that initial entry point implies nothing about any other files. -
--package-typewould set or override the"type"field of thepackage.jsonfile that controls the parsing of the initial entry point (or of the virtualpackage.jsonat the root of the volume, if there are nopackage.jsonfiles up the path from the entry point). It would also apply to the non-file input types. Like the"type"field, it would not override any package scopes beyond the one containing the initial entry point.
This isn’t meant to be a discussion of names. I don’t feel that it’s a good use of GitHub issue threads to bikeshed what we should name things. Once we decide on which functionality we want, we can separately determine the best name for it. Please consider the names below to be placeholders.
Pros and cons of each option
--input-type
Pros:
- Provides a way to use ESM in
--eval,--printandSTDIN, where otherwise ESM syntax would be impossible to enable.
Cons:
- Users would probably expect this to apply to files as well. It’s not obvious why it wouldn’t.
--entry-type
Pros:
-
Provides a way to use ESM in “loose”
.jsor extensionless files, that live outside of any project/package and don’t have a parentpackage.json. -
Explicitly applies to the file or string being referenced in the
nodecommand, so is straightforward in that regard.
Cons:
-
So far, all users who have read about this have assumed that setting the type of the entry also opts in to that type for all files imported by that entry point. As in, if you set the entry point to be ESM,
importstatements of.jsfiles should treat those.jsfiles also as ESM. This user expectation is likely to only become stronger as ESM in browsers becomes more widely used, as this is how<script type="module">behaves in browsers. -
It is inconsistent for
entry.jsanddep.jsto be side by side, whereentry.jsimportsdep.js, andnode --entry-type=module entry.jsloadsentry.jsas ESM but thendep.jsis treated as CommonJS. This is the only case where files with the same extension in the same folder are treated differently by Node. -
There’s no use case for overriding the initial entry point of a project while relying on file extensions or
package.jsonto define the type of all other files in the project.
--package-type
Pros:
-
Behaves as users expect the flag to behave, by setting the type for an entire project.
-
By referencing
package.json"type"in its name, this should be easier for users to understand as they should grasp that it behaves the same way aspackage.json"type"does. -
Allows “loose”
.jsor extensionless files toimportother ESM.jsfiles, so “shell script”.jsfiles don’t need to be limited to a single file. -
Without this, we can’t have
--package-type=auto, as it wouldn’t make sense to have type detection for an entry point only. The use case forautois a project that lacks an explicit"type"field (and uses.js), and it’s implausible to imagine a project with a.jsentry point where all other files are.mjs(or in a subfolder under apackage.jsonwith a"type"field).
Cons:
- Applies to more than just the file or string passed to
node, so users would need to be aware that it’s the equivalent topackage.json"type".
Both:
-
The only use case for needing this flag for a project is when a project is already using ESM in
.jsfiles but without"type": "module"; but most such projects expect Babel or the like to transpile them, and may not be compatible with Node without changes. (For example, they may require refactoring to enable explicit extensions; though--es-module-specifier-resolution=nodemight be sufficient for most such projects to run without changes.) If we build--package-type=auto, regardless of its effectiveness for Babel ESM projectsautowould work great for a CommonJS project without apackage.json"type"field. -
Allows opting into ESM mode by default system-wide via
NODE_OPTIONS=--package-type=module. After setting such an option in a user’s environment, CommonJS projects would need to either have a"type": "commonjs"in theirpackage.jsonor be run via--package-type=commonjs. Allowing changing Node to be ESM by default system-wide would be seen as a pro by some and as a con by others; it’s a pro for those who want to leave CommonJS behind and don’t plan on adopting.mjs; and as a con for those who don’t want to encourage people to expect ESM by default and publish projects and packages that assume so. (The latter concern could presumably be addressed somewhat bynpm publishchecking forimport/exportsyntax in packages about to be published, and erroring if"type": "module"is not present.)
Other considerations
It is not clear to me how these flags would interact with loaders, test runners, stubs/mocks and the like. I’m not sure if any flags are better or worse than others with regard to such things. On the one hand --input-type or --entry-type would seem to be simpler for such add-ons to handle, as they only apply to one string or file; yet if the add-ons need to know how to handle package.json "type", it might be simpler for them to support that and --package-type (which should behave identically) rather than needing to special-case the entry point.
See also
-
upstream objection to --type #296 - Initial discussion of upstream objection
-
esm: scoped --type, cpp refactoring ecmascript-modules#57 - PR that implements
--package-typefunctionality (though with--typename) -
new ESM implementation node#26745 - Upstream PR, the code for which contains
--entry-typecurrently; and the thread contains some discussion of--type