-
Notifications
You must be signed in to change notification settings - Fork 30k
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
doc: module: Conditional exports docs for writing dual packages #30051
doc: module: Conditional exports docs for writing dual packages #30051
Conversation
cc @nodejs/modules-active-members |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I like this, thank you.
@coreyfarrell and @ljharb I’ve revised per your notes, please let me know what you think. (And please check my example code.) Would appreciate more reviews from anyone else interested in dual packages. |
FWIW right now the primary reccomdation to avoid any sort of copy hazard should really just be to ship and use cjs. Until an es module can be used as a replacement for a cjs one and one can get away with only shipping that, it'll always exist in some form or another. The real issue is attempting to encourage shipping two implementations that could be accessed at all... It's just not a great idea, from the perspective of the copy hazard. If you acknowledge that but still want to encourage shipping multiple implementations, then it's a question of where you want to land on how easy it is to accidently introduce a duplicated state issue. I'm sure someone is screaming internally that using |
That’s the first approach that this PR recommends. The issue is that a lot of people won’t consider that “good enough”—it’s still a CommonJS package, just with a sheen of ESM slapped over it. People want to publish pure ESM packages, and they don’t want to drop CommonJS support. Hence approach 2, which I’m trying to go into detail laying out all its disadvantages and gotchas so that people might give it a second thought before attempting it. I feel like if we don’t mention approach 2 at all and don’t mention all the hoops you need to jump through to try to make it avoid hazards, people will just do it anyway and ship all the bugs we’re hoping they avoid. |
if they're shipping a cjs source, they're shipping a cjs source. We should have one authoritative source for each module. Even mentioning hasInstance is a huge red flag to me, it's horrifying. |
I think there's various author vs. package vs. consumer questions that we can't answer all at once. Some authors really want to maintain their source of truth as ESM but aren't ready to drop CJS support (or are they?). Some packages (many packages?) aren't stateful and could just work with two instances. We should be careful to set a very clear scope in terms of audience and problem space. Otherwise we could write a whole book and still only scratch the surface. :)
Agreed. I had hoped nobody would think of |
Sorry. I was looking for a solution not just for this but for duplicated installs. I think linking to the MDN documentation without any further context will result in implementations that fail to correctly deal with inheritance. It took me a few iterations to correctly deal with inheritance in hasInstance and I only found that it was an issue it because I intentionally tested inheritance. |
The counterargument would be that if we don’t give people a recommended way of doing it, they’ll likely use suboptimal solutions that have bugs. That’s really the point of approach 2 in general: we acknowledge that people want to do this and will try to do this, so if they do, we might as well tell them the best possible way to do it and point out the gotchas that are still present even after following all our advice. I think that’s better than forbidden knowledge where users are on their own to figure it out, and they almost certainly won’t do as good a job as we could. |
@GeoffreyBooth i think it would be better for the ecosystem to just not support this for the time being. the current way this is exposed to people writing modules is incredibly complex and confusing, and having code like this littered around is definitely not an improvement to codebases. yes, we lose the ability to ship packages that are both supported on old node and have named exports on new node, but we gain pure simplicity. |
That’s the argument for not supporting conditional exports at all (and therefore not permitting the hazard). That’s not part of this PR. This PR is an addendum to that one; if that one is merged in, and therefore we permit the hazard, what should the docs say to advise users how to avoid bugs? If that PR never gets merged in then this one is moot. |
@GeoffreyBooth then it seems i should post this on the other pr too |
I think there's ways to do it that we can recommend that are easier to get right. E.g. marking each instance with a symbol. Or putting the class implementations into CJS files and refer to them from both variants. It also comes with a "voids warranty" on JS engine performance and, imo, make code much harder to debug because you can suddenly have an
I think the simple question is: How? We can't prevent people from shipping |
@jkrems pkg and pkg/esm is a separate concern. you've imported two different specifiers, you get two different modules. |
I removed the references to |
You may not have. You may have imported N different packages using various specifiers, some of which using pkg, some using pkg/esm. Depending on how each is implemented. |
@jkrems if you think the pkg/module and pkg situation is bad, why on earth would you want to add another implicit way for that to happen |
This isn't implicit. It's explicit. The package author (the person who's most likely to know the kinds of issues that may arise from multiple instances) is explicitly saying "if you use these two copies in the same app, it'll be fine" That's making things better! Because people will ship two copies. If they are passionate about ESM (many are), they will encourage their users to actually use the ESM copy. This gives them a safer path to achieve this, much safer than saying "just ignore those issues and publish both versions under separate specifiers, surely no package tree will ever lead to inconsistencies!". This is about where to put the responsibility when (not if) a package includes both versions. Without this feature, the responsibility is always shared across all consumers in the package tree. That's an awful situation imo. With this feature, the responsibility can be placed in a single, isolated codebase: The package itself. I believe that's way better than the status quo. |
9a620e3
to
3f055a8
Compare
There was consensus in the 2019-10-30 modules meeting that this PR (or something like it) should land as part of landing conditional exports (which was also approved to land with some changes per the group, see #29978 (comment)). @Trott and others, do you mind please taking a look at this PR? This PR includes the other one, so please focus on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking really great, the flow of the material works really well and it is very clear now, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to land with the conditional exports PR.
3f055a8
to
ee79a18
Compare
I've pushed up a commit to remove the section on global state, as well as noting default exports in the wrapper. Re-review welcome. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great overall.
311a8e1
to
8de673d
Compare
This PR seems ready to go, unless there is further feedback on any of the wording here. I've added the blocked label added as this must land alongside #29978. |
I've added an extra commit here to note the |
I've run this CI multiple times, but there is an out of memory error just on Docker arm v7 on the build itself which seems to be entirely unrelated. Edit: this comment applies to #29978 not this PR. |
ec15f17
to
a944456
Compare
PR-URL: #30051 Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Jan Krems <[email protected]>
Lite CI passing - landed in ebe3dcc. |
PR-URL: #30051 Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Jan Krems <[email protected]>
PR-URL: #30051 Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Jan Krems <[email protected]>
PR-URL: #30051 Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Jan Krems <[email protected]>
This PR is an addendum to @guybedford’s #29978. This adds documentation to explain what the divergent specifier hazard is and provide two approaches for preventing or avoiding it when writing dual CommonJS/ES module packages. This came out of discussion in nodejs/modules#371 including some suggestions from @coreyfarrell. cc @MylesBorins.
This PR includes the other one, so please focus on
esm.md
starting at the “Dual CommonJS/ES Module Packages” heading: https://github.com/nodejs/node/pull/30051/files?short_path=8e67f40#diff-8e67f407bc32a0569e25d7ecaff6e494Checklist