pnpm vs npm 徹底比較 — どちらのパッケージマネージャーを選ぶべきか
1. 結論
ディスク効率・速度・厳密な依存関係管理を重視するなら pnpm、チーム全体の導入ハードルの低さや既存エコシステムとの互換性を最優先するなら npm を選んでください。 モノレポ運用を本格的に行うプロジェクトでは pnpm のワークスペース機能が特に強力です。迷ったら、まず pnpm を小規模プロジェクトで試し、チームに合うか判断するのがおすすめです。
2. 比較表
| 観点 | pnpm (v9.x) | npm (v10.x) |
|---|---|---|
| インストール速度 | ⚡ 非常に高速(コンテンツアドレッサブルストア + ハードリンク) | 標準的(v10 で改善傾向) |
| ディスク使用量 | ✅ グローバルストアで重複排除。大幅に節約 | ❌ プロジェクトごとに node_modules をフルコピー |
| node_modules 構造 | 厳密(非フラット/シンボリックリンク方式) | フラット(hoisting) |
| 幽霊依存 (Phantom Deps) | ✅ 構造的に防止 | ❌ hoisting により発生しうる |
| モノレポ対応 | ✅ pnpm-workspace.yaml でネイティブ対応 | △ npm workspaces で対応(機能はやや限定的) |
| TypeScript 対応 | パッケージマネージャーなので直接関係なし(同等) | 同等 |
| 学習コスト | npm とほぼ同じ CLI 体系。移行は容易 | Node.js 同梱。学習コストほぼゼロ |
| CI/CD 互換性 | 主要 CI はすべて対応。corepack enable で導入可 | デフォルトで利用可能 |
| Lock ファイル | pnpm-lock.yaml | package-lock.json |
| プラグイン / 拡張 | .pnpmfile.cjs でフック可能 | npm hook、overrides など |
| 週間ダウンロード数 (2024) | 約 800 万 | Node.js 同梱のため計測対象外(事実上最多) |
| ライセンス | MIT | Artistic-2.0 |
3. それぞれの強み
pnpm の強み
-
コンテンツアドレッサブルストア すべてのパッケージをグローバルストア (
~/.local/share/pnpm/store) に一元管理し、各プロジェクトへはハードリンクで配置します。10 個のプロジェクトで同じバージョンのlodashを使っても、ディスク上の実体は 1 つだけです。 -
厳密な依存関係ツリー
package.jsonに宣言していないパッケージはrequire/importできません。これにより「開発環境では動くが本番でクラッシュする」幽霊依存問題を根本的に排除できます。 -
高速なインストール ストアにキャッシュがある場合、ネットワークアクセスなしでインストールが完了します。大規模モノレポでは npm 比で 2〜3 倍の速度差 が出ることも珍しくありません。
-
ネイティブなモノレポサポート
pnpm-workspace.yaml1 ファイルで設定が完結し、--filterオプションで特定パッケージだけにコマンドを実行できます。
npm の強み
-
ゼロセットアップ Node.js をインストールした瞬間から使えます。チームメンバーへの追加ツール導入説明が不要です。
-
圧倒的なエコシステム互換性 README やチュートリアルの大半が
npm installで書かれており、コピー&ペーストでそのまま動きます。 -
安定性と後方互換性 npm, Inc.(現 GitHub / Microsoft)が公式にメンテナンスしており、破壊的変更は慎重に管理されています。
-
npxの統合 CLI ツールの一時実行がnpxでシームレスに行えます(pnpm にもpnpm dlxがありますが、認知度ではnpxが上回ります)。
4. コード例で比較
以下では「Express + TypeScript のプロジェクト初期化 → 依存追加 → スクリプト実行」を両方で行います。
npm の場合
# プロジェクト初期化
mkdir my-app && cd my-app
npm init -y
# 依存パッケージのインストール
npm install express
npm install -D typescript @types/express @types/node ts-node
# スクリプト実行
npx ts-node src/index.ts
// package.json(npm が生成)
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"dev": "npx ts-node src/index.ts"
},
"dependencies": {
"express": "^4.21.0"
},
"devDependencies": {
"typescript": "^5.6.0",
"@types/express": "^5.0.0",
"@types/node": "^22.0.0",
"ts-node": "^10.9.0"
}
}
// src/index.ts — 共通コード(どちらのマネージャーでも同じ)
import express, { Request, Response } from "express";
const app = express();
const PORT = 3000;
app.get("/", (_req: Request, res: Response) => {
res.json({ message: "Hello from Express + TypeScript!" });
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
pnpm の場合
# pnpm の有効化(Node.js 16.13+ 同梱の corepack を利用)
corepack enable
corepack prepare pnpm@latest --activate
# プロジェクト初期化
mkdir my-app && cd my-app
pnpm init
# 依存パッケージのインストール
pnpm add express
pnpm add -D typescript @types/express @types/node ts-node
# スクリプト実行
pnpm dlx ts-node src/index.ts
# または package.json の scripts に書いて pnpm dev
// package.json(pnpm が生成 — npm とほぼ同一)
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"dev": "ts-node src/index.ts"
},
"dependencies": {
"express": "^4.21.0"
},
"devDependencies": {
"typescript": "^5.6.0",
"@types/express": "^5.0.0",
"@types/node": "^22.0.0",
"ts-node": "^10.9.0"
}
}
ポイント:
package.jsonとアプリケーションコードはまったく同じです。違いはコマンド体系(npm install→pnpm add、npx→pnpm dlx)と、生成されるnode_modulesの内部構造・ロックファイルだけです。
モノレポでの違い(ワークスペース設定)
# pnpm-workspace.yaml(pnpm)
packages:
- "packages/*"
- "apps/*"
# 特定パッケージだけにコマンド実行
pnpm --filter @my-org/api dev
pnpm --filter @my-org/web build
// package.json(npm workspaces)
{
"workspaces": [
"packages/*",
"apps/*"
]
}
# 特定パッケージだけにコマンド実行
npm -w @my-org/api run dev
npm -w @my-org/web run build
5. どちらを選ぶべきか — ユースケース別推奨
| ユースケース | 推奨 | 理由 |
|---|---|---|
| 個人の小規模プロジェクト | どちらでも OK | 差はほぼ体感できません |
| 大規模モノレポ(10+ パッケージ) | pnpm | --filter の柔軟性、ディスク節約、インストール速度が顕著に効きます |
| CI/CD のビルド時間短縮が最優先 | pnpm | キャッシュ効率が高く、コールドスタートでも高速です |
| OSS ライブラリの公開 | npm or pnpm | コントリビューターの参入障壁を下げたいなら npm、厳密な依存管理を優先するなら pnpm |
| チームに Node.js 初心者が多い | npm | 追加ツール不要で学習リソースが豊富です |
| 幽霊依存を絶対に許容したくない | pnpm | 構造的に防止できる唯一の主要パッケージマネージャーです |
| Docker イメージサイズの最小化 | pnpm | pnpm deploy でプロダクション依存だけを抽出でき、イメージの軽量化に貢献します |
| 既存の npm プロジェクトからの移行 | pnpm (容易) | pnpm import で package-lock.json から pnpm-lock.yaml を自動生成できます |
6. まとめ
npm は 「何も考えずにすぐ使える」安心感と互換性 が最大の武器です。Node.js エコシステムの事実上の標準であり、今後もその地位は揺るがないでしょう。
一方 pnpm は 「速度・ディスク効率・依存関係の正確さ」 という、プロジェクトが大きくなるほど効いてくる実利を提供します。CLI 体系が npm とほぼ同じため、移行コストは想像以上に低いです。
迷ったときの判断基準はシンプルです。
- 今のプロジェクトで「
node_modulesが巨大すぎる」「CI が遅い」「宣言していないパッケージが import できてしまう」といった課題を感じているなら → pnpm を試してください。 - 特に困っていないなら → npm のままで問題ありません。
どちらを選んでも package.json の書き方やアプリケーションコードは変わりません。パッケージマネージャーの切り替えはプロジェクトの寿命に比べれば小さな変更です。まずは手元で試し、チームに合う方を選びましょう。