シンボリックリンクされた`node_modules`構造
この記事では、ピア依存関係を持つパッケージがない場合のpnpmの`node_modules`の構造についてのみ説明しています。ピア依存関係を持つパッケージのより複雑なシナリオについては、「ピアの解決方法」を参照してください。
pnpmの`node_modules`レイアウトは、シンボリックリンクを使用して、依存関係のネストされた構造を作成します。
`node_modules`内の各パッケージのすべてのファイルは、コンテンツアドレス可能なストアへのハードリンクです。`bar@1.0.0`に依存する`foo@1.0.0`をインストールするとします。pnpmは、両方のパッケージを次のように`node_modules`にハードリンクします。
node_modules
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar -> <store>/bar
│ ├── index.js
│ └── package.json
└── foo@1.0.0
└── node_modules
└── foo -> <store>/foo
├── index.js
└── package.json
これらは`node_modules`内の唯一の「実在する」ファイルです。すべてのパッケージが`node_modules`にハードリンクされると、ネストされた依存関係グラフ構造を構築するためにシンボリックリンクが作成されます。
お気づきかもしれませんが、両方のパッケージは`node_modules`フォルダ内のサブフォルダ(`foo@1.0.0/node_modules/foo`)にハードリンクされています。これは、
- パッケージが自身をインポートできるようにするためです。`foo`は`require('foo/package.json')`または`import * as package from "foo/package.json"`を実行できる必要があります。
- 循環シンボリックリンクを回避するためです。パッケージの依存関係は、依存パッケージと同じフォルダに配置されます。Node.jsでは、依存関係がパッケージの`node_modules`内にあるか、親ディレクトリの他の`node_modules`内にあるかに違いはありません。
インストールの次の段階は、依存関係へのシンボリックリンクです。`bar`は`foo@1.0.0/node_modules`フォルダにシンボリックリンクされます。
node_modules
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar -> <store>/bar
└── foo@1.0.0
└── node_modules
├── foo -> <store>/foo
└── bar -> ../../bar@1.0.0/node_modules/bar
次に、直接依存関係が処理されます。`foo`はプロジェクトの依存関係であるため、ルート`node_modules`フォルダにシンボリックリンクされます。
node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar -> <store>/bar
└── foo@1.0.0
└── node_modules
├── foo -> <store>/foo
└── bar -> ../../bar@1.0.0/node_modules/bar
これは非常に単純な例です。ただし、レイアウトは、依存関係の数と依存関係グラフの深さに関係なく、この構造を維持します。
`bar`と`foo`の依存関係として`qar@2.0.0`を追加してみましょう。新しい構造は次のようになります。
node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ ├── bar -> <store>/bar
│ └── qar -> ../../qar@2.0.0/node_modules/qar
├── foo@1.0.0
│ └── node_modules
│ ├── foo -> <store>/foo
│ ├── bar -> ../../bar@1.0.0/node_modules/bar
│ └── qar -> ../../qar@2.0.0/node_modules/qar
└── qar@2.0.0
└── node_modules
└── qar -> <store>/qar
ご覧のように、グラフがより深くなった(`foo > bar > qar`)としても、ファイルシステムのディレクトリの深さは変わりません。
このレイアウトは一見奇妙に見えるかもしれませんが、Nodeのモジュール解決アルゴリズムと完全に互換性があります!モジュールを解決する際、Nodeはシンボリックリンクを無視するため、`foo@1.0.0/node_modules/foo/index.js`から`bar`が要求されると、Nodeは`foo@1.0.0/node_modules/bar`の`bar`を使用するのではなく、`bar`をその実際の場所(`bar@1.0.0/node_modules/bar`)に解決します。その結果、`bar`は`bar@1.0.0/node_modules`にあるその依存関係も解決できます。
このレイアウトの大きな利点は、実際に依存関係にあるパッケージのみがアクセス可能になることです。フラット化された`node_modules`構造では、すべての上位に配置されたパッケージにアクセスできます。これがなぜ利点であるかについて詳しくは、「pnpmの厳格さが愚かなバグを回避するのに役立つ理由」を参照してください。