12k
All articles

pnpm の Rust リライトの舞台裏

pnpm 12のRust書き換え、ベンチマーク結果、pnpm 11との互換性、CIのインストールに影響するアップグレード時の注意点を解説します。

OpenReplay Team
OpenReplay Team
pnpm の Rust リライトの舞台裏

pnpm 12 は、TypeScript で書かれていた pnpm のコードベースを Rust によるネイティブ実装に置き換えました。その一方で、pnpm 11 のコマンド、フラグ、設定、ロックファイル形式はそのまま維持しています。そのため、ほとんどのプロジェクトは設定を変更せずにアップグレードできます。

モノレポや多数の CI ジョブで pnpm を使っているなら、「90% 高速化」という見出しを見て、何か落とし穴があるのではと気になったかもしれません。ロックファイルを書き出すツールのメジャーバージョンアップは、ベンチマークのグラフ以上に慎重に検証すべきものです。

本記事では、JavaScript 製のパッケージマネージャーがそもそもなぜ遅いのか、Rust エンジンで何が変わり何を代償にしたのか、各数値を誰が計測したのか、そしてどの挙動変更がパイプラインに影響しうるのかを解説します。

重要なポイント

  • pnpm 12.0.0 は 2026 年 8 月 26 日に安定版となりました。ドキュメント化された一部の相違点を除き、pnpm 11 のコマンド、フラグ、設定、ロックファイル形式を維持しています。
  • pnpm 公式のベンチマークページ(pnpm 11.27.1 と 12.7.0 の比較)では、ウォームな再インストールは 563 ms から 18 ms に短縮されています。一方、クリーンインストールは 8.4 秒から 4.4 秒への短縮にとどまります。コールドインストールでは、ネットワーク転送と展開処理が処理時間の大半を占めるためです。
  • Vercel の計測では、1,670 パッケージを含むワークスペースのインストール時間が、pnpm 10.28 と比べて pnpm 12 で 64.4%〜90.5% 短縮されました。ただし、ネイティブバイナリのダウンロードサイズが大きくなったため、キャッシュなしでの Corepack 起動は 11.1% 遅くなっています。
  • CI を壊す可能性が最も高い変更は pnpm install --resolution-only の削除です。代わりに pnpm peers check を使用してください。

制約条件:pnpm はそのまま、エンジンだけを刷新

pnpm 12 は、アップグレードが「移行作業」にならないことを前提に開発されました。pnpm 12.0 のリリース記事はこれを目標として掲げています。また互換性ガイドでも、一部の相違点を除いて pnpm 12 が pnpm 11 のコマンド、フラグ、設定、ロックファイル形式を維持していることが確認できます。pnpm 12 はコンテンツアドレス可能ストアも引き継いでおり、プロジェクト間でパッケージファイルをコピーせずに共有できます。InfoQ も、node_modules のレイアウトに変更はないと報じています。

多くのリライトでは、新しいコードベースを過去の設計判断を見直す機会として扱います。しかし pnpm は互換性を最優先にしました。その徹底ぶりは、pnpm のドキュメントが両バージョンに共通して適用されると明言しているほどです。表面上はほぼ何も変わっていません。作業の中心は、その下にあるすべてを置き換えることでした。

なぜ JavaScript 製のパッケージマネージャーは遅いのか

JavaScript で書かれたパッケージマネージャーは、大規模なインストールのたびに 2 つのコストを負担します。1 つは、起動のたびに Node.js ランタイムを立ち上げるコストです。もう 1 つは、数千件ものファイルシステム操作を単一の JavaScript ランタイム経由で処理するコストです。

インストールは、おおむね次のフェーズで進みます。

  1. レジストリからパッケージのメタデータを取得する。
  2. 依存関係グラフを解決する。
  3. tarball をダウンロードする。
  4. tarball をストアに展開する。
  5. パッケージを node_modules にリンクする。

1 つ目のコストは固定費です。pnpm 11 では、実際の処理を始める前に Node.js が起動する必要がありました。pnpm 12 はプラットフォームごとに @pnpm/exe.<platform>-<arch> パッケージとして、ネイティブバイナリで配布されています。self-update のドキュメントにあるとおり、事前に Node.js が起動することはないため、この起動コストはなくなりました。

