Node.js プロジェクトの依存パッケージ更新戦略

Node.js でアプリケーションを作成する際の依存パッケージのアップグレードについて考察します。
2020.04.13

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CX 事業本部の Tech Lead のお仕事紹介第 2 弾。英語版も書きました。

TL;DR

アプリケーション構築の際のアップグレード戦略は次の 3 点。

  1. dependencies / devDependencies のバージョン指定は固定しよう
  2. 一ヶ月ごとに依存パッケージのアップグレードを実施しよう
  3. 依存パッケージのアップグレードには npm outdated / yarn outdated を利用しよう

npm-check-updates ではなく npm outdated / yarn outdated を使うよう内容を変更しました。

Node.js プロジェクトの依存とは

package.json の dependenciesdevDependencies に記載されているものです。

dependencies はそのパッケージが動作する際に必要な依存パッケージを、 devDependencies はそのパッケージを開発する際に必要な依存パッケージを指定します。あるパッケージの依存となった場合、 devDependencies に指定したものはインストールされません。

依存パッケージのバージョン指定

dependenciesdevDependencies ではそれぞれ、特に指定せずに新しい依存パッケージを追加した場合、キャレット ^ U+005E が先頭に付与されます。次のような形です。

// snip

  "devDependencies": {
    "typescript": "^3.8.3",
  },

// snip

バージョンについては Semantic Versioning が採用されています。

先頭のキャレットは「一番左の数字が同じである現在の最新バージョンをインストールしなさい」という指定となります。具体的な意味合いは次の表を見るとわかりやすいでしょう。

バージョン指定 意味
^1.3.4 2.0.0 未満の最新バージョン
^0.3.2 0.4.0 未満の最新バージョン
^0.0.4 0.0.5 未満の最新バージョン
^2.1.4-beta.3 3.0.0 未満の最新バージョン
^0.0.3-beta.3 0.0.4 未満の最新バージョン

また、チルダ ~ U+007E を付与することも可能で、その場合は「マイナーバージョンが同じである現在の最新バージョンをインストールしなさい」という指定です。マイナーバージョンの指定がない場合、つまり ~1 などはメジャーバージョンが同じである最新バージョンを指します。

その他にもバージョン指定の方法はありますが、この 2 種類を知っていれば問題ありません。

バージョン固定の必要性

特に気にしない場合、インストールされるパッケージのバージョンに幅が出てきてしまうわけです。これではチームメンバー間で異なるバージョンがインストールされる可能性があり、メンバーによって挙動が異なってしまうかもしれません。

これを防ぐ仕組みとして、 npm では package-lock.json が、 yarn では yarn.lock が提供されています。

それぞれ、 npm install もしくは yarn 実行時の node_modules 以下の構造を固定するために生成されるもので、 Git 管理の対象としてチームメンバーで共有するものです。これによって、メンバー間で異なるバージョンの依存パッケージがインストールされることを防ぎます。

明示的なバージョン固定

package-lock.json や yarn.lock によって依存パッケージのバージョンは固定されますが、個人的に package.json に書くバージョンは固定すべきと考えています。

  • 依存パッケージの取捨選択について自覚的になれる
  • バージョンロックファイル再生成時の確認を切り分けできる

依存パッケージの取捨選択について自覚的になれる

少なくとも、プロジェクトが直接依存しているパッケージについてはどのような機能があるか、脆弱性はないかなどについて自覚的であるべきです。機能追加やバグの有無について把握し、取捨選択しなければプロジェクトがコントロール可能な範囲を超えてしまいます。

もちろん、検証用などその場で使い捨てるプロジェクトについては依存パッケージのバージョンはどんなものでも構わないのでその限りではありません。

バージョンロックファイル再生成時の確認を切り分けできる

後述しますが、この方法によって依存パッケージの依存パッケージを、一括で最新にアップグレード可能です。この際、直接依存するパッケージがアップグレードされることによって意図しない破壊的変更がもたらされると、なにが原因なのかわからなくなってしまいます。

明示的なバージョン固定の方法

すでに dependencies / devDependencies のバージョン指定に ^~ が指定されている場合は取り除きましょう。

新しくインストールする際にバージョン固定したい場合、次のオプションを使うとよいでしょう。

