メインコンテンツまでスキップ

フラットな node_modules が唯一の方法ではありません

· 1 分で読む

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 の構造は最初は珍しく思えましたが、

  1. その構造は完全に Node.js 互換なものであり、
  2. パッケージはその依存とともに適切にグループ化されいます。

peer dependency がある場合は、構造は もう少し複雑 にはなりますが、それでも考え方は同じです:フラットなディレクトリ構造を利用して、シンボリックリンクによりネストされた構造を生成します。