以下の内容はhttps://techblog.raksul.com/entry/2025/12/18/170510より取得しました。


モノレポ改善のためにパッケージマネージャを全部試した

この記事は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.ymlnodeLinker: 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を選ぶと思います。

理由は以下の通りです:

  1. 速度はBunが最速だが...

    • Bunは比較的新しいツールで、エコシステムがまだ発展途上
    • まだ枯れていないツールのトラブルシューティングにリソースを割くのは避けたい
  2. pnpmの安心感

    • npmとの互換性が高く、情報も豊富
    • トラブル時に情報が見つかりやすいことは重要
  3. 速度も十分

    • pnpmでもローカル82%短縮、サイズ90%削減と十分な改善ができている
    • Yarnとほぼ同等の速度で、ディスク効率はより優れている

まとめ

今回は既存のフロントエンドモノレポの改善の一環として、主要なパッケージマネージャの速度とサイズ、そしてCI/CDへの影響を比較検証しました。

それぞれの違いを意識せず使用していましたが、実際に計測してみることで、各パッケージマネージャの特徴が明確になりました。

特に以下のようなことが印象的でした:

  • workspacesを設定するだけで87%もサイズが削減できたこと
  • Bunの圧倒的な速度とマルチスレッド処理の効果
  • pnpmのグローバルストアという賢い設計とディスク効率の高さ

この検証を通じて、それぞれの設計思想や特徴を理解することができました。今後はプロジェクトの特性に応じて、適切なパッケージマネージャを選択し、適切に機能を使っていきたいと思います。

この記事が、パッケージマネージャを選定する際の参考になれば幸いです。




以上の内容はhttps://techblog.raksul.com/entry/2025/12/18/170510より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14