一休.com・フロントエンドエンジニアの宇都宮です。
JavaScriptを使ったWeb開発では、様々なライブラリを使います。開発の活発なライブラリであれば、毎週のようにバージョンアップが行われます。ライブラリのバージョン更新は、それを行ったからといって価値に直結するわけではありません。しかし、以下のような理由から、一定の頻度での定期更新が必要です。
- バージョンアップに追従しないと、古いバージョンにロックインされる
- 差分が大きいバージョンアップはリスクが高い
- ライブラリに脆弱性が見つかった際は速やかにバージョンアップが必要
本記事では、JavaScriptライブラリ管理の標準的ツールであるnpmと、GitHub AppのRenovateを使用した、ライブラリを定期的に更新する仕組みの作り方について解説します。
npmによるパッケージ管理
npmは、JavaScriptライブラリの管理ツールです。
npmでは、使用するライブラリの名前とバージョンをpackage.jsonというファイルで管理します。
以下のように、dependencies(本番環境で使用するライブラリ)と、devDependencies(開発環境でのみ使用するライブラリ)を分け、それぞれのライブラリ名と、バージョン番号を指定します。
{
"dependencies": {
"vue": "2.5.16"
},
"devDependencies": {
"vue-loader": "14.2.2"
}
}
npmパッケージのバージョン番号は原則としてsemverという仕様に基づいており、x.y.zのxがメジャー、yがマイナー、zがパッチバージョンを意味します。メジャーバージョンアップは後方互換性のない変更、マイナーは後方互換性のある新機能追加、パッチは後方互換性のあるバグフィックスです。
依存ライブラリのバージョンには、幅を持たせることが可能です。たとえば「^2.5.16」は、「2.5.16以上のバージョンで、3.0未満」という意味になります。この指定方法はライブラリ向きです。他からライブラリとして参照されることのないアプリケーションでは、バージョン番号を固定しても良いでしょう。
実は、package.jsonだけでは、インストールされるライブラリのバージョンが毎回同じになることは保証されません。vue 2.5.16をインストールしたとしても、vueが依存しているライブラリのバージョンまで、全て同じになるとは限らないからです。
そのため、全ての依存ライブラリのバージョンを記録したファイルを別途用意する必要があります。npm 5以上では、npm installの実行時にpackage-lock.jsonというファイルが生成されます。このファイルには、全ての依存ライブラリのバージョンが記録されます。package.jsonとpackage-lock.jsonが存在する状態でnpm installを実行すれば、正確に同じバージョンのライブラリがインストールされます。
これはnpmコマンドの代替であるyarnでも同様で、yarn.lockに全ての依存ライブラリのバージョンが記録されています。
npmパッケージの更新手順
更新の必要なnpmパッケージを探すには、npm outdatedまたはyarn outdatedコマンドを使用します。
yarn outdatedは、semverに基づいてメジャー/マイナー/パッチの分類を行う等、よりリッチな機能を備えています。個人的にはyarn outdatedを好んで使っています。
バージョンアップの必要なライブラリを見つけたら、yarn upgrade vue 等で、ライブラリのバージョンを更新します。あとは動作確認して、リリースするだけです。
簡単そうに書きましたが、実際のところ、npmパッケージの更新は面倒です。バージョンアップしても問題ないか確認するには、CHANGELOGの確認が欠かせません。
一休の宿泊予約サービス開発チームでは、週に1回程度の頻度で、以下のようなプルリクエストを作成して、npmパッケージの定期更新を行っていました。

この作業は地味に面倒で、毎週1時間程度の時間をアップデートに割いていました。そこで、パッケージ更新作業の省力化を図るための手段をいくつか検討した結果、Renovateの導入を決めました。
Renovateとは
Renovateは、GitHub上で動作するアプリ(bot)です。Renovateを導入したリポジトリでは、以下のようなプルリクエストが自動的に作成されます。

↑のスクリーンショットは、yarnのリポジトリから取得しています。Renovateは、yarnをはじめとした様々なオープンソースプロジェクトや、Uber等の企業まで、幅広く利用されています。
publicリポジトリでは無料、organization + privateリポジトリでは$15/month~で使えます。更新の必要なライブラリの確認 => CHANGELOGまとめ => プルリク作成という定型作業を代わりに行ってくれるので、十分に元が取れる価格だと思います。
また、Renovate本体はOSSであり、自前でホスティングすることも可能です。
Renovateを導入する
RenovateはGitHub Marketplaceから導入できます。Marketplaceからrenovateを探したら、まずはプランを選びましょう。ここでは、私が個人で開発しているオープンソースプロジェクトにRenovateを導入するため、「Open Source」を選んでいます。

次の画面では、有料プランの場合、支払い情報の入力が必要になります。会社のGitHubリポジトリに導入する際は、個人アカウントの請求情報を入力しないよう注意しましょう。

最後に、インストールするリポジトリを選択します。ここでは、「ryo-utsunomiya/amazon-block」を選択しています。

1時間ほど待つと、以下のようなOnboading Pull Requestが作成されます。

このプルリクでは、renovate.jsonというRenovateの設定ファイルをリポジトリに追加します。デフォルトでは以下のような動作をします。
- メジャーバージョンアップは別々のプルリクに分割する
- パッチ・マイナーアップデートを区別しない
- アップデート用ブランチの名前には renovate/ プレフィックスをつける
- マージは自動では行わない(人間が手動でマージする)
- package.json が更新された場合にのみlockファイルを更新する
- プルリクの作成は2時間毎に1回を上限とする
- Renovateが作成したプルリクで、マージ/クローズされていないものの数が20を超えないようにする
このプルリクエストをマージすると、Renovate Botが動き始めます。
Pin Dependencies
パッケージのバージョンが固定されていない場合、以下のようなプルリクエストが作成されます。

ここでは、各パッケージのバージョン番号を固定(Pin)します。Pin Dependenciesプルリクをマージすると、パッケージの更新用のプルリク作成が始まります。
なお、依存ライブラリのバージョンをに幅をもたせたい場合は、以下のようにrenovate.jsonを変更すればOKです。
{
"extends": [
"config:base",
":preserveSemverRanges"
]
}
パッケージ更新の運用
Renovateによって、プルリクを作成するところまでは自動化されました。残りの作業は、安全を期して人間が行うようにしています。
具体的な手順は以下の通りです。毎週月曜の当番制にしています。
- 毎週月曜に、担当者がパッケージ更新の有無を確認する
- 更新があれば、パッケージ更新用ブランチを手元にpullして動作確認する
- 問題なければ、プルリクをマージする
マイナー/パッチアップデートはすぐに適用しています。一方、メジャーアップデートはソースコードの改修が必要なことが多いので、担当者をアサインして、一定の時間を確保してアップデートしています。
導入の効果
Renovateによる自動化によって、プルリク作成までの手順が標準化されたため、誰でもパッケージ更新作業が行えるようになったのが大きいです(従来は、有識者が気づいたときに更新するという体制でした)。
副次的な効果として、どのようなライブラリを使用しているか棚卸しできる、ライブラリの導入時に運用コストまで考慮できる、といったものもあります。
今後の課題
更新時の動作確認に属人性があるため、バグのある状態でリリースしてしまう危険性があります。
- 自動テストの拡充
- 動作確認手順の標準化
などによって、バグのある状態でのリリースを防ぐ対策を強化していきたいと考えています。
おまけ:マイナー/パッチアップデートをまとめる
デフォルト設定の場合、各パッケージ毎に更新プルリクが作成されます。しかし、週1回程度の更新頻度だと、マイナー/パッチアップデートは複数存在する状態になります。そこで、宿泊予約サービス開発チームでは、以下のようなrenovate.jsonの設定を行って、マイナー/パッチアップデートを1つのプルリクエストにまとめています。
{
"extends": [
"config:base"
],
"minor": {
"groupName": "all dependencies"
}
}
↑のrenovate.jsonを使用すると、以下のようなプルリクエストが作成されます。
