-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[Feature] RFC: Peer dependencies w/ defaults #1001
Comments
Pinging @zkochan (pnpm), @isaacs (npm). Also pinging @timneutkens since you're the example I've picked 😄 |
So why adding webpack as a dependency isn't enough? Why should it also be a peer dependency?
As far as I remember from the docs of npm, peer dependencies are only resolved from dependencies of parent packages. |
If
Even with hoisting, there's something guaranteeing that
Then, again, there's no way for
Yep exactly - the problem is that some library authors have this ambivalent need where they want their projects to be battery included (so they ship their whole dependency tree), but also want to allow external plugins (which will need to work with the embed dependencies). This proposal aims to preserve the "works out of the box" workflow that they want while still giving users the ability to take back control to fulfil the requirements of the plugins they use. It also has other interesting applications, allowing to "override" dependencies if the tool allows it (similar to the |
But this doesn't solve the singleton problem. It only solves it for some cases. When the version ranges overlap. So I am not sure why this new complexity is needed if it doesn't even solve the problem in 100% of cases. Maybe something completely new is needed. Peer dependencies are already super complex. |
No, for example (
In this setup (which is what we currently have),
However, this is problematic when you only use
With defaulted peer dependencies, we would be able to describe that
And when we add back
Maybe. I'm concerned it would take a very long time to get agreement and adoption for any new field - especially given that there are other ones that could follow, such as |
Some questions: (1) What happens if the ranges don't match? Ie, if you did this: {
"dependencies": {
"webpack": "4.1.x"
},
"peerDependencies": {
"webpack": "4.x"
}
} or even: {
"dependencies": {
"webpack": "3.x"
},
"peerDependencies": {
"webpack": "4.x"
}
} (2) I'm not sure I follow how automatically including peer deps won't solve this problem. Couldn't Of course, that only works if peer deps are installed by the resolver. npm has working code slated for inclusion in v7 to do this already (with support for (3) It'd be really helpful to outline in more explicit detail what trees would be generated today in a single specific instance: (a) with peer deps (not installed, but warning on conflicts), (b) with regular deps, (c) with auto-installed peer deps, and (d) with this proposal, just so that we can more clearly see what it offers and the scenarios in which the resulting tree is different, and in which ways. (4) Since we already have a (5) I'd like to dig into the status quo behavior of yarn, berry, pnpm, npm v6, and npm v7, when faced with this type of metadata, where a dep appears in both deps and peerDeps, and the ramifications of what that behavior will mean when faced with the situations where defaulted peer deps would be desirable. npm v7 will ignore the peer dep, since arborist loads prod deps before peer deps (somewhat arbitrarily -- this could be changed easily), and ignores any dep when it already has an edge by that name. (It's quite common, for legacy reasons, to have a dep in both optionalDependencies and dependencies, and in this case, the dep is semantically an optional dep, and not a production dep. So the order isn't entirely arbitrary, but I don't know of any reason why prod deps should override peer deps, or vice versa.) The advantage of using peerDependenciesMeta rather than listing the same dep in both places, is that the dependency would be ignored and treated as a plain old peerDep by any package managers that don't know about this new semantic. (Assuming that is an advantage, of course; it may be that treating it as a production dep is a better fallback.) Since there won't be any major changes coming to either yarn or npm v6, and they'll both be with us for some time, it is important to consider the fallback cases if we provide a reason for people to start publishing packages with this structure. That's all I've got for now, hope it's helpful :) |
The best action would imo be to emit a warning at publish time that the package is likely malformed. On the consumer side however those instructions would have a well-defined behavior (if no dep is provided, use 3.x, but if one is provided it has to match 4.x. Weird, but well-defined). We could make it lead to an install-time warning, but I'm not sure it would be a good idea - end users wouldn't be able to act upon it anyway (increasing the warning fatigue for little reason), and since the behavior can be well-defined it isn't a soundness problem - just a probable mistake by the package author.
It's already the case, because that doesn't help because the final dependency tree ends up like this:
It's impossible for
Requirements:
With peer deps: It doesn't satisfy the point 3: the end-user will have to specify
With regular deps: It doesn't satisfy the point 2:
With auto-installed peer deps: Thinking more about it it would actually work since the package manager would be assumed to prevent Interestingly, I think peer dependencies w/ default are quite similar to "auto-installed" peer dependencies but with one key difference: they require an explicit opt-in (in the form of the dual definition), meaning that there is very little chance to break existing packages. Package authors will be able to start using them at their own pace, only when they feel comfortable doing so.
With this proposal: All three requirements are satisfied:
That would require the end user to always list
At the moment I think most package managers (I've checked Yarn and npm, not yet pnpm) will just ignore the peer dependency (which is a good thing; it makes the feature degrade gracefully). I don't think the behavior has been defined anywhere, hence why I think there's an opportunity to make possible something that wasn't before with little cost syntax-wise.
In the case of Next, for example, it would be harmful for |
Thanks for going through all that. I have a much clearer understanding of what you're getting at now.
Yeah, that's what was confusing me, I think, because just listing
What if we added a {
"peerDependencies": {
"webpack": "4.x"
},
"peerDependenciesMeta": {
"webpack": {
"autoinstall": true
}
}
} The added advantage of a meta field here is that yarn/berry could default to false, and npm v7 could default to true, but we could respect the user wishes if they explicitly opt one way or the other. |
I don't think it would be possible because of the peer dependencies format. Peer dependencies are necessarily semver versions (matched against whatever their parents provide), and have no way to express the location where the package should be extracted from. For example, given the following: what would you put to describe that the peer dependency is satisfied by versions 15.0 to 16.0, but by default it uses the version 16.0 from Github? {
"peerDependencies": {
"myPkg": "???"
},
"peerDependenciesMeta": {
"myPkg": {
"autoinstall": true
}
}
} The closest would be to make {
"peerDependencies": {
"myPkg": "15.x || 16.x"
},
"peerDependenciesMeta": {
"myPkg": {
"autoinstall": "arcanis/myPkg#16.0.0"
}
}
} But in this case it's basically just a different way to write the dual definition syntax: {
"dependencies": {
"myPkg": "arcanis/myPkg#16.0.0"
},
"peerDependencies": {
"myPkg": "15.x || 16.x"
}
} Except that in the first case older package managers would default to not install
The problem with this approach is that I see only two possibilities:
We both have opinionated user bases, and I'm not looking forward to the first instance of "but npm is doing it so it's a bug if you don't do it too" that we would receive 🙂 We would be able to explain that using the dual definition is the way to go, but by which point I guess they'll wonder why you have the install-by-default if they still need to add the dual definitions be portable. |
Any more opinion? |
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. |
That's a good concern 🤔 I think it could still work, because people upgrading |
I still don't understand why we can't just install peerdependencies if and only if no dependency is given by the user to guarantee a flat graph. peer dependencies are required by end packages, why arent the ranges there good enough on their own if a package isn't provided by the user themselves? Always install unmet peer dependencies by default if the package isn't provided whatsoever, but do not hoist whatsoever |
if a package isn't dependent on a peer dependency it wouldn't be listing it. it would just be checking *if * it's defined |
Describe 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
orcreate-react-app
, for example, which don't want their users to have to listwebpack
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 onwebpack
(throughmini-css-extract-plugin
).In most package managers, if the user doesn't list
webpack
in their dependencies, thewebpack
fromnext
will likely be hoisted to the top-level, somini-css-extract-plugin
will likely be able to access it. However, this is incorrect: since the user doesn't provide any version ofwebpack
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 thusmini-css-extract-plugin
may end up using a different version than the Webpack server used bynext
.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 bynext
(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:
So for example,
next
would have not only a regular dependency onwebpack
, but also a peer dependency. This pattern, currently invalid because without any semantic meaning, would be defined as such: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, becausenext
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 thenext
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 ofwebpack
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 bothnext
and@zeit/next-css
, avoiding any hoisting conflict.The text was updated successfully, but these errors were encountered: