pnpmが変えるモノレポの設計思想:OSレベルの最適化とCI/CDの極致
フロントエンド開発の現場において、`npm`や`yarn`の「巨大な`node_modules`の海」に溺れる時代は終わった。現代のDevOpsにおいて、パッケージ管理は単なる依存解決ではない。それはOSカーネルのファイルシステム能力をいかに引き出し、I/Oボトルネックを排除するかという低レイヤの戦いである。
本稿では、`pnpm`の真髄であるコンテンツアドレス可能なストレージ(CAS)の仕組みと、`workspace`を用いたモノレポ環境をCI/CDパイプラインと同期させ、開発体験を極限まで引き上げる手法を伝授する。
—
1. なぜpnpmは「速い」のか:ハードリンクの深層
`npm`や`yarn`は、プロジェクトごとに`node_modules`を独立させ、重複してパッケージをインストールする。これはディスク容量の浪費だけでなく、パッケージマネージャが膨大なファイルI/Oを発生させる元凶だ。
一方、`pnpm`はグローバルなコンテンツアドレス可能ストアを利用する。
OSレベルの仕組み:ハードリンクとシンボリックリンク
pnpmは、一度ダウンロードしたパッケージを`~/.pnpm-store`にキャッシュし、各プロジェクトの`node_modules`にはそこからハードリンクを張る。
- ハードリンクの利点: 実体は1つ。OSのファイルシステム上で同一のデータブロックを参照するため、ディスク占有率は驚異的に低い。
- シンボリックリンクの階層構造: pnpmはフラット化(hoisting)を行わない(デフォルト)。これにより、依存関係の「幽霊問題(インストールしていないパッケージが`import`できてしまう現象)」を物理的に封じ込める。
この設計は、ビルド時のキャッシュヒット率を劇的に向上させる。Docker環境での構築において、`pnpm-store`をマウントするだけで、初回ビルドから数秒で依存解決が完了する恩恵を理解できるはずだ。
—
2. pnpm workspaceによるモノレポの自動化構成
モノレポにおいて最も重要なのは、パッケージ間の「依存グラフ」をいかに効率的に解釈し、必要な部分だけを再ビルド(インクリメンタルビルド)するかである。
`pnpm-workspace.yaml` の定義
プロジェクトルートに以下のファイルを配置する。
プロジェクト内の全ワークスペースを指定
packages:
- ‘packages/’
- ‘apps/’
依存関係の結合を制御する設定(デフォルトは厳格だが、必要に応じて緩和可能)
public-hoist-patternは、特定の依存をルートに引き上げる際に使用する
現場で震える自動化:`pnpm filter`の活用
モノレポのCIにおいて「変更があったパッケージだけをテストする」のは基本中の基本だ。
変更のあったパッケージのみを対象にビルドを実行
–filter: 変更されたパッケージ(HEAD~1との差分)を指定
–recursive: 依存関係を遡ってビルドを実行
–parallel: 可能な限り並列化(CPUコアを使い切る)
pnpm recursive run build –filter=”…[HEAD~1]” –parallel
—
3. DockerとCI/CDパイプラインへの統合:究極の最適化
CI/CDにおいて「`pnpm install`が毎回走る」のは設計ミスである。Dockerのレイヤキャッシュを最大化するため、以下の戦略を採る。
効率的なDockerfileの書き方
FROM node:20-slim AS base
ENV PNPM_HOME=”/pnpm”
ENV PATH=”$PNPM_HOME:$PATH”
RUN corepack enable # corepackを利用してpnpmをインストール
FROM base AS deps
WORKDIR /app
依存解決に必要なファイルのみを先にコピー
COPY package.json pnpm-lock.yaml ./
ストアをマウントしつつインストールすることで、コンテナ間のキャッシュ共有を狙う
RUN –mount=type=cache,id=pnpm,target=/pnpm/store pnpm install –frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY . .
必要なパッケージのみをフィルタリングしてビルド
RUN pnpm –filter @my-app/web build
ここがエキスパートの知見: `RUN –mount=type=cache` を使うことで、Dockerイメージのレイヤに依存関係を含めることなく、ビルド時のみストアを参照できる。これにより、イメージサイズを最小化しつつ、構築時間を劇的に短縮できる。
—
4. 内部アーキテクチャの掌握:メモリとI/Oの最適化
pnpmを使いこなす上で、内部的な挙動を理解し、CLIをハックする視点が不可欠である。
なぜ `pnpm-lock.yaml` が重要なのか
`pnpm-lock.yaml`には、パッケージのハッシュ値が含まれている。`pnpm`はこのハッシュをチェックし、物理的なディスク上のデータが破損していないか、あるいは再ダウンロードが必要かを一瞬で判断する。
運用上のTips:`pnpm store prune`
CI環境で長時間運用していると、未使用のパッケージがストアに溜まる。定期的に以下のコマンドをCIのクリーンアップフェーズで実行し、ストレージを解放せよ。
ストアから孤立したパッケージを削除
pnpm store prune
モノレポの依存管理:`pnpm-workspace`の落とし穴
`packages`間の依存は `workspace:` プロトコルを使うのが鉄則だ。これにより、ローカルのバージョン整合性が保たれ、リリース時に正確なパッケージバージョンへの置換が自動化される。
—
伝説的DevOpsアーキテクトからの提言
pnpmを導入するということは、単にパッケージマネージャを変えることではない。「プロジェクト内のリソースと依存関係の論理構造を最適化し、ビルドという名の物理的プロセスを極限まで高速化する」というエンジニアリング姿勢そのものである。
モノレポを構築する際は、まず`pnpm`のフラットではない依存解決の厳格さを楽しみ、次に`filter`コマンドを駆使してCI/CDのパイプラインを「変更差分のみ」という最小単位まで研ぎ澄ましてほしい。
開発効率を突き詰める先にあるのは、IDEが依存関係の解決に迷わず、CIが常に数分で完了する、ストレスフリーなエンジニアリングの世界だ。今すぐ、その最初の一歩を踏み出せ。