昨日までずっと npm install
の挙動について勘違いしていたので、改めて整理する。
なお、引数付きの npm install <package_name>
コマンドではなく、 npm install
単体で打つときの話。
npm install の仕様
npm 5.4.2 以後 (つまり、現代ではほとんどの環境がこちら)
- はじめに、既存の
package-lock.json
とpackage.json
に齟齬がないか確認する。 - 齟齬がなければ、
package-lock.json
のとおりにインストールする。この時、npm ci
とは異なり、既存のnode_modules
ディレクトリは削除されない。 - 齟齬がある場合、
package.json
に記載のバージョンに適合するように、package-lock.json
が変更される。
ここで齟齬と言っているのは、package.json
に書かれたバージョン指定が実際にインストールされているバージョンと整合しないことを指す。
例えば "web3": "^1.7.3"
と package.json
に書かれているのに対して、 package-lock.json
では "web3": "1.7.2"
がインストールされている場合など。
このため、一度 npm install
して package-lock.json
を生成したら、 package.json
を変更しない限りは何度 npm install
の結果はdeterministicであることが保証されている。
この仕様は、GitHubのIssueコメント欄にnpmメンテナが書いたものが由来なので、それなりに信ぴょう性がある。なぜかドキュメントには明記されていないのでやや不安はあるが。
ただし、
If you do run into a case where npm@^5.4.2 mutates a package-lock.json that was otherwise compatible with the paired package.json please open a new issue. This sort of thing would constitute a high priority bug.
というのが曲者で、 要は package-lock.json
が (本来変更されないはずの場合でも) 変更されてしまうようなバグはありえるということ。
実際、2021/3にこのようなIssueが立っていて、現状は不明のまま。
とはいえ、仕様がこうなっているということは、いずれはバグも修正されて仕様どおりの挙動になるのだろう。
このため、本番用のビルドなどクリティカルな用途以外では、仕様を信じて npm install
を使っても良さそう。
npm 5.4.2 より前 (つまり、現代では無関係)
先のIssueを見るかぎり、npm install
はまったく package-lock.json
を無視していたらしい。 (そもそもnpm@4以前は package-lock.json
すらなかった模様) このStackoverflowも参考になる:
5.4.2 がリリースされたのが 2017/9 頃 らしいので、その以前から npm を使っていた人は誤った認識を持っているかもしれない。
自分の勘違い
但し書き: この節に書いてあることはすべて勘違いで誤っているので注意。
npm install
は常にpackage-lock.json
を無視してパッケージをインストールするコマンドである。npm update
との違いはよくわからないpackage-lock.json
のとおりにインストールするコマンドはnpm ci
のみだnpm ci
は実行のたびにnode_modules
ディレクトリを削除して全てのパッケージを取得し直すので、なんて非効率なことか、このIssue を未だに放置している現状は異常としか言いようがないpackage-lock.json
のとおりにかつ既存のnode_modulesディレクトリを残しながらパッケージをインストールするには、yarn install --frozen-lockfile
しかない- この点だけで
npm
を捨ててyarn
に移行するまである!
上記の仕様理解が正しいとすれば、これらは完全に勘違いだったことになる。 古い誤った知識をアップデートできずただただ文句を言うだけだった自分が恥ずかしい。懺悔します。
言い訳
勘違いし続けたのは、ちまたの言説で「CIサーバーでは必ず npm ci
を使え」と言われすぎているからだと思う。
実際は上記の仕様なのであれば、 npm install
を使っても依存関係のインストールは deterministic になるので、実用上の問題があるとは思えない。
むしろ、 npm install
で package-lock.json
が更新されるということは package.json
と package-lock.json
に不整合がある状態ということである。
この状態を早めに検知するために npm install
を使うほうが良いのではとすら思う。
ただし一点不安なのは、npm のバグにより 意図せず package-lock.json
が更新されてしまう場合。
実際自分もこれまでそのようなことがあった気がするのである。
これが起きるとするなら、npm install
を使う CI/CD が生成するアーティファクトを信用できなくなる。
それなら、リスク0の npm ci
を使うという選択は妥当だろう。
「CIサーバーでは必ず npm ci
を使え」と唱える人は、背景をそこまで説明してほしかったなという気持ちになる。
まとめ
まとまらないが、自分は npm install の仕様を信じて、開発時は基本 npm install
一本で行きたいと思う。
AWS CDKでアプリをデプロイしていると、 npm ci
の挙動がデプロイ速度の律速になることがまれにあるため。
これで yarn
を導入せずに済むな。