2 つ目のコストは、処理量に比例して増えます。フェーズ 4 と 5 では tarball の展開と数千ファイルのハードリンク作成が行われ、そのすべての操作が JavaScript ランタイムを経由します。

この原則はどのツールにも当てはまります。固定オーバーヘッドの削減が最も効果を発揮するのは、ほかにやるべき処理がほとんど残っていない場合です。インストールでほぼ何もすることがなければ、実行時間の大半は起動時間が占めます。逆に数百 MB をダウンロードする必要がある場合、起動時間はほとんど無視できる程度になります。

pnpm 12 はどれくらい速くなったのか

pnpm 12 の高速化は本物ですが、効果にはばらつきがあります。ウォームな再インストールは 563 ms から 18 ms に短縮される一方、クリーンインストールは 8.4 秒から 4.4 秒への短縮にとどまります。これらの数値は、ベースラインが異なる 2 つの計測結果に基づいています。1 つの数値にまとめないよう注意してください。

シナリオ従来pnpm 12計測者ベースライン
ウォームな再インストール563 ms18 mspnpm ベンチマークページ(pnpm 12.7.0)pnpm 11.27.1
クリーンインストール8.43 秒4.42 秒pnpm ベンチマークページ(pnpm 12.7.0)pnpm 11.27.1
1,670 パッケージのワークスペース、6 シナリオ(中央値)n/a所要時間 64.4〜90.5% 短縮Vercel(pnpm 12.0.0)pnpm 10.28.0
キャッシュなしの Corepack 起動n/a11.1% 低速化Vercel(pnpm 12.0.0)pnpm 10.28.0
キャッシュありの Corepack 起動n/a74.7% 高速化Vercel(pnpm 12.0.0)pnpm 10.28.0

pnpm の数値は、pnpm ベンチマークページに掲載されている alotta-files プロジェクトでの pnpm 11.27.1 と pnpm 12.7.0 の比較結果です。このページは定期的に再計測され、常に各ツールの最新バージョンを表示するため、数値は今後変わる可能性があります。pnpm の 2 行の差は、前セクションで説明した原則をそのまま表しています。ウォームインストールが約 30 倍高速化したのは、起動などの固定費が実行時間の大部分を占めていたためと考えられます。一方、クリーンインストールが約 1.9 倍の改善にとどまるのは、エンジンの実装言語にかかわらず、ネットワーク転送と tarball の展開が処理時間の大半を占めるためです。

Vercel 独自の計測結果にも同じ傾向が表れています。node_modules が既に存在し、ストアがウォームで、スクリプトを無効にした状態では、インストール時間は 1.476 秒から 142 ms に短縮されました。ライフサイクルスクリプトを有効にした完全なコールドインストールでは、9.850 秒から 3.472 秒になっています。各中央値は、21 プロジェクトで構成される Turborepo ワークスペースを使い、1 台の Linux マシン上で各バージョンを 20 回ずつ実行して得たものです。

pnpm 12 のネイティブビルドには代償もあります。Vercel によると、pnpm 12 の Corepack ダウンロードサイズは 47.3 MB で、pnpm 10.28.0 の 17.5 MB から増加しました。Vercel は、キャッシュなしでの起動が遅くなった原因をこのダウンロードサイズの増加としています。Corepack をキャッシュしていない CI ランナーでは、ジョブのたびにこのコストが発生する可能性が高いでしょう。

新エンジンと同時に導入された決定論的な循環依存の処理は、これとは別の改善です。pnpm の互換性ガイドは、循環依存の多いワークスペースでのメモリ使用量の約 25% 削減と、peer 依存関係の解決の 2〜3 倍の高速化について、Rust エンジンそのものではなくこの変更による効果だとしています。

pnpm の公開ベンチマークページは現在、pnpm 12 を npm および pnpm 11 とのみ比較しています。InfoQ の報道によると、ベンチマーク環境の問題によってランキングの信頼性が損なわれたため、pnpm は Bun と Yarn を比較対象から外しました。

なぜ pnpm のロックファイル形式は同一に保つ必要があったのか

pnpm 12 がロックファイル形式を維持しなければならなかったのは、形式が変わるとアップグレードの途中でチームが分断されてしまうためです。pnpm 12 が新しい形式で書き出すとしたら、すべての開発マシンと CI ランナーを同じ日に切り替える必要があります。そうしなければ、1 つのリポジトリ内で 2 種類のロックファイル形式が混在し、すべてのプルリクエストに、最後に実行したバージョンに由来する不要な差分が含まれてしまいます。

形式を維持する目的は、チームが段階的にアップグレードできるようにすることです。ファイルの内容が今後一切変わらないという意味ではありません。形式と内容は別物です。

  • 既存のロックファイルはそのまま動作します。 ガイドによると、pnpm は再解決が必要になるまで既存のエントリには手を加えません。
  • 再解決によってエントリが変わる場合があります。 pnpm 12 は、GitHub、GitLab、Bitbucket からの依存関係を、SSH URL ではなく必ず HTTPS URL で記録します。また、依存関係の循環を毎回同じ箇所で切断するため、循環依存の多いワークスペースではロックファイルが小さくなります。

pnpm 12 で最初に再解決を伴うインストールを行ったときの差分は、独立したコミットとしてレビューしてください。

pnpm 12 へのアップグレードで何が壊れるのか

pnpm 12 へのアップグレードで CI ジョブが失敗した場合、最も可能性が高い原因は、スクリプトがまだ pnpm install --resolution-only を呼び出していることです。v12 ではこのフラグは拒否されます。peer 依存関係をレポートする役割は、現在は pnpm peers check が担っています。

# pnpm 11
pnpm install --resolution-only

# pnpm 12
pnpm peers check

v12 では pnpm install --frozen-lockfile false も拒否されます。frozen-lockfile モードを無効にするには --no-frozen-lockfile を使い、有効にするには --frozen-lockfile だけを指定してください。

その他の変更は主に結果に影響するものですが、そのうち 3 つはコマンドの実行を停止させる可能性もあります。

変更内容影響を受ける対象対処法
GitHub/GitLab/Bitbucket 上の Git 依存関係を HTTPS 経由で解決するSSH 経由でアクセスしているプライベートリポジトリマシン上で Git の URL 書き換えを設定する
pnpm-workspace.yaml 内の不明なキーが報告される。プロジェクトが pnpm のバージョンを固定しており、実行中の pnpm がその条件を満たす場合は ERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGS でコマンドが失敗するタイプミスを含む設定キーを修正または削除する
グローバルインストールを変更するコマンドは、sudo 下で ERR_PNPM_SUDO_NOT_SUPPORTED により失敗するsudo pnpm self-update などsudo なしで実行する
engineStrict が有効な場合、通常の dependencies 内に互換性のないエンジンがあると、optionalDependencies 配下のエントリであってもインストールが失敗するengineStrict を使用しているプロジェクト以前は警告だけだったインストールが失敗することを想定しておく
Linux の packageImportMethod: auto が reflink より先にハードリンクを試すLinux ユーザー通常は対応不要
グローバルの node、deno、bun がプロジェクトで固定されたバージョンに従うグローバルにランタイムをインストールしているマシン固定されたバージョンが使われることを想定しておく

ワークスペース設定のチェックが重要な理由は、v12.0.0 のリリースノートで説明されています。pnpm 11 では、minimumReleaseAge のような設定をタイプミスしても、pnpm は何の通知もなくそのキーを無視していました。そのため、本来設定されるはずのルールが一度も適用されていなかったのです。

# pnpm-workspace.yaml
packages:
  - "apps/*"
minimumReleseAge:   # typo: pnpm 12 reports this key

互換性ガイドには、全部で 8 つの相違点が記載されています。そのうち 6 つは結果を変えるもので、残り 2 つは pnpm 11 で受け付けていたコマンドライン構文(--resolution-only と --frozen-lockfile false)を拒否するものです。上の表はこれらをすべて網羅しているわけではありません。たとえば、pnpm add で yarn などのパッケージマネージャー名を指定した場合の pnpm 12 の扱いは含めていません。また、ワークスペースのキーと sudo に関する行は、ガイドではなくリリースノートに基づいています。アップグレードの前に、ガイド全体に目を通してください。

pnpm 12 にアップグレードすべきか

基本的にはアップグレードすべきです。作業も特に問題なく完了するでしょう。それこそが設計目標でした。pnpm 11.10 以降を使っている場合は、次のコマンドを実行します。

pnpm self-update

packageManager で pnpm のバージョンを固定しているプロジェクトでは、self-update は pnpm をグローバルにインストールするのではなく、そのフィールドのバージョンを更新するだけです。次にコマンドを実行したときに、pnpm が新しいバージョンを取得します。CI でも同じバージョンが使われるよう、この変更をコミットしてください。

{
  "packageManager": "pnpm@12.8.1"
}

12.x の最新リリースを使用してください。pnpm deploy や特定のリンカーモードなど、あまり一般的でない機能に依存している場合は、まずブランチ上でアップグレードを試してください。まだ npm を使っているチームは、両方の変更を一度に行う前に、npm から pnpm への移行が妥当かどうかを確認するとよいでしょう。

まとめ

pnpm 12 は、コマンド、ロックファイル形式、ストアモデルなど日常的に使う部分を維持したまま、その裏側のエンジンを再構築しました。このリライトが成果を上げたのは、JavaScript 版の性能を 2 つのコストが制約していたためです。1 つは起動のたびに発生する Node.js の起動時間、もう 1 つは単一のランタイムに集中するファイル I/O です。アップグレードの前に、CI 設定に --resolution-only と --frozen-lockfile false が含まれていないか検索してください。また、SSH 経由でアクセスしているプライベートな Git 依存関係がないか確認し、最初に再解決されたロックファイルは単独でコミットして差分をレビューできるようにしておきましょう。

よくある質問

pnpm 12 の実行に Node.js のインストールは必要ですか?

いいえ。インストール後の pnpm 12 はネイティブプログラムとして動作するため、Node.js は不要です。スタンドアロンのインストールスクリプトも、インストール時を含めて Node.js を必要としません。唯一の例外は npm 経由で pnpm 12 をインストールする場合で、このときはインストーラーの実行に Node.js 22.13 以降が必要です。使用しているプラットフォーム向けにビルド済みの pnpm 12 バイナリがない場合、pnpm のドキュメントでは JavaScript 版の pnpm 11 を使うことが推奨されています。

pnpm 12 でプライベートな Git 依存関係に引き続き SSH を使うにはどうすればよいですか?

pnpm 12 は、GitHub、GitLab、Bitbucket の依存関係を各ホストの HTTPS URL 経由で取得します。SSH を引き続き使用するには、Git の URL 書き換えを設定してください。たとえば git config --global url.'git@github.com:'.insteadOf https://github.com/ のように設定します。pnpm は内部で git を呼び出しているため、このルールは pnpm が実行するすべての Git コマンドに適用されます。pnpm が認識しないホストや、認証情報を含む URL は、SSH を含めて記述したとおりにそのまま使われます。

pnpm 12 が認識できない pnpm-workspace.yaml の設定でコマンドが失敗するのはなぜですか?

失敗するのは、プロジェクトが pnpm のバージョンを固定しており、実行中の pnpm がその条件に一致する場合だけです。この場合、pnpm は不明なキーを誤りとみなし、ERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGS で停止します。条件に一致する固定がなければ、警告が表示されるだけでコマンドはそのまま続行されます。キーがタイプミスと思われる場合、pnpm は本来意図していたと考えられる設定名を提示します。誤ったキーを含むファイルでも pnpm config コマンドは実行できるため、これを使って問題を特定し修正できます。

sudo で実行すると失敗する pnpm 12 のコマンドはどれですか?

sudo 下では、pnpm setup、pnpm self-update、および pnpm add --global のようにグローバルインストールを変更するすべてのコマンドが ERR_PNPM_SUDO_NOT_SUPPORTED で停止します。以前のバージョンでは、代わりに root のホームディレクトリへ通知なく書き込んでいました。グローバルパッケージと設定は自分のホームディレクトリに保存されるため、これらのコマンドに root 権限は必要ありません。pnpm bin --global のような読み取り専用のコマンドは、sudo 下でも引き続き実行できます。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.