Zenn
💭

yarn to pnpm

2025/03/24に公開

やりたかったこと

いくつかの内部パッケージを含むリポジトリ(いわゆるモノレポ)で利用しているパッケージマネージャーを yarn v1 から pnpm への移行

狙い

  • 各内部パッケージの依存ライブラリの厳格化
    • yarn はデフォルトでは各内部パッケージで共通している依存ライブラリはルートの node_modules に hoist される
    • pnpm では一旦 node_modules/.pnpm にすべてインストールし、そこから各内部パッケージの package.json に記載されている依存関係に合わせてシンボリックリンクで配置する

ちなみによく言われるディスク容量の削減については yarn でも hoist することで重複は解決しているので大して変わらないと思う
今回移行しようとしているリポジトリで確認すると以下のような結果になった

  • yarn
% du -shc node_modules packages/*/node_modules
2.3G	node_modules
1.4M	packages/A/node_modules
952K	packages/B/node_modules
1.6M	packages/C/node_modules
868K	packages/D/node_modules
1.3M	packages/E/node_modules
 84K	packages/F/node_modules
1.0M	packages/G/node_modules
2.3G	total
  • pnpm
% du -shc node_modules packages/*/node_modules
2.6G	node_modules
4.0K	packages/A/node_modules
 24K	packages/B/node_modules
 32K	packages/C/node_modules
 24K	packages/D/node_modules
 40K	packages/E/node_modules
 16K	packages/F/node_modules
 16K	packages/G/node_modules
2.6G	total

実行速度は依存関係の解決方法がシンプルになったのでそりゃ速くなるわという感じ
そもそも他のパッケージマネージャーだとシンボリックリンクを利用するアプローチけっこうある気がするし、重複してるものを hoist するとか無駄に複雑化しすぎでは?と思ってちょっと調べたら https://github.com/pnpm/pnpm/issues/496 とのことで歴史的な問題だったらしい🙏

修正・追加が必要だったところ

import の宣言

yarn では内部パッケージも hoist してくれるので下記のように宣言してもビルドツールが解釈してくれる
deep import と呼ばれるものらしい

import Hoge from "@terass/mypackage/src/node/A"

pnpm では hoist されないので上記のようなものはビルド時にエラーになるので下記のように変更

import Hoge from "@terass/mypackage"

https://gist.github.com/daleyjem/0f38f561a4e91e58eba580889f38330f 不具合の原因にもなり得るので怒られるようになってよかったものだったりする

preinstall, postinstall が実行されない

onlyBuiltDependencies:
  - puppeteer

なにも設定していない状態でインストールを実行すると下記のような警告を出してくれるので従えば基本的に問題なさそう

╭ Warning ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                                                   │
│   Ignored build scripts: @firebase/util, @sentry/cli, @swc/core, canvas, es5-ext, esbuild, protobufjs, puppeteer, re2, sqlite3.   │
│   Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.                                          │
│                                                                                                                                   │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

内部パッケージを利用した際の依存関係

  • 起こったこと
    • パッケージ A から内部パッケージ B を利用して esbuild でバンドル
    • 実行時にパッケージ A は直接依存してないがパッケージ B が依存しているライブラリに対して Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'XXX' になる
  • 試した解決方法
    • パッケージ A だけ依存ライブラリを hoist する設定を入れる
    • esbuild でバンドルする際に metafile: true を設定し、メタファイルを吐き出すようにして内容を元に依存関係を追加する
      • パッケージ A が直接依存していないものを追加する必要があるので不本意だが、動くことを優先して一旦これで解決
        • 他に良い方法が絶対ありそうだが見つけられなかったのでだれか教えてほしい

esbuildexternalPlugin を利用していて、そっちが原因の可能性も十分に考えられるので pnpm との相性的な問題かも

最後に

  • 微妙な解決方法の部分があるが、ローカルで動作するところまで確認
    • 次はデプロイ周り
  • 全社的には pnpm を利用するプロジェクトが多くなってきているので他と足並みを揃えることができた
  • 今後も負債になりそうなところが隙を見つけて解消していく
Terass Tech Blog

Discussion

ログインするとコメントできます