モノレポの迷宮を脱する:pnpm Workspaceがもたらす「依存解決の不可逆的進化」
かつて我々は、`npm link` という呪縛に囚われていた。異なるプロジェクト間でライブラリを共有しようとすれば、シンボリックリンクの階層構造で迷子になり、`node_modules` の依存関係が崩壊する「幽霊依存(Phantom Dependencies)」に夜な夜な頭を抱えていたはずだ。
今日、我々が語るべきは、pnpmがもたらした「Content-Addressable Store(コンテンツ指向ストア)」という革命による、モノレポ開発の最適解である。
—
1. なぜ「npm link」は悪手なのか?—内部構造の真実
`npm` や `yarn (v1)` は、依存関係をフラットに展開しようとする。これが大規模モノレポにおいて致命的なのは、「存在しないはずのパッケージにアクセスできてしまう」という脆弱性を内包するからだ。
対して `pnpm` の内部アーキテクチャは、すべてのパッケージをグローバルストアに一度だけ保存し、各プロジェクトの `node_modules` にハードリンクを張る。さらに `pnpm workspace` におけるローカルパッケージは、単なるシンボリックリンクとして `node_modules` 下に配置される。これにより、プロジェクト間でのライブラリ共有において「ビルドの再現性」と「ディスクI/Oの最小化」が同時に達成される。
2. Workspaceによる「型安全な依存関係」の強制
モノレポにおいて、ローカルパッケージ間の循環参照や、意図しないバージョンの混入は、CIの失敗率を跳ね上げる最大の要因だ。`pnpm-workspace.yaml` を用いた構成では、`workspace:` プロトコルを活用し、常に「今ここにある最新コード」をリンクさせる。
pnpm-workspace.yaml
依存関係の解決を workspace 内に限定することで、
外部公開を待たずにローカルでの変更を即時反映する
packages:
- ‘packages/’
- ‘apps/’
この設定により、`apps/web` が `packages/ui` に依存している場合、`pnpm` はこれを単なる外部ライブラリではなく、ローカルのファイルシステム上のディレクトリとして解決する。
3. CI/CDを極限まで加速する:Filteringと並列実行の最適化
モノレポのCIにおいて「全パッケージを毎回ビルドする」のは愚行だ。我々が求めるのは、「変更された差分のみを特定し、依存グラフに基づいて最小限の順序でビルドする」ことである。
以下のコマンドは、CIパイプラインにおいて「変更差分のみを特定し、依存先をビルドする」ための最適解だ。
–filter: 変更されたパッケージ(HEAD~1)を選択
–filter …: 依存している全ての downstream パッケージも含める
–parallel: 依存関係を壊さない範囲で最大並列数で実行
pnpm recursive run build –filter “…[HEAD~1]” –parallel
この `–filter` 構文こそが、大規模モノレポを運用するエンジニアの特権である。CIの設定ファイル(GitHub Actions等)では、これを `changed-files` アクションと組み合わせることで、ビルド時間を数十分から数秒単位へ短縮可能だ。
4. Dockerコンテナでの「完全自動化」:ビルドレイヤーのハック
Dockerビルドにおいて、`node_modules` のキャッシュは鬼門である。`COPY . .` を行う前に必要なパッケージだけをインストールし、レイヤーキャッシュを最大化する。
依存関係の定義ファイルのみをコピーし、ストアを構築する
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/ui/package.json ./packages/ui/
RUN pnpm fetch # ネットワークアクセスを一度で完結させる
ソースコードをコピーしてビルド
COPY . .
RUN pnpm install –offline # ローカルストアから高速展開
`pnpm fetch` はネットワークを一切使わず、ロックファイルから必要なメタデータをストアに展開する。これにより、CIのコンテナ起動ごとに数ギガバイトのダウンロードを繰り返すという、現代の技術者にとって最も無駄な時間を排除できる。
5. アーキテクトの視点:なぜ今、このツールなのか
メモリ消費を抑え、ディスクI/Oを極限まで減らす。`pnpm` の真価は単なるパッケージ管理ではなく、「開発という作業を、ファイルシステムの操作レベルまで最適化したこと」にある。
大規模なモノレポを管理する場合、以下の独自スクリプトを `scripts` に仕込んでおくことを推奨する。
{
“scripts”: {
// 依存関係がworkspace内で完結しているか検証する
// CIのパイプラインの冒頭で実行し、不正な依存を即座に弾く
“lint:deps”: “pnpm list -r –filter ‘…[main]’ –depth 0”
}
}
結び:エンジニアの誇りとして
ツールに踊らされるのではない。ツールの深淵を覗き込み、その挙動を完全に掌握した上で、パイプラインを設計する。`pnpm workspace` を使った開発フローは、単に「便利だから」使うのではなく、「ビルドという非決定的なプロセスを、完全な決定論へと昇華させるため」に存在する。
あなたのモノレポが、ただの「コードの詰め合わせ」ではなく、厳密に管理された「エンジニアリングの結晶」となることを期待する。さあ、次はあなたのCIパイプラインを、この知見で書き換えてみてほしい。