pnpm の新規ユーザーから、pnpm が生成する node_modules
の奇妙な構造についてよく聞かれます。 なぜ平坦な構造を使用しないのでしょうか。 依存のさらにその依存はどこにあるのでしょうか。
この記事では、npm や Yarn の生成するフラットな
node_modules
に馴染みのある読者を想定しています。 npm が v3 からフラットなnode_modules
を採用する必要があった理由については、 なぜ pnpm が必要なのでしょうか (英語) を参照してください。
では、なぜ pnpm は通常とは異なる構造の node_modules
を使用するのでしょう。 試しに 2 つのディレクトリを作成して、片方には npm add express
を、もう一方には pnpm add express
を実行してみてください。 npm の方のディレクトリにある node_modules
は次のようになります。
.bin
accepts
array-flatten
body-parser
bytes
content-disposition
cookie-signature
cookie
debug
depd
destroy
ee-first
encodeurl
escape-html
etag
express
こちら でディレクトリ全体を確認できます。
そして、こちらが pnpm が生成する node_modules
は次のようになります。
.pnpm
.modules.yaml
express
こちら からディレクトリ全体を確認できます。
依存のすべてはどこにあるのでしょうか。 node_modules
配下には、フォルダは .pnpm
のみが存在し、あとは express
という名前のシンボリックリンクです。 今回、 express
しかインストールしていないため、アプリケーションからアクセスできるパッケージはこれのみなのです。
pnpm のこの厳格さがどうしていいのか、ということにういては こちら を参照してください。
express
の中身がどうなっているか確認してみましょう。
▾ node_modules
▸ .pnpm
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
.modules.yaml
express
の中には node_modules
がないのでしょうか。 express
の依存のすべてはどこにあるのでしょうか。
express
がただのシンボリックリンクであるというところに仕掛けがあります。 Node.js は依存解決をする際に、シンボリックリンクであるかに関わらず、実際のパスを使います。 しかし、今気にしている express
の本当の場所はどこなのでしょうか。
それがここになります: node_modules/.pnpm/express@4.17.1/node_modules/express
これで、.pnpm/
フォルダの役割もわかりましたね。 .pnpm/
はすべての依存パッケージをフラットなフォルダー構成にして保存し、各依存は次のような命名規則に従ったフォルダに格納されています。
.pnpm/<name>@<version>/node_modules/<name>
このディレクトリを仮想ストアと呼んでいます。
フラットは構造によって、npm v2 によるネストされた node_modules
での、パス名が長くなりすぎる問題を解決し、さらに npm v3,4,5,6 や Yarn v1 が生成するフラットな node_modules
とは違い、依存パッケージをそれぞれ独立させています。
今度は本物の express
の中身を見てみましょう。
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
これは騙されているんでしょうか? node_modules
はやはりありません! pnpm の node_modules
に対する第二の仕掛けは、パッケージの依存は、それ自身と同じ階層に置かれている点にあります。 express
の依存は .pnpm/express@4.17.1/node_modules/express/node_modules/
ではなく .pnpm/express@4.17.1/node_modules/ にあります。
▾ node_modules
▾ .pnpm
▸ accepts@1.3.5
▸ array-flatten@1.1.1
...
▾ express@4.16.3
▾ node_modules
▸ accepts
▸ array-flatten
▸ body-parser
▸ content-disposition
...
▸ etag
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
express
の依存のすべてはシンボリックリンクであり、それらは node_modules/.pnpm/
内の適切な場所へとリンクされています。 express
の依存は1つ上の階層に置くことで、循環したシンボリックリンクになることを回避しています。
見て分かる通り、pnpm の node_modules
の構造は最初は珍しく思えましたが、
- その構造は完全に Node.js 互換なものであり、
- パッケージはその依存とともに適切にグループ化されいます。
peer dependency がある場合は、構造は もう少し複雑 にはなりますが、それでも考え方は同じです:フラットなディレクトリ構造を利用して、シンボリックリンクによりネストされた構造を生成します。