[Feature] RFC: Peer dependencies w/ defaults
See original GitHub issueDescribe the user story
Various packages want to improve the user experience by including most of the packages that would typically be expected from the top-level (think next
or create-react-app
, for example, which don’t want their users to have to list webpack
in their dependencies).
The problem then becomes: what happens to the top-level packages that happen to have peer dependencies on the packages embed within the source package? For example, @zeit/next-css
is a package that the end user has to list at the top level, in their own dependencies, but it also has a peer dependency on webpack
(through mini-css-extract-plugin
).
In most package managers, if the user doesn’t list webpack
in their dependencies, the webpack
from next
will likely be hoisted to the top-level, so mini-css-extract-plugin
will likely be able to access it. However, this is incorrect: since the user doesn’t provide any version of webpack
themselves, there is no safe way to satisfy the peer dependency.
More than just a theoretical issue, it may cause practical issues: if the user has two workspaces, with one depending on Gatsby and another on Next (or, more generally, if multiple workspaces use different versions of Webpack in any way), there’s no telling which version of webpack
will be hoisted - and thus mini-css-extract-plugin
may end up using a different version than the Webpack server used by next
.
At the same time, end users can’t list webpack
in their dependencies either, because it will likely resolve to a different version than the one used by next
(even if the ranges are assumed to resolve to the same version if they overlap, the end user still has to keep the ranges in sync, and that’s likely to break at some point).
Describe the solution you’d like
I believe this problem is solvable if we introduce a concept of “Peer dependencies with defaults” - in line with the “Optional peer dependencies” we introduced a year ago. Peer dependencies with default would be defined by using the existing fields:
{
"dependencies": {
"webpack": "^4"
},
"peerDependencies": {
"webpack": "^4"
}
}
So for example, next
would have not only a regular dependency on webpack
, but also a peer dependency. This pattern, currently invalid because without any semantic meaning, would be defined as such:
A package X listing a package Y as both peer dependency and regular dependency will be guaranteed to access the version of Y provided by its parent if available, or the one listed in its dependencies otherwise.
Going back to the initial story, end users would be able to add webpack
to their dependencies when using @zeit/next-css
, thus fulfilling the peer dependency. Additionally, because next
would list a default peer dependency, it would be guaranteed to use the version provided by its parent, but without requiring it to be provided.
Describe the drawbacks of your solution
This proposal doesn’t have obvious drawbacks - not only does it merely give a semantic meaning to a pattern that didn’t have any, it’s also completely forward compatible: older versions of our package managers will downgrade gracefully by installing the regular dependency and ignoring the peer dependency (without printing any warning).
Describe alternatives you’ve considered
We could try to provide a way for next
to directly share its dependencies with its siblings, but I’m worried this would be a very drastic change that would be much more controversial. It would require specific syntax, wouldn’t be forward compatible, and would be technically challenging to get right, if even possible (it would break the assumption that the dependency tree only goes in one direction).
Npm has a proposal to make peer dependencies installed automatically if not provided. Regardless of my general doubts about this approach, I don’t think this would solve the issue described here, as in this case webpack
would be resolved independently on the next
and @zeit/next-css
branches. Even if the package manager was able to ensure they end up resolving to the same version, it wouldn’t necessarily be the same instance as another version of webpack
could end up being hoisted to the top level (especially in the multi-workspace setup I described earlier). This isn’t a problem for the proposal here, because the user will explicitly “pick” the version they want to make available to both next
and @zeit/next-css
, avoiding any hoisting conflict.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:13
- Comments:15 (10 by maintainers)
Top GitHub Comments
It’s unlikely that we’ll adopt this for Next.js as webpack is an implementation detail and could be swapped out eventually. The mentioned plugins are deprecated btw as they’re now built into Next.js.
Closing as the described solution was implemented in https://github.com/yarnpkg/berry/pull/628 and https://github.com/yarnpkg/berry/pull/2915