Fragments of verbose memory

冗長な記憶の断片 - Web技術のメモをほぼ毎日更新

Jan 15, 2026 - 日記

Bunは速い、pnpmは正しい:2つのパッケージマネージャが示すJSエコシステムの未来

Bunは速い、pnpmは正しい:2つのパッケージマネージャが示すJSエコシステムの未来

npmやYarnからの移行を検討していると、必ず選択肢に上がるpnpmBun 。どちらも「npmより速い」とアピールしていますが、実際のところ何が違うのでしょうか。

State of JS 2024では、pnpmは「継続利用意向(retention)」が高く、開発者からの評価が安定していることがわかります。一方で、Bunも新しい選択肢として存在感を増しています。しかし、両者の設計思想は対照的です。pnpmは「正しい依存関係管理」を、Bunは「速度とDX」を追求しています。本記事では、この2つのパッケージマネージャの違いを整理し、あなたのプロジェクトに合った選択肢を見つけるヒントを提供します。

npmの何が問題だったのか

移行を考える前に、まずnpmの抱える課題を整理しましょう。

1. 遅いインストール速度

公式ベンチマーク(2026年1月時点)によると、clean installの速度は以下の通りです:

パッケージマネージャ時間
npm34.1秒
pnpm8.5秒(4倍速)
Yarn Classic7.2秒
Yarn PnP3.5秒

ローカル開発での npm install 待ち時間だけでなく、CI/CDパイプラインでの累積時間を考えると、この差は無視できません。

2. ディスクスペースの浪費

npmはプロジェクトごとに node_modules にパッケージをコピーするため、同じパッケージが複数プロジェクトで重複します。10個のプロジェクトで同じ依存関係を持つと、10倍のディスク容量を消費します。

3. 隠れた依存関係(Phantom Dependencies)

npmのフラットな node_modules 構造では、package.json に明示的に書いていない依存関係でも require() できてしまいます。これにより、実際には依存しているのに package.json に記載されていない「ファントム依存」が生まれ、プロジェクトの移植性を損ないます。

pnpmの哲学:正しさと堅牢性

pnpm(performant npm)は、npmの問題を「正しい方法」で解決しようとするパッケージマネージャです。

Content-Addressable Store

pnpmの最大の特徴は、グローバルな Content-Addressable Store を使用する点です。全てのパッケージは一度だけディスクに保存され、各プロジェクトの node_modules からハードリンクで参照されます。

1
2
3
4
5
6
7
8
9
# 同じパッケージを複数プロジェクトで使用しても
# ディスク使用量は1つ分だけ
~/.pnpm-store/
├── [email protected]/
└── [email protected]/

# 各プロジェクトはハードリンクで参照
project-a/node_modules/react  → ~/.pnpm-store/[email protected]/
project-b/node_modules/react  → ~/.pnpm-store/[email protected]/

これにより、プロジェクト数が増えるほどディスク使用量を大きく削減できます。

ストアの場所は環境によって異なるので、pnpm store path で確認できます。

厳格な依存関係管理

pnpmは node_modules 内にシンボリックリンク構造を作成し、package.json に記載された依存関係のみがアクセス可能になります。

1
2
3
4
5
6
node_modules/
├── .pnpm/                    # 実際のパッケージはここに
│   ├── [email protected]/
│   └── [email protected]/
├── react -> .pnpm/[email protected]/node_modules/react
└── (lodashはアクセス不可)

この仕組みにより、Phantom Dependencies を技術的に防止します。npmでは動いていたコードがpnpmで壊れる場合、それは潜在的なバグが顕在化しただけです。

移行時に直面する現実

pnpmへの移行で最も多い問題は、まさにこの「正しさ」です。以下のようなコードが動かなくなります:

1
2
3
// package.json には "react" だけ記載
// でも lodash は react の依存関係なので npm では動く
import _ from 'lodash';  // ❌ pnpm ではエラー

修正方法は明確です:

1
2
# 本当に必要なら明示的に追加
pnpm add lodash

この「壊れる」体験は、プロジェクトの健全性を診断する良い機会です。Redditのスレッド では、「4日かかったが100%価値があった」という報告もあります。

Bunの哲学:速度とDX

Bunは、パッケージマネージャの枠を超えた「オールインワンJavaScriptランタイム」です。

Zigで書かれた高速性

BunはZig言語で実装されており、パッケージマネージャだけでなく、JavaScriptランタイム、テストランナー、バンドラーを統合しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# パッケージマネージャとして
bun install        # npm install より圧倒的に速い

# ランタイムとして
bun run app.ts     # Node.js/ts-node より速い

