Перейти к основному содержимому
Версия: 7.x

Как происходит разрешение peer dependencies

Одной из лучших особенностей pnpm является то, что в одном проекте конкретная версия пакета всегда будет иметь один набор зависимостей. Однако есть одно исключение из этого правила — пакеты с peer dependencies.

Peer-зависимости разрешаются из зависимостей, установленных выше в графе зависимостей, пока их версии совпадают с версиями родительских пакетов. Это означает, что если у foo@1.0.0 есть две peer-зависимости (bar@^1 и baz@^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-1 и foo-parent-2. Both packages have bar and baz as well, but they depend on different versions of baz. В результате foo@1.0.0 имеет два разных набора из зависимостей: один с baz@1.0.0, а другой с baz@1.1.0. Чтобы поддерживать эти варианты использования, pnpm должен создать столько жёстких ссылок на foo@1.0.0, сколько существуют разных наборов зависимостей, требующих его.

Обычно, если пакет не имеет peer dependencies, он жестко связан с папкой 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 содержит peer dependencies, для него может быть несколько наборов зависимостей, поэтому мы создаем разные наборы зависимостей для отличающихся peer dependencies:

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 внутри foo@1.0.0_bar@1.0.0+baz@1.0.0, либо на foo@1.0.0_bar@1.0.0+baz@1.1.0. Как следствие, загрузчик модулей в Node.js найдет правильные peer dependencies.

If a package has no peer dependencies but has dependencies with peers that are resolved higher in the graph, then that transitive package can appear in the project with different sets of dependencies. For instance, there's package a@1.0.0 with a single dependency b@1.0.0. b@1.0.0 has a peer dependency c@^1. a@1.0.0 will never resolve the peers of b@1.0.0, so it becomes dependent from the peers of b@1.0.0 as well.

Here's how that structure will look in node_modules. In this example, a@1.0.0 will need to appear twice in the project's node_modules - resolved once with c@1.0.0 and again with 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