jsconf.eu 2019 に行ってきました。 特に npm や yarn の今後の話とそもそも Registry をどうしていくか、の話があったのでお知らせします。 そもそも Registry をどうしていくかについては次のエントリで話します。
tink: A Next Generation Package Manager
npm の次のコマンドラインツールである tink が紹介されていました。
presentation: github.com video:
そもそも npm の仕組み
- ローカル依存ファイルを読む (package.json, package-lock.json, shrinkwrap.json)
- 存在しないパッケージのメタデータをfetchする
- 木構造を計算して、実行する(npm v3 以降だとflattenする)
- 実際に存在しないパッケージをダウンロードする
- インストールスクリプトを実行する
今のどこがダメなのか
npm は tar ball
を fetch して、そこから gunzip
して、最後にできあがったファイルをnode_modules以下にコピーするという非常にピーキーな処理をしています。最初のfetchは network IO
に影響し、 gunzip
は CPU
に影響し、ファイルコピーは file IO
に影響します。
これを簡単にするためにキャッシュしたり、展開済みのファイルをハードリンクさせたりとチューニングをしています。 これ自身は必要な処理なのですが、実体のファイルとして作る関係上、どうしても node_modules は巨大になります。
tink sh
tink
は node
本体の代替として起動します。
$ node foo.js
で起動するのではなく、
$ tink sh foo.js
で起動します。これで何がやりたいかというと、 node_modules
を物理的なフォルダにするのではなく、仮想上のフォルダにすることを目指してます。
仮想上のフォルダになることのメリット
いくつか利点がありますが、まず実際にファイルをコピーする必要がない分、 File IO
への影響は緩和されます。
また、 tink
自身はローカル内にハッシュ値を基にしたキャッシュを作るので、ハッシュ値が一致したパッケージに関してはキャッシュが使われます。 Network IO
への影響も緩和されます。
また、最大のメリットとして、 tink sh
で実行した際にランタイムで依存モジュールを解決するため、 npm install
のコマンド実行が不要になります。
つまり、 git clone
や git pull
してから依存ファイルがなかったとしても tink sh <cmd>
で起動すれば実行時に依存モジュールを解決し、起動することができます。
この試みのことを zero install
と呼びます。
prepare && unwind
本番環境で Docker 等を使って毎回ゼロからイメージを作ってる場合はイメージ作成後のキャッシュがないため、毎回起動時にパッケージのダウンロードが開始されます。
これを解決するために事前にFetchしてくるコマンド(prepare)と事前に node_modules を作るコマンド(unwind)の2つが提供されています。
$ tink prepare # 事前にfetchしてくる $ tink unwind # node_modulesの実体を作る
load map
tink
は npm v8
で npm
コマンドに統合される予定とのことでした。
yarn v2: berry
presentation:
yarn v2
も tink
と同様の戦略です。zero install
を実現しています。 .pnp.js
というファイルを内部に作成し、そこに依存モジュールを解決できるようにしています。
開発ツールとしては tink
も yarn v2
も同じ戦略を取っていました。細かな機能の違いはあるのでいくつか紹介します。
yarn contraints
package.json と実行時の依存関係に矛盾があった場合に、警告した上でpackage.jsonを修正してくれるサブコマンドです。
一番多いユースケースは、実行時に依存してる package が package.json に書かれてなかったとか、 workspace と一緒に使った際にmonorepoのサブパッケージ間で異なるバージョンのライブラリに依存してた等ですね。
いわば、 package.json の linter みたいなものですね。
どうやって実現しているのか
tink
も yarn v2
も実態は node
のプロセスである以上、 node
側の標準モジュールでは node_modules
以下にあるファイルをロードしに行く必要があります。
これを解決するために、 tink
と yarn v2
では node
の標準APIの module
にパッチを当てています(!)
module
のファイルロードを行う箇所のメソッドを拡張し、 node_modules
がなくてもファイルをロードできるようにしています。
標準のモジュールロードからは逸脱しているため、今のところ使うのは危険ではあります。将来的に読み込み方が変わる可能性もありますし、標準の node の読み込み方を変更する可能性もあるので、まだ experimental
と言ったところでしょうか。
ただし、 electron
も同様の手法でモジュールを読み込めるように拡張しているので、そこまで変更がドラスティックに加えにくい箇所でもありますね。
実際に、 tink
の解説では、 electron
も同じことやってる(ので大丈夫)というニュアンスで発表していました。
まとめ
モジュール管理は zero install
時代になっていくと思います。事前の処理を不要にすることで開発効率を上げて、 git clone
や git pull
から何もしなくても起動できるようになっていくと思われます。ただ一方で実際に本番で zero install
にしてしまうと、起動してから実際に動作するまでのパフォーマンスは落ちる可能性もあるため、起動方法は変更になるかと思われます。
これらを解決するための本番環境用のコマンドとして、 tink prepare
や tink unwind
も公開されているので、それらを使って行くのではないかと思っています。
また一方で、 tink
も yarn v2
もほとんど同じことをしているので、どちらにも有意差があるようには見えず、状況は変わらないまま今後に突入していくのではないかと思われます。
これらを踏まえて、 Registry についての話を書きます。また次回!