Skip to content

fix(npm): peerDependency resolution leading to multiple versions being installed + hanging#32358

Merged
dsherret merged 15 commits intomainfrom
peerdep-bugs
Mar 2, 2026
Merged

fix(npm): peerDependency resolution leading to multiple versions being installed + hanging#32358
dsherret merged 15 commits intomainfrom
peerdep-bugs

Conversation

@marvinhagemeister
Copy link
Contributor

@marvinhagemeister marvinhagemeister commented Feb 27, 2026

There are still weekly reports of Fresh users having errors because of duplicated preact or @preact/signals packages. So I spend the day hunting down various peer dependency installation issues.

Essentially, this PR rewrites our dependency resolution code to use a two-phase approach, which fixes duplicate peer dependencies or hanging installation.

Peer deps were resolved inline during BFS traversal, making results dependent on traversal order. If package lib (which peers with react) was encountered before react was added to the graph by a sibling, it would auto-resolve react@18.2.0 (latest). When encountered again in a subtree where react@18.3.0 already existed, it got a different resolution, which creates a duplicate lib entry. With patterns like Expo/React Native (100+ plugins all peering with react, react-native, and expo), this caused exponential blowup.

This issue affects practically every frontend project as the framework is always a peerDependency (react/preact/vue/etc) and most of them have some form of singletons which only work when one copy of the framework is installed.

Fixes #31179
Fixes #21394

@marvinhagemeister marvinhagemeister changed the title fix(npm): peerDependency resolution leading to multiple versions being installed fix(npm): peerDependency resolution leading to multiple versions being installed + hanging Feb 28, 2026
…ency installations

We had a bunch of issues where we were either repeatedly re-resolving the same dependencies over and over again when cyclic peer dependencies were present, or growing module ids unboundly which also caused hangs.

This was all caused by using a 1 pass algorithm prior to this change, whereas proper peer dependency resolution requires a 2 pass algorithm. The reason for that is that peer dependency resolution is based around the "peer context" that is passed around and can influence which version a package is resolved to. We need to be super careful when those should bubble up to parents.

Pure dependencies without any peers should be resolved from a local cache, then check if we resolved a dep with the same peer context and lastly do the expensive thing of checking directly.
dsherret and others added 4 commits March 2, 2026 13:02
Verify that the resolved peer dep actually points to package-peer@1.0.0
and that package-c's package-b dependency references a package-a with
the peer resolved, confirming propagation through the correct scope.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Member

@dsherret dsherret left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work!! This is going to be so much better.

Thanks a lot for looking into this.

@dsherret dsherret enabled auto-merge (squash) March 2, 2026 14:37
@dsherret dsherret merged commit 5fb8a4d into main Mar 2, 2026
219 of 222 checks passed
@dsherret dsherret deleted the peerdep-bugs branch March 2, 2026 15:33
littledivy pushed a commit to littledivy/deno that referenced this pull request Mar 2, 2026
…g installed + hanging (denoland#32358)

Essentially, this PR rewrites our dependency resolution code to use a
two-phase approach, which fixes duplicate peer dependencies or hanging
installation.

Peer deps were resolved inline during BFS traversal, making results
dependent on traversal order. If package lib (which peers with `react`)
was encountered before `react` was added to the graph by a sibling, it
would auto-resolve `react@18.2.0 (latest)`. When encountered again in a
subtree where `react@18.3.0` already existed, it got a different
resolution, which creates a duplicate lib entry. With patterns like
Expo/React Native (100+ plugins all peering with `react`,
`react-native`, and `expo`), this caused exponential blowup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deno install hangs for over 10 minutes when installing deps on new expo project npm peerDependency duplicate module issue with elysia

3 participants