# テストランナーとして
bun test           # Jest より速い

# バンドラーとして
bun build app.ts   # webpack/esbuild より速い

全てが統合されているため、ツールチェーンの切り替えコストがなく、一貫したDX(開発者体験)を提供します。

速度重視のアーキテクチャ

Bunのインストール速度は驚異的です。公式ベンチマークでは、pnpmやYarnをさらに上回る速度を記録しています。ただし、これは従来のContent-Addressable Storeとは異なるアプローチによるものです。

互換性の課題

Bunの最大の課題はNode.jsエコシステムとの互換性です。ネイティブモジュール(.node ファイル)や一部のAPIで問題が発生することがあります。

1
2
# 例:node-gypに依存するパッケージ
bun install bcrypt  # 動かない場合がある

公式は「100%互換を目指す」旨を掲げていますが、実際のプロジェクトでは一部の差分が致命的になる場合があります。

どちらを選ぶべきか:プロジェクト特性別ガイド

pnpmを選ぶべきケース

以下の条件に当てはまる場合、pnpmが最適です:

  • 大規模プロジェクト/モノレポ: 複数パッケージの依存関係を厳格に管理したい
  • CI/CDコストが気になる: GitHub Actions等の課金を削減したい
  • 長期運用: 依存関係の健全性を重視する
  • npmからの移行: 互換性が高く、段階的に移行できる

特にTurborepoNx を使ったモノレポ構成では、pnpm workspaceが定番になっています。

1
2
3
4
# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'

Bunを選ぶべきケース

以下の条件に当てはまる場合、Bunが最適です:

  • 新規プロジェクト: グリーンフィールドで互換性問題が少ない
  • 速度至上主義: 開発サイクルの高速化を最優先
  • TypeScriptメイン: ネイティブモジュールへの依存が少ない
  • 実験的プロジェクト: 最新技術にキャッチアップしたい

特に個人プロジェクトやスタートアップの初期段階では、Bunの統合性とスピードが開発効率を大きく向上させます。

両方試す価値がある理由

実は、pnpmとBunは排他的ではありません。以下のような使い分けも可能です:

1
2
3
4
5
6
7
# ローカル開発はBunで高速に
bun install
bun run dev

# CIはpnpmで安定性を確保(Dockerキャッシュが効きやすい)
pnpm install --frozen-lockfile
pnpm test

移行の実際:4つのステップ

1. 準備(npm → pnpm/Bun)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 既存のnode_modulesを削除
rm -rf node_modules

# pnpmの場合
npm install -g pnpm
pnpm import  # package-lock.json から pnpm-lock.yaml を生成(package-lock.json は残しておく)
rm -f package-lock.json
pnpm install

# Bunの場合
curl -fsSL https://bun.sh/install | bash
bun install

2. 互換性確認

1
2
3
4
5
# テストを実行して問題を検出
pnpm test  # or bun test

# ビルドの確認
pnpm build  # or bun run build

3. Phantom Dependencies の修正(pnpmの場合)

エラーが出たパッケージは明示的に追加します:

1
2
3
4
# エラー例:
# Error: Cannot find module 'lodash'

pnpm add lodash

4. CI/CDの更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# GitHub Actions の例(pnpm)
- uses: pnpm/action-setup@v2
  with:
    version: 8
- run: pnpm install --frozen-lockfile
- run: pnpm test

# GitHub Actions の例(Bun)
- uses: oven-sh/setup-bun@v1
  with:
    bun-version: latest
- run: bun install --frozen-lockfile
- run: bun test

まとめ:思想の違いが選択を決める

pnpmとBun、どちらも「npmより速い」という点では共通していますが、その根底にある思想は大きく異なります。

pnpm は、正しさと堅牢性を追求します。Phantom Dependenciesを防ぎ、ディスク効率を高め、長期的なプロジェクトの健全性を保証します。移行時に「壊れる」のは、むしろプロジェクトの隠れた問題を発見するチャンスです。

Bun は、速度とDXを追求します。パッケージマネージャ、ランタイム、テストランナー、バンドラーを統合し、一貫した高速体験を提供します。新規プロジェクトや実験的な開発で真価を発揮します。

どちらを選ぶかは、あなたのプロジェクトの優先順位次第です。大規模で長期運用ならpnpm、速度と革新性を求めるならBun。幸い、両者とも移行コストは低く、気軽に試せます。

npmの node_modules 地獄に疲れたなら、まずは小さなプロジェクトでどちらかを試してみてください。その体験が、あなたの次のプロジェクトでの選択を決めるはずです。

参考リンク