人類はより高速にCIを回していくべきだと思っている りんご(@mstssk)です。
先日、 npm の v5.7がリリースされ npm ci
というサブコマンドが新たに追加されました。
The npm Blog — Introducing
npm ci
for faster, more reliable... http://blog.npmjs.org/post/171556855892/introducing-npm-ci-for-faster-more-reliable
CI/CDを開発プロセスに組み込んでいる場合により整合性があり高速なエクスペリエンスを提供する、と公式ブログでは紹介しています。
npm ci
は何をするのか
npm ci
を実行すると常に package-lock.json から依存関係をインストールします。
既に node_modules フォルダの中身があっても一旦削除します。
従来の npm install
コマンドを実行すると、 package.json と package-lock.json の両方を見て依存関係の解決と依存パッケージの node_modules へのインストールを行います。 package.json を解決して必要に応じてロックファイルである package-lock.json の更新もします。
一方で npm ci
は package.json の依存関係の解決を行わず、常に package-lock.json を見て依存パッケージをダウンロードし node_modules の洗い替えを行います。
しかし、 package.json を完全に無視するというわけではなく、 package-lock.json と依存バージョン指定が食い違っているとエラーにしてくれます。例えば、あるパッケージを v0.8.9
でインストールし package-lock.json まで作成済みの時に、そのパッケージを更新しようとして package.json だけ v0.9.0
に書き換えてしまった状態で npm ci
を実行するとエラーになります。
このように、依存関係の更新をせずに整合性チェックと依存パッケージのダウンロードのみを行うため npm install
より高速に動作し、CIで必要なことだけを行うのが npm ci
コマンドです。
npm ci
は銀の弾丸ではない
npm ci
が package.json と package-lock.json の整合性をチェックしてくれるのは嬉しいです。
一方で、依存パッケージのダウンロード・インストールの高速化については、爆速になるというほどではありません。
私が普段触っているWebサイト構築プロジェクトいくつかで時間を計測してみました。
npm install |
npm ci |
|
---|---|---|
プロジェクトA | 25.515s | 16.656s |
プロジェクトB | 29.761s | 20.676s |
プロジェクトC | 54.380s | 40.710s |
※いずれも node_modules が存在しない状態で計測。
どの場合でも早くなるのは10秒前後でした。この10秒が package.json を見て依存関係の解決を行う時間なのでしょう。
削減できなかった時間が何なのかというと、ログを見ている限りでは各依存パッケージの preinstall
/ postinstall
のスクリプトの実行時間のようです。
例えば node-sass パッケージは実行環境ごとのバイナリのインストールや動作チェックを行っているようですし、その他にもインストール時に node-gyp
を使ってネイティブモジュールをビルドしている、なんてものもありました。
思い切って node_modules をキャッシュすると早い
CIの速度を重視するなら npm ci
を使うのではなく、もっと愚直に node_modules をキャッシュしてしまうという手段もあります。
例えば、私は普段CircleCIの config.yml で次のように設定して、node_modules をキャッシュさせています。
version: 2
jobs:
build:
steps:
- checkout
- restore_cache:
keys:
- npm-cache-{{ checksum "package-lock.json" }}
- run: npm install
- save_cache:
key: npm-cache-{{ checksum "package-lock.json" }}
paths:
- ./node_modules
/* 後略 */
この方法によって、通常 npm install
に54秒かかっていたプロジェクトも、キャッシュのレストアと npm install
の時間あわせて15秒程度で済んでいます。
もちろん、最初の1回や package-lock.json を更新した際は時間がかかってしまいますが、多くの場合で大幅に速度改善になります。
ただし、この方法では npm ci
が提供する package.json と package-lock.json の整合性チェックが使えません。
npm ci
は node_modules があると一旦消して洗い替えしてしまうのでキャッシュと一緒に使用できません。
CIで何を担保するのかを考慮した上でキャッシュの使用を検討するとよいでしょう。
まとめ
npm ci
は既にCIでnpmを使っているプロジェクトへの強力なサポートとなります。
どうしても速度を改善したい場合は node_modules をキャッシュしてしまう方法もあります。
みなさんもプロジェクトにあわせてCIをメンテしていきましょう。