-
Notifications
You must be signed in to change notification settings - Fork 29.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discussion: In an ESM-first mode, how should a package.json
file with no type
field be handled?
#49494
Comments
they already can; the file just needs the .mjs extension. If the .js extension not being ESM is a problem for that, then so is the .cjs extension not being ESM (just to forestall that rebuttal) |
Thanks @GeoffreyBooth for following these threads. I've had a couple of conversations recently where people unfamiliar with JS that weren't naturally guided to the step of just setting up a I also think the CLI / extensionless use case is important to finally crack, either as part of this or separately. When adopting a new top-level default it does give us the option to change the legacy compat paths to solve it. My preference would be one of 2. (c) or 2. (d):
Or
With either, a Even if such a process takes many years, setting up the path now with a flag would be very valuable. And even if we don't get it right first time, we can at least figure out what flag works best for users by iterating on this work given it will be entirely experimental and optional. Let most users ignore it for the however many years it takes to get it stabilized, but it would very much close the loop on the ESM integration IMO. |
Thanks for the great writeup @GeoffreyBooth. I think purposefully breaking modules that have been working untouched for years is against what our users expect from us. Here is an interesting data point: developers still use readable-stream v2.3.7 (60 millions weekly), v2.3.8 (35 millions downloads weekly), v3.6.2 (30 millions downloads weekly), while v4 is at 3 millions downloads. If we leave the default I think they will be open to that case, given that we are protecting their users. Worst case scenario, we can easily we can patch it like we do with V8. In other terms: let's not break everybody, please. |
I think I’m leaning toward 2-c, where typeless There are two variations we could add onto this:
|
What happens in this proposal if a file, in a typeless project, is a symlink outside of node_modules, pointed to a file inside node_modules inside a typeless package dir? |
Also, since type module doesn't make extensionless files ESM, would this mean that installed packages with an executable could never use extensionless ESM, only the top-level project? (i'd thought the primary motivating use case for this recent discussion was "extensionless files as ESM") |
Thinking about this after a few night of sleep, a slightly better solution is to require type to be set (or at least warn to do so). Otherwise we could end up a situation that a maintainer author a module as ESM and then it's interpreted as commonjs. |
It’s a question of who we want to inconvenience: the package author who forgets to test their package as imported by an app, or the new user trying to follow a tutorial and potentially not knowing what a Ultimately either case could be completely addressed by a package manager. It’s package managers that edit |
It's very unlikely we would implement something like that in Yarn. Packages are by default loaded directly from their cache archives, meaning we don't get a chance to "patch the package.json" (and it's in part by design, because we want users to use exactly the source files they've been given). Doing something like what you suggest would break this model, for what seems to be a very low gain imo. |
Sure, and as I’ve said earlier, I think we can’t assume any cooperation from package managers (not out of ill intent, but because they have their own goals and they’re fragmented). My point is just that package managers have a role to play in ensuring good UX too; they could fix this problem very easily if they want to. For example, on Regardless, if we leave package managers out of it and focus just on Node, saving a package author from a bad release also feels like a very low gain, when the cost is making things harder for new users. (Which is a choice we need to make only if package managers do nothing.) |
Both install and publish are often performed headlessly, so there's not much opportunity for prompting users there. |
My point was: it would not be "very easily" 🙂 If you want to achieve the goal of allowing beginners to write
That's something I'd be more supportive about, since it doesn't break the existing ecosystem, only affecting future packages in a consistent fashion. |
I think part of this is that we’re just guessing how package managers respond; and how new users will act. But we don’t need to get it right on the first try: it’s an experimental flag. We can start with the “typeless = ESM” behavior at first and change it to the “type required” behavior later, before unflagging, once we see if (or how) package managers adapt to the existence of this new mode. I’m not strongly opposed to making Node require the |
Building off of #49432, #49295 (comment) and #31415, we’re considering a new mode, probably enabled by flag, where all of the current places where Node defaults to CommonJS would instead default to ESM. One of the trickiest questions to answer for defining such a new mode is how to handle
package.json
files that lack atype
field.A
package.json
file, whether or not it contains atype
field, defines a “package scope”: the folder that thepackage.json
file is in, and all subfolders that don’t themselves contain apackage.json
file. Within this package scope, currently apackage.json
containing"type": "module"
will cause.js
files to be interpreted as ES modules; apackage.json
file containing"type": "commonjs"
or no"type"
field will cause.js
files to be interpreted as CommonJS modules.In a naïve “just flip all the defaults” implementation, where one literally goes through the Node codebase and symmetrically reverses everywhere that we default to CommonJS to instead default to ESM, a
package.json
lacking atype
field would cause all the files in that scope to be treated as ES modules. The problem with this is that there are lots of popular dependencies that people install that have notype
field. Most real-world apps would fail to run in an ESM-first mode that behaved this naïve way, because it would be rare for every dependency of an app to contain apackage.json
with atype
field.To make an ESM-first mode that’s actually usable, we need to find a solution for this problem. As I see things, the solutions fall into two categories: one where we preserve the pure symmetrical “no
type
= ESM” behavior, and one where we don’t. Here’s a running list that I’ll update if people suggest additional ideas:1. Preserving symmetry: no
type
field is interpreted as ESMa. After installing packages, users would run a script that patched any dependencies’
package.json
files to add"type": "commonjs"
wherever thetype
field wasn’t specified.npm
team has done in recent years around reproducible builds and immutable packages. Ideally a patch script such as this would be part of thenpm install
command and its equivalents in other managers, but it’s probably unlikely that we would get support for such an approach from many (or any?) of the popular package managers.b. After installing packages, users would run a script to warn them if any of their packages lack a
type
field. (Or as part of the package installation command, the command would error on attempting to install anytype
-less package. This would presumably be an option that users would enable.) Then users would presumably uninstall that package in favor of some alternative (or choose to patch it).type
field added, even if just"type": "commonjs"
, which it’s probably safe to assume would rankle many package authors. Besides those authors complaining that we’ve pushed a requirement onto them, they may reasonably argue that atype
field makes no sense for packages intended for non-Node environments such as browsers.type
-less package would need to be patched or upgraded to the latest version (assuming the author has kindly published a new version with the field). This option creates a lot of friction for both users and package authors.c. The
npm
registry starts requiring thetype
field in order for packages to be published, just as they already require thename
andversion
fields.npm
folks agree to this, as there are countless packages not intended for use in Node, and it would be unclear whattype
field value those packages should have; andnpm
surely doesn’t want to put themselves in the position of getting many package authors angry at them.type
field, as thenpm
registry strictly disallows old packages to be modified.2. Different behavior in ESM-first mode
Assuming that no workable option for preserving symmetry is found, the question then becomes “okay, now what?” Here are some options, that I’ll update as people comment:
a. Keep current behavior. All
type
-less package scopes are still treated as CommonJS.b. Error on
type
-less packages.type
field. It presents the same problems: users would need to patch, or pressure the package authors to update their packages.c. Under a
node_modules
folder,type
-less packages are treated as CommonJS; but are treated as ESM otherwise. This follows the precedent that the ESM resolution algorithm already special-cases folders namednode_modules
.npm init
, assuming it never changes, and still write ESM code for their app without needing to enable it somehow; and the user couldnpm install
any dependency which should “just work” like today.npm
might need to adjust to support this behavior, if they don’t save packages in subfolders undernode_modules
. Package managers that save packages in a cache folder might create anode_modules
folder at the root of their cache and put the packages one level down inside that, for example. But some package managers might have trouble working with this behavior.d. The “no
type
means ESM” behavior only applies to the package scope of the entry point. So in anapp
folder withapp/package.json
(that lacks atype
field) andapp/entry.js
, runningnode --experimental-flag-for-esm-first-mode-name-tbd entry.js
would interpretentry.js
and any other files in that package scope as ESM, but all other package scopes anywhere on the disk would be interpreted as CommonJS.npm
package managers, but at the cost of the app possibly not being statically analyzable by tools. It would be ambiguous whether a particular package scope will be the one that an entry point uses and would therefore be acquiring this new behavior, unlike “is it under a folder namednode_modules
“ which is easily determined by external tools.Any other ideas? Or additional pros/cons to any of these suggestions. @LiviaMedeiros @nodejs/loaders @nodejs/wasi @nodejs/tsc
The text was updated successfully, but these errors were encountered: