Repository navigation
Flags functionality options #300
Description
Activity
- addedmodules-agendaTo be discussed in a meetingTo be discussed in a meeting
on Mar 26, 2019 I think there's two different cases / functionalities that may be good to keep apart:
- How should
.jsbe interpreted? Is it.cjsor.mjs? - What is the content-type of some bytes that we have no further info on (no extension, stdin, eval, etc.)?
The first is a mostly binary choice between two modes. The latter is not. The content-type could be JSON, WASM, binary module, binary AST (forward looking), JS module, CJS module, or any future format. The first is "switch mime DB", the latter is "provide mime for this specific bit".
If I say
node --input-type=wasm <foo.wasmandfoo.wasmimportsx.jsfile, we would not want to assume thatx.jsis WASM just because that was the input type. But if I runnode --input-type=wasm --package-mode=js-is-esm <foo.wasm, I would expect that thex.jsfile imported fromfoo.wasmwill be interpreted as a JS module.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.
For test runners (or
coffee --package-type foo.coffee) the problem is that they need to fork to apply this setting to the node runtime, unless it can be changed from inside the process somehow. But we could assume that those tools will just say "If you want to run tests, add apackage.json. We don't support--package-type".- How should
Taking off my neutrality hat from the top post, the one thing I’m sure of is that I think
--entry-typeis a footgun. Most, if not all, users expect it to behave differently than it actually does. Even within our group we have trouble keeping track of how it should behave in various scenarios (nopackage.json, apackage.jsonthat lacks a"type"field, etc.).I’m sympathetic to the argument that the use cases for
--package-typearen’t compelling. Those use cases, that I can think of, are:-
Running a project from a pre-Node 12 that uses
import/exportsyntax and expects Babel oresm, and the user wants to try running it without Babel oresm(and hopefully they’re not doing things that we don’t support, like named exports from CommonJS). There are likely few such projects that will run successfully, so this use case is perhaps not worth considering. If they have to refactor at all, they can add"type": "module". -
Users who want to flip the defaults for Node system-wide to be ESM-first, via
NODE_OPTIONS=--package-type=module. To be honest I don’t see why we’d want to prevent users from doing this, but changing Node’s system default has never been a goal of ours, so it’s not necessarily a use case we need to support (yet).
So basically if the choice was only between
--entry-typeand--input-type, I’d rather ship--input-type. The latter does handle a use case we’ve decided we want to support, namely ESM in--eval/--print/STDIN. That’s important enough to add a flag for, I think we all agree. And--input-typeisn’t the footgun that--entry-typeis; it simply won’t work on files, rather than operate on files in surprising ways.Switching from
--entry-typeto--input-typemeans that the use case of “loose”.jsor extensionless files outside of any package scope would not be executable as is; they would need to be renamed to use.mjsor put inside a folder with apackage.json. I’m okay with this. There’s not much JavaScript meant to be run by Node that doesn’t use a non-core dependency, so a file without apackage.jsonshould be rare; basically shell scripts or CLI tools, and the latter are usually symlinks into package scopes (like hownpmis a symlink intonode_modules/npmsomewhere). No one would want to run an extensionless file with a flag anyway;node –entry-type=module npmdoesn’t make sense (if you’re going to type all that,npmcan just have an extension). So requiring executables likenpmto be symlinks or inside package scopes, which most of them are already, doesn’t bother me; nor does it bother me to ask shell scripts to do the same or to use.mjs. Those solutions, I feel, are sufficient for those rare use cases.Even with
--input-type, whatever its final name becomes, there’s still the issue that people will probably expect/want it to work on files, and if it should work on files, then it should behave like--package-type. I still feel that way; but I’m willing to ship--input-typefor now and wait for that feedback. When users complain that--input-typedoesn’t work on files, we can ask them why they want it to; what are their use cases, beyond the two I listed above? If they’re compelling enough, that might lead us to support a flag for files, either like--package-typeor maybe somehow different. But I see the logic that maybe we should wait for such feedback rather than building a flag we don’t know the use case for just yet.So how would people feel about this approach? Replace
--entry-typewith--input-typenow, and--input-typeis what ships with Node 12; and we wait for feedback before expanding its scope further?Reacted by Łukasz Szewczak, Charles Samborski and T.J. Crowder-
I like this approach
Given that being able to control the parse goal of non-file input is the only one of the three that is strictly necessary as opposed to being about convenience, sugar, etc, I wholeheartedly agree with this plan. It will neatly sidestep concerns about defaults, modes, consistency with package.json, etc; it makes something impossible possible; and it leaves open the design space to make something possible perhaps be simpler.
- removedmodules-agendaTo be discussed in a meetingTo be discussed in a meeting
on Apr 10, 2019 Input from a user (me :-) ):
-
Glad to see
--entry-typego, it was definitely confusing. -
--input-typesounds great for the case it handles (direct input). -
I really want
--package-typeas well, so that when throwing together a quick example I can use ESM with normal (to me) filenames and not have apackage.json. I know it can be solved with a script (like Andrea's), but it seems like central functionality to me. Moreover, with clear semantics ("--package-typedoes whattypeinpackage.jsondoes"), I don't see a strong argument against adding it. Yes, the bar to adding flags should be high-ish, but something as fundamental as ESM justifies it (to me, as a user).
Thanks to all for your deep thought, and hard work, on this. I'm overbooked the next two months, but I hope to start giving back by pitching in on things a newbie can help with (so, probably not this) in a couple of months.
-
Why is it a need to not have a package json when using multiple files? Do you often have multiple files, no package.json (and thus no non-builtin dependencies), and run things directly with node?
@ljharb - (Replacing my earlier reply, hit send too soon.) My quick examples are usually just a single file, but I prefer ESM to
requireeven for built-in modules. But yes, sometimes they're a couple of files as well, and I'd want to use ESM to connect them.With a single file, you can use the proper extension (mjs) and it will be interpreted as such; at the moment the intention is for multiple files to require a package.json if you want to use an alternative file extension.
@ljharb - "the proper extension" can be a heated phrase. For me, the proper extension is
.js, and.mjswas a necessary temporary evil that these new features are making optional. Let's not turn this into a discussion on file extensions, it wouldn't be useful.Reacted by Matthew Phillips and Mike SherovI meant to say, and should have said, "can be a heated phrase," not "is". Sorry about that. (Didn't want to just silently edit.)
Definitely let’s not debate it; but the reality is that i used an accurate term. It’s totally fine if you want to use a different extension, and i fully support (#283) everyone’s full freedom to do so - but that doesn’t change what the (not temporary) defaults are, and that you need a package.json to override the defaults.
I'd use "default," it avoid the problems with "proper." The reality is indeed the
.mjsis the default extension for ESM on Node.js....and that you need a package.json to override the defaults.
Or, ideally,
--package-type. :-)- --package-type still has some confusion due to it not being clear about override boundary being the cwd, all the fs, etc. Perhaps we can use a different name like --default-types , the usage of multiple formats covered by these flags also makes me wish we would use the plural…On Sat, Apr 13, 2019, 10:30 AM T.J. Crowder ***@***.***> wrote: I'd use "default," it avoid the problems with "proper." The reality is indeed the .mjs is the *default* extension for ESM on Node.js. ...and that you need a package.json to override the defaults. Or, ideally, --package-type. :-) — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub <#300 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AAOUo2HIah-TZ7HV4QWzlGd_3wZzoFZUks5vgff_gaJpZM4cKuhd> .
One of the use cases discussed was wanting to change Node's default type, i.e. making .js treated as ESM by default when there's no package.json or there is one but it lacks a
typefield. That can be accomplished via--package-typebut that's a counterintuitive way to achieve it. Perhaps a new flag just for this purpose, like--default-type=modulethat could be added toNODE_OPTIONS, could fit the bill.@bmeck “types” already has meaning in TypeScript so it's probably not a good choice. I don't find the use of singular confusing, as the user is choosing only one type. And the browser attribute is just
type.Reacted by T.J. CrowderThe flag does not act similar to the browser attribute though and that at a glance leads to the confusion of treatment similar to how --entry-type was compared to the attribute in an odd way. The browser flag just uses an alternative scripting infrastructure and does not affect how the browser runtime identifies how to interpret content. The flag in Node does not cause a different scripting system to be used to load the module, it only affects how the ESM infrastructure identifies how to interpret a files contents for ESM.
The typescript argument also brings up verbiage problems (also see the previous issue that was brought) for dialogue that are being ignored here like:
"What type of package is X"
"X is a typescript package"
"No, what type does it export"
"X exports a Person class"
"No what is the content type like in the browser"
"X is type=module, but exports a text/javascript entrypoint"
"No, what is the type you set in package.json that node will get the content type from"
...
The term is very overloaded.Also the flag can and should become general purpose due to needs of supporting other types such as .coffee, .ts, etc. for properly dealing with loader composition. We have had many talks about how the feature being described does not apply to a single type over time but must deal with further extension to handle existing types such as .jsx, .es6, etc. As well as portential new types.
None of these need to really be addressed when considering --default-types if we call it default-type , but the usage of singular is something we should probably avoid.
- addedmodules-agendaTo be discussed in a meetingTo be discussed in a meetingand removedmodules-agendaTo be discussed in a meetingTo be discussed in a meeting
on Jun 18, 2019
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
--typeshould behave. I’ll refer to the options by proposed new names based on how the flag would behave. They all take eithermoduleorcommonjsas 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-typePros:
--eval,--printandSTDIN, where otherwise ESM syntax would be impossible to enable.Cons:
--entry-typePros:
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-typePros:
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:
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-typeor--entry-typewould 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 handlepackage.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