💭
yarn to pnpm
やりたかったこと
いくつかの内部パッケージを含むリポジトリ(いわゆるモノレポ)で利用しているパッケージマネージャーを yarn v1
から pnpm
への移行
狙い
- 各内部パッケージの依存ライブラリの厳格化
-
yarn
はデフォルトでは各内部パッケージで共通している依存ライブラリはルートのnode_modules
に hoist される- 結果として直接的な依存関係を
package.json
に記述しなくても他の内部パッケージ内で require や import ができてしまう(phantom dependency
)
- 結果として直接的な依存関係を
-
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
が実行されない
- これは
puppeteer
のインストール時に起きた問題-
puppeteer
当有のものではなくインストール時にpreinstall
やpostinstall
でなにかしらのスクリプトを動かすようになっているものならすべて発生するはず
-
-
puppeteer
はインストール後にブラウザをダウンロードするスクリプトが実行される -
pnpm
ではデフォルトではpreinstall
,postinstall
が実行されないようになっているので設定を追加する必要がある- 今回は
pnpm-workspace.yaml
に追加 - https://pnpm.io/ja/next/package_json#pnpmonlybuiltdependencies
- 今回は
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 から内部パッケージ B を利用して
- 試した解決方法
- パッケージ A だけ依存ライブラリを hoist する設定を入れる
-
esbuild
でバンドルする際にmetafile: true
を設定し、メタファイルを吐き出すようにして内容を元に依存関係を追加する- パッケージ A が直接依存していないものを追加する必要があるので不本意だが、動くことを優先して一旦これで解決
- 他に良い方法が絶対ありそうだが見つけられなかったのでだれか教えてほしい
- パッケージ A が直接依存していないものを追加する必要があるので不本意だが、動くことを優先して一旦これで解決
esbuild
の externalPlugin
を利用していて、そっちが原因の可能性も十分に考えられるので pnpm
との相性的な問題かも
最後に
- 微妙な解決方法の部分があるが、ローカルで動作するところまで確認
- 次はデプロイ周り
- 全社的には
pnpm
を利用するプロジェクトが多くなってきているので他と足並みを揃えることができた - 今後も負債になりそうなところが隙を見つけて解消していく
Discussion