跳到主要内容
版本:11 & 12

对等依赖 (peers) 是如何被处理的

pnpm 的最佳特性之一是,在一个项目中,软件包的特定版本将始终具有同一组依赖项。 不过,这个规则有一个例外 - 具有 [对等依赖项][peer dependedncies] 的软件包。

对等依赖会从依赖图中更高的已安装的依赖项中解析,因为它们与父级共享相同的版本。 这意味着 如果 foo@1.0.0 有两个对等(bar@^1baz@^1)那么它可能在同一个项目中有 多组不同的依赖项。

- foo-parent-1
- bar@1.0.0
- baz@1.0.0
- foo@1.0.0
- foo-parent-2
- bar@1.0.0
- baz@1.1.0
- foo@1.0.0

在上面的示例中, foo@1.0.0 已安装在 foo-parent-1foo-parent-2 中。 这两个包都有依赖包 bazbar, 但是它们却依赖着不同版本的 baz。 因此, foo@1.0.0 有两组不同的依赖项:一组具有 baz@1.0.0,另一组具有 baz@1.1.0。 若要支持这些用例,pnpm 必须有几组不同的依赖项,就去硬链接几次 foo@1.0.0

通常,如果一个包没有对等依赖项,它会被硬链接到其依赖项的符号链接旁边的 node_modules 文件夹,如下所示:

node_modules
└── .pnpm
├── foo@1.0.0
│ └── node_modules
│ ├── foo
│ ├── qux -> ../../qux@1.0.0/node_modules/qux
│ └── plugh -> ../../plugh@1.0.0/node_modules/plugh
├── qux@1.0.0
├── plugh@1.0.0

但是,如果 foo 有对等依赖,那么它可能就会有多组依赖项,所以我们为不同的对等依赖项创建不同的解析:

node_modules
└── .pnpm
├── foo@1.0.0_bar@1.0.0+baz@1.0.0
│ └── node_modules
│ ├── foo
│ ├── bar -> ../../bar@1.0.0/node_modules/bar
│ ├── baz -> ../../baz@1.0.0/node_modules/baz
│ ├── qux -> ../../qux@1.0.0/node_modules/qux
│ └── plugh -> ../../plugh@1.0.0/node_modules/plugh
├── foo@1.0.0_bar@1.0.0+baz@1.1.0
│ └── node_modules
│ ├── foo
│ ├── bar -> ../../bar@1.0.0/node_modules/bar
│ ├── baz -> ../../baz@1.1.0/node_modules/baz
│ ├── qux -> ../../qux@1.0.0/node_modules/qux
│ └── plugh -> ../../plugh@1.0.0/node_modules/plugh
├── bar@1.0.0
├── baz@1.0.0
├── baz@1.1.0
├── qux@1.0.0
├── plugh@1.0.0

我们创建 foo@1.0.0_bar@1.0.0+baz@1.0.0foo@1.0.0_bar@1.0.0+baz@1.1.0 内到 foo 的软链接。 因此,Node.js 模块解析器将找到正确的对等依赖。

如果某个包没有对等依赖,但存在依赖项,其含有在途中被更高解析的对等依赖,那么该传递包可以出现在具有不同依赖关系集的项目中。 例如,a@1.0.0 具有单个依赖项 b@1.0.0b@1.0.0 具有对等依赖项c@^1a@1.0.0 永远不会解析 b@1.0.0 的对等,因此它也变得依赖于 b@1.0.0 的对等。

该结构在 node_modules 中的样子如下。 在这个例子中,a@1.0.0 需要在项目的 node_modules 中出现两次 - 其中一次是被 c@1.0.0 解析,另一次被 c@1.1.0 再次解析。

node_modules
└── .pnpm
├── a@1.0.0_c@1.0.0
│ └── node_modules
│ ├── a
│ └── b -> ../../b@1.0.0_c@1.0.0/node_modules/b
├── a@1.0.0_c@1.1.0
│ └── node_modules
│ ├── a
│ └── b -> ../../b@1.0.0_c@1.1.0/node_modules/b
├── b@1.0.0_c@1.0.0
│ └── node_modules
│ ├── b
│ └── c -> ../../c@1.0.0/node_modules/c
├── b@1.0.0_c@1.1.0
│ └── node_modules
│ ├── b
│ └── c -> ../../c@1.1.0/node_modules/c
├── c@1.0.0
├── c@1.1.0

Cyclic dependencies

Added in: v12.0.0-rc.5 (pnpm v12 only)

Packages may depend on each other in a cycle: a depends on b, and b — directly, or through further packages — depends back on a. When a package inside such a cycle has peer dependencies, there is no single point "higher in the graph" to resolve them from, because entering the cycle at a and entering it at b lead to different parents.

pnpm cuts every cycle at a fixed place. The packages that form a cycle are ordered by their package IDs, and the edges that close it are cut at the same point no matter where the installation walks into it. The cut edge is still a real dependency — it is resolved against an occurrence of its target taken at the project level — but the walk never travels around the cycle, so a package inside a cycle is no longer resolved once per path that reaches it. Peer dependencies of packages inside a cycle still resolve to the nearest match along that order.

Because the cut no longer depends on the path the installation takes, the lockfile is a function of the dependency graph alone. Repeated installs, reordered packages globs in pnpm-workspace.yaml, and reordered entries in package.json all produce a byte-identical lockfile. Projects with cycle-heavy dependency graphs also end up with a smaller lockfile, since packages inside a cycle no longer get a separate peer variant per path that reaches them.

Existing lockfiles keep working: --frozen-lockfile installs consume them unchanged, and installs that skip resolution leave them untouched. The first install that re-resolves — after a dependency change, for instance — re-keys the peer variants of cyclic packages once, which shows up as a one-time lockfile diff.

In pnpm v11, where the cut depends on the order the graph is walked, the same dependencies can produce different lockfiles depending on the order projects and dependencies are listed in.