
この記事はNovasell Advent Calendar 2025の18日目の記事です。
初めましてエンジニアの小川です。ノバセルにて主にAIプロダクトの開発をしています。
本記事では、フロントエンドのモノレポ環境において npm / Yarn / pnpm / Bun の4つのパッケージマネージャを実際に導入し、速度とディスク使用量を比較検証した結果をまとめています。
この記事の対象読者
- フロントエンドのモノレポ環境で
npm installの遅さに悩んでいる方 - パッケージマネージャの違いを実測データで比較したい方
- pnpm や Bun への移行を検討している方
はじめに
私たちのプロジェクトには、多数のフロントエンドアプリが集まっているリポジトリがあります。
frontend/ ├── packages/ # 共有パッケージ │ ├── pkg-A/ │ ├── pkg-B/ │ └── pkg-C/ ├── app-1/ ├── app-2/ ├── app-3/ ├── app-4/ └── ... 他10アプリ以上
合計で 20個以上のフロントエンドアプリ がこのディレクトリに存在しています。
アプリが急増していく中で、きちんとした管理がされていませんでした。
そのため使っているライブラリがかなり似通っているのにも関わらず、それぞれのアプリで個別にライブラリをインストールしていました。
共通で使われている主なライブラリ:
- React 18 / React DOM - 全アプリ共通
- @radix-ui/react-* - 10種類以上のUIコンポーネント
- TailwindCSS / tailwind-merge - スタイリング
- react-hook-form / zod - フォーム管理
- @tanstack/react-query - データフェッチ
- lucide-react - アイコン
- Vite - ビルドツール
ローカルでのinstallもCI/CDも全アプリに渡るとそこそこ時間がかかっており、開発者体験も悪くなっていました。
何か非効率だなと思っていたのですが、ある時興味本位でJavaScriptのパッケージマネージャについて調べていました。
そこで、ちゃんとパッケージマネージャを選ぶことで改善できるのではないかと思い、この比較を試してみることにしました。
比較対象のパッケージマネージャ
今回比較するのは以下の4つのパッケージマネージャです。
| パッケージマネージャ | 初リリース | 特徴 |
|---|---|---|
| npm | 2010年 | Node.js標準。最も高い互換性 |
| Yarn (Berry) | 2020年 | Plug'n'Play、Zero-Install |
| pnpm | 2017年 | グローバルストア + ハードリンクで効率化 |
| Bun | 2023年 | Zig製。JSランタイム内蔵で超高速 |
npmは元々使っていたパッケージマネージャで、今回はworkspacesの設定を行った状態で比較します。
Yarnはv1(Classic)とv2以降(Berry)がありますが、Classicはnpmと大きな違いはないため今回はBerryを対象とします。
検証
検証環境
| 項目 | 内容 |
|---|---|
| マシン | MacBook Pro(Apple M3 Pro) |
| メモリ | 36GB |
| Node.js | v24.3.0 |
| 測定方法 | キャッシュクリア後の初回インストール |
実際にキャッシュ削除用で使っていたスクリプトです。
#!/usr/bin/env bash
set -euo pipefail
echo "== Project =="
# プロジェクト内のnode_modulesディレクトリを全て削除
find . -name node_modules -type d -prune -exec rm -rf '{}' + 2>/dev/null || true
# 各パッケージマネージャーのロックファイルを全て削除
find . -type f \( -name package-lock.json -o -name pnpm-lock.yaml -o -name yarn.lock -o -name bun.lockb \) -not -path "*/node_modules/*" -delete 2>/dev/null || true
echo "== npm =="
# npmのグローバルキャッシュとnode-gypのビルドキャッシュを削除
rm -rf ~/.npm ~/.node-gyp ~/.cache/node-gyp 2>/dev/null || true
echo "== yarn cache only =="
# yarnのキャッシュをクリーンアップコマンドで削除
yarn cache clean 2>/dev/null || true
# yarnのキャッシュディレクトリを直接削除
rm -rf ~/.cache/yarn 2>/dev/null || true
echo "== pnpm store (公平性のため完全削除) =="
# pnpmのグローバルストア(共有パッケージストレージ)を完全削除
rm -rf ~/.pnpm-store ~/.local/share/pnpm/store 2>/dev/null || true
echo "== bun cache =="
# bunのインストールキャッシュとビルドキャッシュを削除
rm -rf ~/.bun/install/cache ~/.cache/bun 2>/dev/null || true
echo "== Done =="
ベースライン(改善前の状態)
ただいろんなアプリが同じディレクトリ内に入っているだけの状態です。
以下のコマンドを使って一気にinstallします。
# 全ディレクトリをループしてinstall
time (find . -maxdepth 2 -name package.json -not -path "*/node_modules/*" \
| xargs -I {} dirname {} \
| xargs -I {} sh -c 'cd {} && npm install')
サイズは以下のコマンドで計測します。
# node_modules の合計サイズ du -shc $(find . -name "node_modules" -type d -prune) | tail -n 1
結果
| 指標 | 値 |
|---|---|
| 速度 | 113.8秒 |
| サイズ | 5.2GB |
63.69s user 80.65s system 126% cpu 1:53.81 total
💡 出力の見方
- user: ユーザーCPU時間。プログラム自体の処理にCPUを使った時間
- system: システムCPU時間。ファイル操作やネットワーク通信などOSを介した処理の時間
- cpu: CPU使用率。100%を超えていれば複数コアで並列処理されている
- total: 実時間。コマンド実行から完了までの経過時間
他との比較がまだですが、明らかに時間とディスク容量の両面で課題があります。
特に約2分という時間は、開発者の作業フローを阻害する要因となります。
では、これを基準にどれだけ改善されていくか見ていきましょう。
npm(workspaces)
ここではパッケージマネージャの違いではなく、設定の違いでどれだけ変わるかを見ていきます。
まずはworkspaceの設定のため、ルートであるfrontend/にpackage.jsonを作成します。
{ "name": "frontend-monorepo", "private": true, "workspaces": [ "*", "packages/*" ] }
そして速度とサイズを計測します。ここからinstallは1回実行すれば良いので、速度はこれ以降以下のコマンドを使っていきます。
time npm install # または yarn install / pnpm install / bun install
結果
| 指標 | 値 | ベースラインとの比較 |
|---|---|---|
| 速度 | 51.2秒 | 55%短縮 |
| サイズ | 657MB | 87%削減 |
16.82s user 10.42s system 53% cpu 51.203 total
この時点で速度もサイズもかなり改善されています。
それぞれのアプリがどれも同じテンプレートをベースに作られているということもあり、共通のライブラリが1度のインストールでまとめられています。
サイズが劇的に小さくなっているのはそのためです。
従来の方法では、同じライブラリを複数回インストールしていたため、大きな無駄が生じていたことがわかります。
開発段階やCI/CDでnpm installが1回で済むというのも、運用面で大きなメリットとなります。
Yarn (Berry)
ここからはパッケージマネージャを変えて速度とサイズの変化を見ていきます。
Yarn Berryではnode_modulesを使わないPlug'n'Play (PnP) モードが有名なのですが、既存のアプリとの互換性の問題があり、今回はnode_modulesを使用するモードで導入します。
まずは以下を実行しYarn Berryを有効化します。
yarn set version berry
それによって生成された.yarnrc.ymlにnodeLinker: node-modulesを追加します。
yarnPath: .yarn/releases/yarn-4.12.0.cjs nodeLinker: node-modules
結果
| 指標 | 値 | ベースラインとの比較 |
|---|---|---|
| 速度 | 21.0秒 | 82%短縮 |
| サイズ | 631MB | 88%削減 |
19.11s user 10.12s system 139% cpu 20.970 total
ベースラインから82%短縮、サイズも88%削減と大幅に改善されました。
npm(workspaces)の51.2秒と比べても約2.4倍速くなっています。
user(実処理)やsystem(I/O)にかかる時間はnpmとあまり変わっていませんがcpuの値は139%と100%を超えています。
このデータからマルチコアを効率的に使用し、ディスクI/Oやファイルロックなどでの待ち時間を減らすことで高速化できたと考えられます。
pnpm
frontend/以下にpnpm-workspace.yamlを作成し以下を記述します。
packages: - '*' - 'packages/*'
結果
| 指標 | 値 | ベースラインとの比較 |
|---|---|---|
| 速度 | 20.0秒 | 82%短縮 |
| サイズ | 513MB | 90%削減 |
12.04s user 19.25s system 156% cpu 20.000 total
ベースラインから82%短縮、サイズは90%削減とさらに改善しました。
また、サイズが最も小さくなっています。これはpnpmの設計が関係しています。
📦 pnpmの仕組み
pnpmではインストールしたパッケージはグローバルコンテンツストア(
~/.pnpm-store)に1度だけ保存されます。各プロジェクトのnode_modulesには、パッケージ本体へのハードリンクと、依存関係を解決するためのシンボリックリンクが組み合わせて作成されます。この設計では、同じバージョンのパッケージはディスク上に1つしか存在しません。プロジェクトごとにパッケージが複製されることがないため、大幅にサイズが節約できます。
また、パッケージが一元管理されていることで、既にインストールしたことのあるパッケージであればリンクを作成するだけで済むため、インストール速度の向上にも効いてきます。
この点から、pnpmはモノレポ構成に適したパッケージマネージャと言えます。
Bun
特に導入作業は要りませんでした。package.jsonがあればそのまま動きます。
結果
| 指標 | 値 | ベースラインとの比較 |
|---|---|---|
| 速度 | 8.4秒 | 93%短縮 |
| サイズ | 525MB | 90%削減 |
3.04s user 13.89s system 201% cpu 8.391 total
ベースラインから93%短縮という圧倒的に速い結果となりました。 pnpmの20.0秒と比べても約2.4倍の速さです。ベースラインの113.8秒と比べると13.6倍速いことになります。
サイズはpnpmより若干大きいですが、90%削減と十分な改善です。
この速さの要因には言語とアーキテクチャが関わっています。
⚡ Bunが速い理由
1. 言語の違い npmやpnpmはJavaScriptで書かれていますが、BunはZigという言語で書かれています。Zigはコンパイルされてネイティブコードで動作するため、実行速度がかなり高速です(Cの後継とも言われています)。
userの値が 12.04s → 3.04s になっていることからも明らかです。2. マルチスレッドI/O Bunはダウンロードや解凍、ファイルシステムの操作といった時間がかかるI/O処理を積極的にマルチスレッドで処理します。
cpuが201%と非常に高く、並列処理を最大限に活用していることがわかります。
結果比較
段階的にパッケージマネージャを変えて改善幅を見てきましたが、出揃ったところで全体を振り返ってみましょう。
以下がそれぞれの方法でインストールした場合の速度とサイズの比較です。
| パッケージマネージャ | 速度 | サイズ | 速度(対ベースライン) | サイズ(対ベースライン) |
|---|---|---|---|---|
| ベースライン(個別install) | 113.8秒 | 5.2GB | - | - |
| npm (workspaces) | 51.2秒 | 657MB | 55%短縮 | 87%削減 |
| Yarn (Berry) | 21.0秒 | 631MB | 82%短縮 | 88%削減 |
| pnpm | 20.0秒 | 513MB | 82%短縮 | 90%削減 |
| Bun | 8.4秒 | 525MB | 93%短縮 | 90%削減 |
視覚的に見るとこうなります:
速度(短いほど良い) ベースライン ████████████████████████████████████████ 113.8s npm ██████████████████░░░░░░░░░░░░░░░░░░░░░░ 51.2s Yarn ███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 21.0s pnpm ███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 20.0s Bun ███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 8.4s
CI/CDへの影響
ローカル環境での改善が確認できたので、次は実際のCI/CDパイプラインで各パッケージマネージャを導入した際の効果を検証しました。
CI(継続的インテグレーション)
GitHub Actionsで全フロントエンドアプリのリント&テストを並列実行しています。
| パッケージマネージャ | 開発者の待ち時間 | ジョブ累計実行時間 | ジョブ実行時間の短縮率 |
|---|---|---|---|
| ベースライン (個別install) | 1分13秒 | 23分32秒 | - |
| npm (workspaces) | 2分25秒 | 13分35秒 | 42%短縮 |
| Yarn (Berry) | 1分18秒 | 7分27秒 | 68%短縮 |
| pnpm | 1分10秒 | 7分52秒 | 67%短縮 |
| Bun | 1分7秒 | 7分0秒 | 70%短縮 |
matrix strategyでアプリごとに並列実行しているため、開発者の待ち時間は大きく変わりません。
しかし、ジョブ累計実行時間は42〜70%削減と大幅に改善されました。
GitHub Actionsは実行時間に応じてコストがかかるため、チーム全体で1日に何度も実行することを考えると、この削減効果は大きいです。
CD(継続的デプロイ)
本番環境へのデプロイでも検証を行いました。
| パッケージマネージャ | デプロイ時間 | 短縮率 |
|---|---|---|
| ベースライン(個別install) | 5分50秒 | - |
| npm (workspaces) | 3分39秒 | 37%短縮 |
| Yarn (Berry) | 3分37秒 | 38%短縮 |
| pnpm | 3分33秒 | 39%短縮 |
| Bun | 3分35秒 | 39%短縮 |
デプロイでも約40%(約2分)の短縮ができました。
興味深いことに、CDでは4つのパッケージマネージャがほぼ同等の結果になりました。
最速のpnpmと最遅のnpmでも6秒差しかありません。
つまり、CDではパッケージマネージャの違いよりも、workspaces設定そのものの効果(2分11秒の短縮)の方が大きいと言えます。
どれを選ぶか?
| 観点 | npm | Yarn | pnpm | Bun |
|---|---|---|---|---|
| 速度(ローカル) | △ | ○ | ○ | ◎ |
| 速度(CI) | △ | ○ | ○ | ◎ |
| 速度(CD) | ○ | △ | ○ | ◎ |
| ディスク効率 | △ | ○ | ◎ | ○ |
| 互換性・安定性 | ◎ | ○ | ○ | △ |
| 情報の豊富さ | ◎ | ○ | ○ | △ |
| npm との互換性 | - | ○ | ◎ | ○ |
速度だけ見ればBunが圧倒的ですが、実際のプロジェクトでは他の要素も考慮する必要があります。
今後正式に移行していくとしたら私はpnpmを選ぶと思います。
理由は以下の通りです:
速度はBunが最速だが...
- Bunは比較的新しいツールで、エコシステムがまだ発展途上
- まだ枯れていないツールのトラブルシューティングにリソースを割くのは避けたい
pnpmの安心感
- npmとの互換性が高く、情報も豊富
- トラブル時に情報が見つかりやすいことは重要
速度も十分
- pnpmでもローカル82%短縮、サイズ90%削減と十分な改善ができている
- Yarnとほぼ同等の速度で、ディスク効率はより優れている
まとめ
今回は既存のフロントエンドモノレポの改善の一環として、主要なパッケージマネージャの速度とサイズ、そしてCI/CDへの影響を比較検証しました。
それぞれの違いを意識せず使用していましたが、実際に計測してみることで、各パッケージマネージャの特徴が明確になりました。
特に以下のようなことが印象的でした:
- workspacesを設定するだけで87%もサイズが削減できたこと
- Bunの圧倒的な速度とマルチスレッド処理の効果
- pnpmのグローバルストアという賢い設計とディスク効率の高さ
この検証を通じて、それぞれの設計思想や特徴を理解することができました。今後はプロジェクトの特性に応じて、適切なパッケージマネージャを選択し、適切に機能を使っていきたいと思います。
この記事が、パッケージマネージャを選定する際の参考になれば幸いです。