パッケージマネージャー オプション
npm --save-exact
yarn --exact

次のように使います。

npm install --save-exact react
// or
yarn add --exact react

バージョンを上げるタイミング

依存パッケージがアップグレードされた場合、一般的にその中身が洗練されているため、追従してそのバージョンを使用するようにすべきです。ここではアップグレードすべきタイミングについて議論します。

依存パッケージに脆弱性が発見されたとき

GitHub の Security alerts という機能がありますので、普段はこれを利用しましょう。ただし、コードの中身を GitHub に検査させることに同意しなければならないため、情報漏えいに気をつける場合は使用を控えましょう。

手動で脆弱性を探す方法として、 npm では npm audit / yarn audit が提供されています。手元でこれらのコマンドを打つと、脆弱性を持っている依存パッケージが表示されますので、適宜アップグレードしましょう。

1 ヶ月ごと

機能追加や処理内容の効率アップなどが見込めるため、依存パッケージのアップグレードを定期的に実施しましょう。昨今の npm パッケージの開発速度を鑑みて、約 1 ヶ月おきにアップグレードすると良いでしょう。あまりに頻繁ですと開発者の負担が大きくなりすぎてしまい、長続きしませんし、これより長いスパンだと破壊的な変更を同時に数多く考慮しなければならなくなり、アップグレードが大仕事になってしまいます。

チームメンバーと相談し、アップグレード週間などを設けるとよいでしょう。

バージョンを上げる方法

フレームワークが提供しているアップグレード方法

フレームワークは自身のアップグレード方法を提供している場合があります。

create-react-app の場合

各バージョンごとにアップグレード方法が紹介されています。

React Native の場合

専用のツールが提供されています。

現在のバージョンと、どのバージョンへアップグレードしたいかを指定することで作業内容が表示されます。

Vue.js の場合

こちらも同様にアップグレード方法のページが整備されています。

バージョンロックファイルの削除と再生成

定期的なアップグレード時にはまずこれを実施しましょう。

rm -f package-lock.json
npm i
// or
rm -f yarn.lock
yarn

依存パッケージが最新になります。 package.json の指定によりますが、後方互換性が保たれているはずのアップグレードであっても、各種テストが通ることの確認と手動での動作確認を実施しましょう。

後方互換性の維持は非常に難しいため、依存パッケージに意図せず破壊的変更が含まれているかもしれないからです。

依存アップデートのサポートツール

npm outdated および yarn outdated コマンドを使いましょう。 npm outdated の実行結果は次です。

$ npm outdated
Package         Current  Wanted  Latest  Location
dotenv-webpack    1.7.0   1.7.0   1.8.0  liff-app-template

yarn outdated の結果は色がついているので画像で紹介します。

それぞれのサブコマンドでバージョン指定をアップデートすることも可能です。ドキュメントを参照してください。

バージョン変更後は npm i もしくは yarn の実行を忘れないでください。

peerDependencies

基本的に依存パッケージは最新バージョンであることが望ましいですが、 peerDependencies を満たすようにしなければなりません。これは協働するパッケージのバージョンを指定するためのもので、プラグインなどが動作する本体ツールのバージョンを指定するために用いられます。

最新にアップグレードする際はプラグインと本体ツールを同時にアップグレードするようにしましょう。また、最新にアップグレードしたあとの npm i / yarn によって peerDependencies 違反が見つかった場合、バージョンを落としてください。

依存アップグレードツールの使用は控える

世の中には依存パッケージがアップグレードされた際にその変更を検知し、 Pull Request を生成してくれるものがありますが、こういったものの使用は控えましょう。どういったフェイズであっても依存パッケージをアップグレードすることによる動作確認は避けられず、そういったツールによって作成された Pull Request が邪魔になるからです。

あくまでアップグレードは自覚的に実施しましょう。

まとめ

このアップグレード戦略はあくまで流派のひとつとして捉えてください。直接の依存パッケージバージョンの固定など、かなり安全寄りに倒した運用です。

また、アプリケーションではなく、ライブラリーを作成する際はまた勝手が違うことにも気をつけてください。想定ユーザーがアプリケーションとは全く異なるためです。