ピアの解決方法
pnpmの最も優れた機能の1つは、プロジェクトが、特定バージョンの依存パッケージを常に1セットの依存関係として持つことです。 There is one exception from this rule, though - packages with peer dependencies.
ピア依存関係は、依存関係グラフの上位にインストールされている依存関係として解決されます。同じバージョンの依存パッケージを親として共有するからです。 つまり、foo@1.0.0
に2つのピア依存関係があるとき (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
にインストールされています。 どちらのパッケージにもbar
とbaz
が存在しますが、baz
のバージョンが異なります。 結果として、foo@1.0.0
は2つの依存関係のセットを持つことになります。片方のセットには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
がピア依存関係を持つ場合、おそらく依存関係のセットは複数になります。pnpmはピア依存関係の解決結果に対する、それぞれの依存関係のセットを作成します。
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
pnpmはfoo@1.0.0_bar@1.0.0+baz@1.0.0
とfoo@1.0.0_bar@1.0.0+baz@1.1.0
のそれぞれに、foo
のシンボリックリンクを作成します。 結果として、Node.jsのモジュールリゾルバは正しいピア依存関係を発見できるようになります。
ピア依存関係を持たないパッケージでも、そのパッケージの依存関係が持つピア依存関係は、依存関係グラフの上位で解決されることになります。すると、推移的依存関係となるパッケージが、プロジェクトの複数の依存関係のセットとして表れることになります。 例えば、パッケージa@1.0.0
が1つの依存関係b@1.0.0
を持っているとしましょう。 b@1.0.0
はピア依存関係c@^1
を持っています。 a@1.0.0
がb@1.0.0
のピア依存関係を解決することはありません。ですから、b@1.0.0
のピア依存関係も同じように依存関係として追加されます。
node_modules
の構造がどうなるのか見てみましょう。 ここでは、a@1.0.0
はプロジェクトのnode_modules
に2回登場しなければなりません。1つはc@1.0.0
を解決するため、もう1つは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