🔖

package.jsonでのバージョン指定は完全固定にしよう

2024/08/27に公開

バージョン指定ゆるくなってませんか?

標準の状態でnpm installなどで依存ライブラリを追加すると、package.jsonには

"package-name": "^5.4.3",

こんな形で登録されます。

これは「バージョン5.4.3を使う」の指定ではありません。

最初の^は「0でない最初の数値を固定する」の意味で、この場合は「5.4.3以上かつ6.0.0未満のバージョンを使う」になります。

パッケージマネージャーが参照するlockfile、例えばnpmであればpackage-lock.jsonに具体的な指定があればそのバージョンが使われるし、なければ条件に合致するその時点の最新版がlockfileに登録されます。

この挙動はsemver(semantic versioning)の考え方に基づいた挙動になっています。

https://github.com/npm/node-semver?tab=readme-ov-file#caret-ranges-123-025-004

semverの基本的な考え方として「メジャーバージョンが同じであれば後方互換性は保たれている」とされるため、こうした設定がデフォルトになっていますが現実はそう甘くはありません。

マイナーバージョンで機能を大きく変えるライブラリはたくさんあるし、パッチで挙動が変わる(意図するしないは別として)なんてのも日常茶飯事です。

そもそもソフトウェアというものは変更するためにバージョンを進めるものなので、そこに「変わらないこと」を期待するのに無理があるのです。

あなたがアプリケーション開発者であれば、開発するプロジェクトでのバージョン指定は

"package-name": "5.4.3",

のように、パッチ(3つめの数字)まで完全に決め打ちで固定した方が意図しない挙動を防げます。

package-lock.jsonがあればバージョンは固定できますが、このファイルは

  • 間接依存しているライブラリに脆弱性が見つかった
  • 複数のライブラリの依存関係を解決する際に複数バージョンが競合した

などの問題が起きた時に削除して作り直したくなることがあります。その際に意図せずバージョンが上がってしまうのは望ましくありません。

直接的に利用しているライブラリに脆弱性が見つかった場合などは、lockfileではなくpackage.jsonでのバージョン指定を問題ない状態に保ちましょう。

そもそもpackage.jsonでバージョンを指定しているのに、そこでの指定を不完全にして更に別のファイルに依存する必要はありません。

package-lock.jsonなどのlockfileは間接的に依存するライブラリのバージョンを管理するためのものと考えるのがいいでしょう。

バージョン固定で追加する方法

npmでもyarnでもpnpmでも、インストール時に-Eを付けるとバージョンを固定できます。

npm i -E package-name

みたいにして追加すればその時点の最新版で固定されます。-E--save-exactの省略形です。

人類は毎回これを忘れずに付けられるほど高性能ではないので、設定ファイルに書いておくといいでしょう。

利用しているパッケージマネージャーがnpmかpnpmであれば~/.npmrc

save-exact=true

と書いておけばデフォルトでバージョン固定してくれます。プロジェクトで合意できるならば、プロジェクトのルートに.npmrcを置いてもいいかもしれません。

yarnを使っている場合はv1なら~/.npmrcの設定を見てくれるので同じで構いません。~/.yarnrc

save-prefix ""

と書いても実現できます。

yarn v2以降では~/.yarnrc.yml

defaultSemverRangePrefix: ""

みたいに書くようです(私はv2以降は使ってない)。

ライブラリを公開する場合

先に「あなたがアプリケーション開発者であれば」と書きましたが、逆にライブラリを外部に公開する場合はバージョンを完全に固定するのは望ましくありません。

仮にあなたのライブラリが依存する別のパッケージの指定したバージョンに、後から脆弱性が見つかった場合には

  1. package.jsonで指定するバージョンを問題のないものに変える
  2. その変更をリリースする
  3. 利用者にその修正版を使ってもらう

という対応が必要になります。これには手間がかかるし、時間がかかる分だけリスクも増します。

semverを使って範囲指定することで、利用者はlockfileの修正だけで対応できる可能性が高くなります。

アプリケーションであれば自分で使用するバージョンを更新すれば済む話なので固定すればいいのですが、ライブラリを提供する場合は利用者のことを考えて設定しましょう。

Discussion