🚀

Yarn v1からpnpmに移行したらinstallがけっこう早くなったので成果報告する

2025/01/29に公開

はじめに

この記事について

こんにちは、zomysan ( https://x.com/zomysan ) です。本日はソーシャルPLUS社のフロントエンドチームで取り組んだ、 Yarn v1 から pnpm への移行の成果について紹介します。

従来から使用していたパッケージマネージャーである Yarn v1 には、installに時間がかかるなど、いくつかの課題がありました。そのため、Yarn v1からpnpmへ移行を行いました。結論として、3つの改善につながりました 💪

  1. CI / ローカルともにパッケージのインストールが早くなり、開発効率が向上 🚀
  2. パッケージの管理が厳格になったことで、意図しない依存関係が発生しなくなった 🪨
  3. パッケージインストールのキャッシュの仕組みが改善され、メンテナンスしやすくなった ✨

この記事では、これらの改善内容について1つずつ詳しくご紹介します。また、それらに交えてpnpmの良さについても紹介していきます。

この記事の対象

  • pnpm の何が良いのか知りたい方
  • Yarn v1 から pnpm に移行するか迷っている方
  • 改善事例が好きな方

この記事で触れないこと

  • 移行の具体的な手順、内容
  • モノレポの詳細な構成
  • Yarn v1, pnpm以外(Yarn v2移以降、npm など)のパッケージマネージャ

改善事項

それではさっそく、何が改善されたのか1つ1つ見ていきましょう。

パッケージのインストールが早くなった

問題

シンプルに yarn install にかかる時間が長くて困っていました。これまでフロントエンドのリポジトリでは、yarn install に3分程度かかっていました。CIでもローカル環境でも、どちらでもかなりの時間を要していました(具体的なデータは後述します)。

結果

pnpm を導入した結果、CircleCI・ローカル環境ともにインストール時間が大幅に改善しました🚀

環境 対象 Yarn v1 pnpm
CircleCI installジョブ 平均3分 平均2分
ローカル環境 モノレポ全体の初期install 310s 96s
ローカル環境 小さいプロジェクトの初期install 25s 6.5s
ローカル環境 モノレポへのパッケージ追加 18s 12s

それぞれの項目について詳細な結果を紹介します。

CircleCI

CircleCI Insights で統計情報を確認したところ、Yarn v1 を使っていた30日間の平均は3分強かかっていました。それに対し、pnpm 移行後は 2分少々と大幅に早くなりました 🎉

Yarn v1 時代の平均

pnpm 導入後の平均

3分 → 2分だとあまり効果が大きくないように見えますが、これは以下の3つの合計時間だからです。

  1. キャッシュ復元
  2. インストール(ここが1〜3分→1分前後に短縮された)
  3. キャッシュ保存・後続ジョブのための永続化

3についても効率化できそうなのでおいおい検討していきたいです。詳しくは記事の最後に紹介します。

ローカル環境で検証1: モノレポ全体のインストール

CIだけでなく、ローカルでのインストールも大幅に早くなりました。

プロジェクト全体を clone してきて install する作業はそれほど頻繁に行うわけではありませんが、それでも早いに越したことはないです💪

条件

  • pnpm 移行直前のコミットと、最新の main ブランチで比較
    • 厳密には依存するパッケージなどは異なりますが、どちらかというと pnpm 側のほうがパッケージの依存関係の記述が増えています
  • どちらも clone したての状態
  • pnpm のローカルキャッシュも pnpm store prune で消去済み

結果

Yarn v1: 310.0 s

pnpm: 95.8s 🚀

ローカル環境で検証2: モノレポ外の小さめのプロジェクト

巨大なプロジェクトの比較を行なったので、小さめのプロジェクトでどうだったかも紹介します。まだモノレポの管理下にないプロジェクトも存在するので、こちらでも試してみたところ、大幅に早くなりました。条件は「ローカル環境で検証1」と同じです。

結果

Yarn v1: 25.15s

pnpm: 6.5s 🚀

ローカル環境で検証3: パッケージの追加

パッケージの追加も2/3ほどの時間で完了します。パッケージの追加は普段の開発時にちょくちょく行う作業なので嬉しいですね。

Yarn v1: yarn add axios / 18.36s

pnpm: pnpm add axios / 12.5s

解説: pnpm はなぜ早いのか

依存関係の抽出・フェッチ処理・依存関係のリンクをパッケージごとに行うから早い

pnpm では、パッケージごとに依存関係の抽出・フェッチ処理・依存関係のリンクを行います。
従来の Yarn v1 のように、すべての依存関係を抽出してからすべてをフェッチし、最後にすべてをリンクするという手順を踏むより、当然早くなります。

Yarn v1など、従来型の処理

pnpmの処理

画像引用元: https://pnpm.io/ja/motivation

重複して同じパッケージをフェッチしないから早い

シンボリックリンクを使うことでファイルの実体を1か所にまとめ、重複したフェッチ処理を行わないため、速度が上がります。シンボリックリンクを使ったパッケージ管理方法については、「pnpm のパッケージ管理方法」で詳細に説明します。

詳しく知る

詳細に知りたい方は pnpm 公式 をごらんください!

その他、pnpm がなぜ早いのか?という話についてはいろいろな良い記事がありました。興味がある方はぜひごらんください。

パッケージへの明示的な依存関係がなくても import できてしまっていた

課題

モノレポ内部には十数のプロジェクトがあり、それぞれ必要なパッケージのリストが含まれる package.json を持っています。本来であれば、個別のプロジェクトに必要なパッケージはすべて package.json に書かれているべきですが、そうなっていませんでした。

Yarn v1 の仕様により、package.json から記述が漏れていても、該当のパッケージに他のプロジェクトからの依存があったり、依存しているパッケージからの依存があったりすると、記載が漏れていても動いてしまうという問題がありました。

結果

依存対象となっているのにもかかわらず、package.json の dependencies, devDependencies に書かれていないようなパッケージが多々ありました。pnpmを導入したことで、そういった依存関係の記載漏れを修正できました。将来的に突然アプリケーションのビルドが壊れる事故が起きる可能性も減ったはずです 🙌

解説: Yarn v1、pnpm それぞれの node_modules

以下のような依存関係のパッケージを例に説明します。Product App がメインのプロジェクト、Direct Deps が Product App が直接依存するパッケージです。Direct Deps からは Indirect Deps というパッケージに依存しています。

このような場合、Product App から Direct Deps は import できてもかまいませんが、Indirect Deps は import できてほしくありません。Product App から Indirect Deps への依存関係はないからです。

フラット構造はなぜ問題なのか?

Yarn v1では、すべてのパッケージをフラットに node_modules 以下に展開します。

前述したように、フラット構造の node_modules では、パッケージ自体の依存関係に指定していないにもかかわらず、他パッケージを通した間接的な依存があるとビルドや型チェックが通ってしまいます。

これは明示的にバージョンを指定しないままパッケージを使っている状態、さらに言うと他パッケージの依存関係に乗っかっている状態なので、以下のような問題が起きえます。

  • バージョンが管理できていない
    • 指定していないので意図しないバージョンを使うことになり、バグの原因になる
    • ある時点まで動いていたとしても、Direct Deps にあたるパッケージのアップデートにより Indirect Deps も知らない間にバージョンが上がり、壊れる可能性がある
  • 急に依存関係が存在しなくなるおそれがある
    • 「バージョンが管理できない」と似た話
    • 上図における Direct Deps が内部的に Indirect Deps を使わなくなると、当然 node_modules から消えるのでビルドが壊れる
  • Direct Deps が不要になり依存から外したら Indirect Deps への依存も消えるのでビルドが壊れる

pnpm は依存関係を明示しないと import できない

pnpm の管理下では、 node_modules がパッケージごとに階層構造になっています。

Direct Deps から Indirect Deps に依存しているとしても、Product App として依存しているのが Direct Deps だけなので、Indirect Deps は参照できません。

pnpm のパッケージ管理方法

こちらは余談ですが、pnpmのパッケージ管理方法について少し詳しく紹介します。

直前の「pnpm は依存関係を明示的にしないといけない仕組みになっている」の図を見ていると、 Product App から依存し、かつ Direct Deps からも依存する場合は Indirect Deps が2重にインストールされて無駄ではないか?という疑問が出てくるかもしれませんが、その心配はありません :+1:

pnpmでは、各パッケージのソースコードなどの実体は .pnpm というディレクトリに格納されます。階層構造の node_modules の内容は、この .pnpm の中身へのシンボリックリンクになっています。これによって、ディスク容量を節約しています。

より詳しく知りたい方は以下の記事をどうぞ!

独自で実装している node_modules をキャッシュする機構を排除できた

課題

Yarn v1 を利用している場合、node Orb の install-package command がルートの node_modules しかキャッシュしてくれないので、CIにキャッシュの機構を独自に用意していました。

その設定にはすべてのプロジェクトの node_modules をリストアップする必要があり、面倒ですし誤りのもとにもなる状態でした。

設定のイメージ
...
- dirs:
    - apps/message-manager/node_modules
    - apps/embedded-app/node_modules
    - ...(以下すべてのアプリケーション・パッケージの node_modules が続く)

結果

pnpm に移行したことで、 CircleCI が用意している orbs の機構を使用してキャッシュができるようになりました。

40行前後あった独自の設定もゴッソリなくなりました。リポジトリ内プロジェクトの追加削除にともなって node_modules のリストを手動で修正する必要もなくなり、リポジトリのメンテナンス性がよくなりました 🎉

まとめ

Yarn v1 から pnpm に移行することで、以下の3つの改善が行われました 🎉

  1. CI / ローカルともにパッケージのインストールが早くなり、開発効率が向上 🚀
  2. パッケージの管理が厳格になったことで、意図しない依存関係が発生しなくなった 🪨
  3. パッケージインストールのキャッシュの仕組みが改善され、メンテナンスしやすくなった ✨

ほかにもいろいろな課題があるので解決に取り組み、よりよい開発体験を目指していきます 💪

今後解決したい課題

workspace への persist が遅い

以下は現在のCircleCIワークフローの一部抜粋です(一部の例外ジョブを除くほとんどのジョブがinstallジョブに依存しています)。installジョブの内訳を見ると、実際のインストール処理が1分、後続ジョブにインストールした内容などを引き継ぐための保存処理に1分かかっています。

これはpnpm移行以前から存在する問題ですが、pnpmに移行してからは workspace への persist が占める割合が大きくなってきたため、対応を検討したいです。

たとえば、installジョブを廃止して、それぞれのジョブでインストールする方法などが考えられます。ただ、その場合はインストールが8並列で動くため、余計なCreditを消費します。

現在は今回のpnpm移行による効率化でCIにかかる時間が短縮できたため、この問題の優先度は高くありません。時間ができたら、消費するCreditと短縮できる時間を比較して検討し、必要に応じて取り入れていきたいです。

SocialPLUS Tech Blog

Discussion