pnpmからBunへの移行でCI実行時間を64%削減した話
はじめに
私たちのプロジェクトでは、パッケージマネージャーとしてpnpmを使用していましたが、CI/CDパイプラインの実行時間が課題となっていました。特に、依存パッケージのインストールやテストの実行に多くの時間がかかり、開発効率に影響を及ぼしていました。
そこで、高速なパフォーマンスで注目を集めているBunへの移行を決断しました。本記事では、実際の移行プロセスと、その結果得られた大幅なパフォーマンス改善について詳しく解説します。
プロジェクト構成
本プロジェクトは、以下のような構成のモノレポです:
- Backend: Hono.js + Prisma + TypeScript
- Frontend: React + Vite + TypeScript
- CI/CD: GitHub Actions
- テスト: Bun Test(バックエンド)
移行前の課題
CI実行時間の増加
プロジェクトの規模拡大に伴い、以下のような課題が顕在化していました:
- テスト実行時間: 特にリポジトリ層のテストが1分以上かかる
- 依存パッケージのインストール時間: pnpm installに時間がかかる
これらの課題により、PR作成からマージまでのリードタイムが長くなり、開発速度に影響を与えていました。
Bunとは
Bunは、JavaScriptおよびTypeScriptのための高速なオールインワンツールキットです。以下の特徴を持ちます:
- 高速なパッケージマネージャー: npmやpnpmと比較して圧倒的に高速
- ビルトインテストランナー: Jest互換のAPIを持つ高速なテストランナー
- バンドラー: 高速なJavaScript/TypeScriptバンドラー
- Node.js互換: 既存のNode.jsエコシステムとの高い互換性
特に、Zigで実装された高速なランタイムと、効率的なパッケージ管理により、従来のツールと比較して大幅なパフォーマンス向上が期待できます。
移行手順
1. Bunのインストールとバージョン管理
まず、プロジェクトで使用するBunのバージョンを.tool-versionsファイルで管理することにしました:
bun 1.2.14
また、各パッケージのpackage.jsonにpackageManagerフィールドを追加し、使用するBunのバージョンを明示しました:
{
"name": "backend",
"type": "module",
"packageManager": "bun@1.2.14",
// ...
}
2. パッケージのインストール
pnpmからBunへの移行は非常にシンプルです:
# pnpmのロックファイルと依存関係を削除
rm -rf node_modules pnpm-lock.yaml
# Bunで依存関係をインストール
bun install
Bunはpackage.jsonを読み取り、互換性のある形で依存関係をインストールします。bun.lockファイルが生成され、これが新しいロックファイルとなります。
3. スクリプトの更新
package.json内のスクリプトを、Bunコマンドに対応するよう更新しました:
{
"scripts": {
"dev": "bun --watch src/index.ts",
"build": "bun build src/index.ts --outdir dist --target bun",
"test": "bun test",
"fmt": "bunx @biomejs/biome format --write .",
"lint": "bunx @biomejs/biome lint ."
}
}
bunxコマンドは、npxやpnpm dlxに相当するコマンドで、パッケージを一時的にインストールして実行します。
4. GitHub Actionsの更新
GitHub ActionsのワークフローをBunに対応させました。主な変更点は以下の通りです:
Bunのセットアップ
- name: Setup Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version-file: ".tool-versions"
キャッシュキーの更新
pnpmからBunへの移行に伴い、キャッシュキーも更新しました:
- name: Get node_modules cache
uses: actions/cache@v4
with:
path: |
**/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('backend/bun.lock', 'backend/package.json') }}
restore-keys: |
${{ runner.os }}-bun-${{ hashFiles('backend/bun.lock') }}
依存関係のインストール
- name: Install dependencies
run: bun install --frozen-lockfile
--frozen-lockfileオプションは、pnpmの--frozen-lockfileやnpmのciコマンドに相当し、ロックファイルを更新せずに依存関係をインストールします。
移行後の効果
テスト実行時間の劇的な改善
最も顕著な改善が見られたのは、リポジトリ層のテスト実行時間です:
| 項目 | 移行前(pnpm) | 移行後(Bun) | 削減率 |
|---|---|---|---|
| Repository Layer Tests | 1m 5s | 23s | 約64%削減 |
実行時間の比較
| 移行前(pnpm) | 移行後(Bun) |
|---|---|
![]() |
![]() |
| 1分5秒 | 23秒 |
この改善により、PRのフィードバックループが大幅に短縮され、開発速度が向上しました。
なお、今回は具体的な数値データがあるリポジトリ層のテストのみを紹介していますが、実際には他のCIワークフロー(ビルド、デプロイなど)でも同様に実行時間の短縮が確認されています。Bunへの移行は、プロジェクト全体のCI/CD効率化に大きく貢献しました。
移行時の注意点と課題
1. ロックファイルの管理
pnpmからBunへの移行に伴い、ロックファイルがpnpm-lock.yamlからbun.lockに変更されます。移行時には、以下の点に注意が必要です:
-
既存のロックファイルの削除:
pnpm-lock.yamlを削除してからbun installを実行 - バージョンの固定: 依存パッケージのバージョンが変わらないことを確認
-
Git管理:
bun.lockをGitにコミット
2. CI/CDの段階的な移行
すべてのワークフローを一度に移行するのではなく、段階的に移行することをお勧めします:
- ローカル開発環境での検証: まずローカルでBunを試す
- 非クリティカルなワークフローから移行: lintやformatチェックなど
- テストワークフローの移行: テストが正常に動作することを確認
- デプロイワークフローの移行: 最後にデプロイ関連のワークフローを移行
3. 互換性の確認
Bunは高いNode.js互換性を持ちますが、一部のパッケージでは動作しない可能性があります:
- ネイティブモジュール: 一部のネイティブモジュールは動作しない可能性がある
- 特定のNode.js API: 一部のNode.js APIはまだ実装されていない可能性がある
私たちのプロジェクトでは、以下のパッケージで問題なく動作しました:
- Prisma(ORM)
- Hono(Webフレームワーク)
- Vite(ビルドツール)
- Biome(Linter/Formatter)
4. テストランナーの移行
Bunは独自のテストランナーを持っており、Jest互換のAPIを提供しています。私たちのプロジェクトでは、以下のように移行しました:
// 移行前(Jest)
import { describe, it, expect } from '@jest/globals';
// 移行後(Bun)
import { describe, it, expect } from 'bun:test';
ただし、多くの場合、インポート文を変更する必要はなく、Bunが自動的に互換性を提供します。
5. キャッシュ戦略の見直し
Bunは独自のキャッシュメカニズムを持っているため、GitHub Actionsのキャッシュ戦略を見直す必要がありました:
# 最適化されたキャッシュ設定
- name: Get node_modules cache
uses: actions/cache@v4
with:
path: |
**/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
移行のベストプラクティス
1. バージョン管理の徹底
.tool-versionsファイルとpackage.jsonのpackageManagerフィールドを使用して、Bunのバージョンを明示的に管理しましょう:
{
"packageManager": "bun@1.2.14"
}
2. ロックファイルの固定
CI/CDでは必ず--frozen-lockfileオプションを使用して、ロックファイルを固定しましょう:
bun install --frozen-lockfile
3. 段階的な移行
すべてを一度に移行するのではなく、段階的に移行することで、問題が発生した際の切り戻しが容易になります。
4. パフォーマンスの測定
移行前後でパフォーマンスを測定し、改善効果を定量的に評価しましょう。GitHub Actionsの実行時間を比較することで、効果を可視化できます。
まとめ
pnpmからBunへの移行により、**リポジトリ層のテスト実行時間が約64%削減(1m 5s → 23s)**という大幅な改善を実現できました。
この改善により、開発者のフィードバックループが短縮され、開発効率が大幅に向上しました。
Bunは、高速なパフォーマンスとNode.jsとの高い互換性を持ち、既存のプロジェクトへの導入も比較的容易です。特に、CI/CDの実行時間が課題となっているプロジェクトでは、Bunへの移行を検討する価値があるでしょう。
ただし、移行にあたっては、段階的なアプローチを取り、互換性の確認やテストの実施を十分に行うことが重要です。


Discussion