* fix(deps-resolver): carry full peer suffix for walk-ancestor peers
When a node's resolved peer is a walk-ancestor still in progress (e.g.
`@eslint-community/eslint-utils` peer-depends on its parent `eslint`),
`build_peer_id` had no depPath for it yet and fell back to the
`name@version` cycle form, freezing a collapsed suffix like
`eslint-utils@4.9.1(eslint@10.4.1)` instead of pnpm's
`eslint-utils@4.9.1(eslint@10.4.1(supports-color@8.1.1))`. Because the
depPath is the graph key, the truncation cascaded to every reference.
Port pnpm's deferred `calculateDepPath`: leave the walk (and the
provisional depPaths `find_hit` reads) untouched, capture each node's
NodeId-level edges, then recompute depPaths in a post-walk pass that
resolves each peer to its full depPath and collapses to `name@version`
only for genuine cycles — detected as peer-graph SCCs via iterative
Tarjan. The graph is rebuilt from the per-node records keyed by the
corrected depPaths, so wrongly-collapsed nodes split and correctly-
collapsed ones still merge.
Also fixes a related divergence: snapshot `dependencies` now carry only
the node's own resolved peers, mirroring pnpm's
`childrenNodeIds: { ...children, ...resolvedPeers }`, so a descendant's
optional peer (e.g. `debug`'s `supports-color`) is no longer materialized
as the parent's dependency.
Refs pnpm/pnpm#12266.
* fix(deps-resolver): don't hoist optional peers against run-resolved versions
`getHoistableOptionalPeers` hoists an optional missing peer only when a
preferred version is already in scope. pnpm reads its static
`ctx.allPreferredVersions` (wanted lockfile + manifests, empty on a fresh
install) for that decision and never folds in versions resolved during
the run. pacquet was feeding every freshly-resolved transitive version
into the same map, so an optional peer like `debug`'s `supports-color`
got hoisted against a deep-tree provider (e.g. `jest-worker`'s
`supports-color`) that pnpm never considers — resolving the peer where
pnpm leaves it bare, and non-deterministically (the outcome depended on
whether the provider landed in the map before the optional pass ran).
Snapshot the preferred-versions map before any run-resolved version is
folded in and use that snapshot for the optional-peer hoist. The
required-peer hoist keeps using the run-extended map: a required peer is
auto-installed either way, and reusing an in-tree version matches pnpm's
picker dedup (covered by the existing auto_install_* tests).
New regression test `optional_peer_only_in_resolved_tree_is_not_hoisted`,
verified to fail without the fix.
Refs pnpm/pnpm#12266.
* fix(deps-resolver): restore min-depth tie-break in build_final_graph
`build_final_graph` keyed a graph node's `depth` off the per-NodeId
`NodeRecord`, but the `pure_pkgs` and `find_hit` fast paths short-circuit
before a `NodeRecord` is created — they only lower the depth on the inline
`self.graph`, which the rebuild discards. So a package first walked at a
deeper depth and later revisited shallower kept the deeper depth, dropping
the `Math.min` tie-break those fast paths' own comments preserve.
Recompute the minimum tree depth per final depPath from `node_dep_paths`
(which records every walked NodeId, revisits included) and use it for each
rebuilt node. No lockfile-visible change today — `DependenciesGraphNode.depth`
isn't serialized and the hoister computes its own BFS depth — but it keeps
the graph's depth correct for any future consumer. Regression introduced by
the deferred depPath rebuild; guarded by a new test.
Refs pnpm/pnpm#12266.
* test(deps-resolver): port resolvePeers "peer's own peer shared with sibling"
Ports upstream's `resolvePeers.ts` "a peer's own peer is shared with a
sibling that peer-depends both" — `plugin`'s `parser` peer must resolve the
`typescript@1.0.0` that `plugin` itself uses, not be shadowed by a top-level
`parser` that resolved `typescript@2.0.0`. Exercises the depPath suffix
machinery reworked for the peer-suffix fixes; pacquet already passes it.
Also records the peer-resolution porting status (resolvePeers.ts +
hoistPeers.test.ts) in plans/TEST_PORTING.md: what's ported/covered and the
remaining gaps (multi-project peer separation, npm-alias peers, the
peer-conflict reporting edge cases, and the unported lockedPeerContext
series).
Refs pnpm/pnpm#12266.
简体中文 | 日本語 | 한국어 | Italiano | Português Brasileiro
Fast, disk space efficient package manager:
- Fast. Up to 2x faster than the alternatives (see benchmark).
- Efficient. Files inside
node_modulesare linked from a single content-addressable storage. - Great for monorepos.
- Strict. A package can access only dependencies that are specified in its
package.json. - Deterministic. Has a lockfile called
pnpm-lock.yaml. - Works as a Node.js version manager. See pnpm runtime.
- Works everywhere. Supports Windows, Linux, and macOS.
- Battle-tested. Used in production by teams of all sizes since 2016.
- See the full feature comparison with npm and Yarn.
To quote the Rush team:
Microsoft uses pnpm in Rush repos with hundreds of projects and hundreds of PRs per day, and we’ve found it to be very fast and reliable.
Platinum Sponsors
|
|
|
Gold Sponsors
|
|
|
|
|
|
|
|
|
|
|
Silver Sponsors
|
|
|
|
|
|
|
|
|
|
⏱️ Time.now |
Support this project by becoming a sponsor.
Background
pnpm uses a content-addressable filesystem to store all files from all module directories on a disk. When using npm, if you have 100 projects using lodash, you will have 100 copies of lodash on disk. With pnpm, lodash will be stored in a content-addressable storage, so:
- If you depend on different versions of lodash, only the files that differ are added to the store.
If lodash has 100 files, and a new version has a change only in one of those files,
pnpm updatewill only add 1 new file to the storage. - All the files are saved in a single place on the disk. When packages are installed, their files are linked from that single place consuming no additional disk space. Linking is performed using either hard-links or reflinks (copy-on-write).
As a result, you save gigabytes of space on your disk and you have a lot faster installations!
If you'd like more details about the unique node_modules structure that pnpm creates and
why it works fine with the Node.js ecosystem, read this small article: Flat node_modules is not the only way.
💖 Like this project? Let people know with a tweet
Getting Started
Benchmark
pnpm is up to 2x faster than npm and Yarn classic. See all benchmarks here.
Benchmarks on an app with lots of dependencies:
License
MIT, except the pnpr/ directory, which is source-available under the PolyForm Shield License 1.0.0.