maybe daily dev notes

私の開発日誌

npm install は package-lock.json を変更しないことがほとんどだった

昨日までずっと npm install の挙動について勘違いしていたので、改めて整理する。

なお、引数付きの npm install <package_name> コマンドではなく、 npm install 単体で打つときの話。

npm install の仕様

npm 5.4.2 以後 (つまり、現代ではほとんどの環境がこちら)

  1. はじめに、既存の package-lock.jsonpackage.json に齟齬がないか確認する。
  2. 齟齬がなければ、 package-lock.json のとおりにインストールする。この時、 npm ci とは異なり、既存の node_modules ディレクトリは削除されない。
  3. 齟齬がある場合、 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メンテナが書いたものが由来なので、それなりに信ぴょう性がある。なぜかドキュメントには明記されていないのでやや不安はあるが。

github.com

ただし、

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が立っていて、現状は不明のまま。

github.com

とはいえ、仕様がこうなっているということは、いずれはバグも修正されて仕様どおりの挙動になるのだろう。 このため、本番用のビルドなどクリティカルな用途以外では、仕様を信じて npm install を使っても良さそう。

npm 5.4.2 より前 (つまり、現代では無関係)

先のIssueを見るかぎり、npm install はまったく package-lock.json を無視していたらしい。 (そもそもnpm@4以前は package-lock.json すらなかった模様) このStackoverflowも参考になる:

stackoverflow.com

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 installpackage-lock.json が更新されるということは package.jsonpackage-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 を導入せずに済むな。