feat: dedupe packages that have peers when dedupe-peer-dependents is true#6153
Conversation
5a7511d to
4addac1
Compare
|
Strongly agree this is the right direction to go. This is one of the most surprising gotchas today with pnpm. I was about to propose doing this, but you beat me to it! Thanks. 😊 |
|
We can make it default in pnpm v8 but I had to force full resolution when this setting is on. So some performance optimisations will have to be done probably. |
|
It is ready for review. |
dedupe-peer-dependents is true
| for (const { directNodeIdsByAlias, id } of opts.projects) { | ||
| dependenciesByProjectId[id] = mapValues((nodeId) => pathsByNodeId[nodeId], directNodeIdsByAlias) | ||
| } | ||
| if (opts.dedupePeerDependents) { |
There was a problem hiding this comment.
It looks like this "patches" the resolution after it's occurred.
That works, but it may be worth discussing an alternative approach that behaves similarly to resolve-peers-from-workspace-root, but instead passes in the entire depGraph rather than only workspace root dependencies. Would that work as well?
I'm wondering if there's a way we an avoid recomputing the dep graph again, and have it deduped already when peersOfChildren is called.
There was a problem hiding this comment.
I assume you mean to move the dedupe logic to this file: https://github.com/pnpm/pnpm/blob/main/pkg-manager/resolve-dependencies/src/resolveDependencies.ts
I was thinking about it. It was my first idea. However, at that point we don't know yet that the packages are duplicated (we know maybe the ones that were duplicated during previous install). The packages with peer dependencies are duplicated only after peers resolution, which is the next stage. So we can only deduplicate them after peers resolution.
My first idea was to try to tweak the code that does auto installation of peer dependencies and prefer versions that are already present in the lockfile or are already used to resolve peers. But that was a lot harder to implement and less reliable I believe.
There was a problem hiding this comment.
I assume you mean to move the dedupe logic to this file: https://github.com/pnpm/pnpm/blob/main/pkg-manager/resolve-dependencies/src/resolveDependencies.ts
Roughly. To elaborate more, I was thinking of adding the full dep graph to the pkgsByName object above, in addition to rootPkgsByName.
I was thinking about it. It was my first idea. However, at that point we don't know yet that the packages are duplicated (we know maybe the ones that were duplicated during previous install). The packages with peer dependencies are duplicated only after peers resolution, which is the next stage. So we can only deduplicate them after peers resolution.
That's true. Adding more available peers to select from during peers resolution would change the result without knowing whether that change was necessary to dedupe. I find something nice about being able to intuit "If it's in the lockfile, it can be a peer.", but I could also see that being undesirable for complicated cases.
Don't have strong opinions here. Your final solution is good. 👍
Thanks for discussing!
dedupe-peer-dependents is truededupe-peer-dependents is true
This option plus
Ah, is this why the approach dedupes after the initial peers resolution rather than during peers resolution?
This version should work and I definitely wouldn't let perfect get in the way! |
The issue is that currently we don't dedupe, so when we add a new dependency, we should only analyse a subgraph. And we resolve peer dependencies for the subgraph. We only pass a subgraph to the peers resolver. But now that we dedupe, we always need to pass the whole graph to peers resolver and we will also re-resolve all the peers every time (which might be slow). But I don't see other alternatives. I was thinking about different approaches for 2 or 3 days 😅 |
|
I ran into that same problem researching this independently. My draft approach didn't handle For what it's worth, I think the performance penalty of looking at the entire graph is well worth it. There is a possible performance improvement given an index mapping dependencies to other dependencies it can be a peer of. That allows pnpm to avoid reanalyzing the entire graph, but that could be complex to implement for small performance gains. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
🚢 7.29.0-1 |
|
Is this supposed to fix #6152? (if so we can close the issue I guess) |
|
@simonihmig yes |
|
what means conflicting peer dependencies? |
|
Not different version ranges. The version ranges will be the same. But they are resolved by different versions of the peer. Like and In this case we cannot deduplicate |
|
Great work! It's hard to explain to others that optional peerDependencies will make framework library save into duplicate copies. After turning on this flag, developer don't need to concern about the peerDependencies. They only need to ensure that the library versions are the same. |
|
Sounds correct. |
|
As @gluxon mentioned, this change basically means that the I need to make another PR that will force resolution on all projects when adding a new dependency to a workspace project. Currently, dedupe will break if you run |
|
It's a little bit complex to understand. Most of developers only want to make sure that, there is only one copy of each dependencies in one application. That means, one application process have only one I wrote a tool to check. I hope my tool can help developers like me. |
|
Please make a note of this change in the migration guide for v8 |
|
Have a question about the naming between |
|
it should be dependents because the packages that have peer dependencies are duplicated. So, dependents of peer dependencies. |
A new setting is now supported:
dedupe-peer-dependents.When this setting is set to
true, packages with peer dependencies will be deduplicated after peers resolution.For instance, let's say we have a workspace with two projects and both of them have
webpackin their dependencies.webpackhasesbuildin its optional peer dependencies, and one of the projects hasesbuildin its dependencies. In this case, pnpm will link two instances ofwebpackto thenode_modules/.pnpmdirectory: one withesbuildand another one without it:This makes sense because
webpackis used in two projects, and one of the projects doesn't haveesbuild, so the two projects cannot share the same instance ofwebpack. However, this is not what most developers expect, especially since in a hoistednode_modules, there would only be one instance ofwebpack. Therefore, you may now use thededupe-peer-dependentssetting to deduplicatewebpackwhen it has no conflicting peer dependencies. In this case, if we setdedupe-peer-dependentstotrue, both projects will use the samewebpackinstance, which is the one that hasesbuildresolved: