こんにちは、フロントエンドエンジニアの小張です。Renovateを使ってフロントエンドのパッケージやライブラリのバージョンアップを改善したことについて紹介します。
PR TIMESではReactに関するコードを、monorepoとしてprtimes-frontendという1つのリポジトリで管理しています。
このリポジトリは作成されてから2年ほどしか経っておらず、使っているライブラリも比較的新しいため、今までバージョンアップの仕組みを特に整備していませんでした。
ただフロントエンドのライブラリはバージョンアップの頻度が多く、異なるライブラリ間でバージョンの依存関係があることもあり、将来のことを考えればライブラリのバージョンを更新する仕組みを作ることはほぼ必須でした。
また、monorepoであるためライブラリのバージョンを大きくあげようとした際の対応コストも大きく、最新との差が小さいうちに細かくバージョンアップしていきたかったことも理由の一つでした。
バージョンアップデートを検知する
バージョンアップの仕組みを作るに当たって、以下の2点を決める必要がありました。
- バージョンアップデートをどのように検知するか
- バージョンアップデートをどのようにリリースするか
Renovateを利用するとリポジトリのpackage.jsonやDockerfileを見て、ライブラリを新しいバージョンに上げるPull Requestを自動で立てることができるため、1についてはRenovateを使用することにしました。
Renovateのインストール方法については以下をご覧ください。
Dependabotとの比較(2024-02-16時点)
GitHubの提供するDependabotでも同じくバージョンアップのPull Requestを自動作成する事ができますが、いくつかの箇所で機能的に不足を感じたので採用を見送りました。具体的には以下の部分です。
- コンフリクト時にだけrebaseする設定ができない
- Renovateでは
“rebaseWhen”: conflictedで可能
- Renovateでは
- Pull Requestをdraftで作成できない
- Renovateでは
“draftPR”: trueで可能
- Renovateでは
^1.0.0などの形式を禁止し、1.0.0のようにバージョンを固定値に自動修正できない- Renovateでは:pinDevDependenciesで可能
react-queryを@tanstack/react-queryに修正するような、別のパッケージとして配布されるようになったライブラリを自動修正できない- RenovateではReplacement Presetsで可能
バージョンアップデートのリリース方法
上記の2については月1回のリファクタリングデーに合わせて、1ヶ月間にアップデートされたライブラリたちをまとめてバージョンアップすることにしました。
リファクタリングデーについて詳しくはこちらをご覧下さい。

背景としては、特にmajorアップデートを含む場合、リグレッションテストを行うべきQA範囲はとても広くなるため、このリグレッションテストをライブラリ毎に行うことは、QAのリソースを考えると非現実的だったことがあります。
実際に運用しているRenovateの設定
renovate.jsonの設定はこちらです。
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"group:allNonMajor", // minorとpatchアップデートを1つのPRにまとめる
"config:best-practices",
":disableRateLimiting" // 1時間単位のPR数と同時に存在可能なPR数を無制限にする
],
"enabledManagers": ["npm", "dockerfile", "github-actions"],
"baseBranches": ["release-renovate"], // release-renovateブランチがあるときにだけrenovateを起動する
"draftPR": true, // CIの実行時間を抑えるため、renovateがdraftPRを作成するようにする
"timezone": "Asia/Tokyo",
// デフォルトではtests配下のpackage.jsonが無視されてしまうため、デフォルトを上書き
"ignorePaths": [
"**/node_modules/**",
"**/bower_components/**",
"**/vendor/**",
"**/examples/**",
"**/__fixtures__/**"
],
// CIの実行時間を抑えるため、rebaseをconflict時のみにする
"rebaseWhen": "conflicted",
"github-actions": {
"fileMatch": [
"^(workflow-templates|\\.(?:github|gitea|forgejo)/workflows)/[^/]+\\.ya?ml$",
"(^|/)action\\.ya?ml$",
// デフォルトでは.github/actions配下は管理対象にならないため追加
"\\.github/actions/.+\\.ya?ml$"
]
},
"packageRules": [
{
"matchPackageNames": ["node"],
"matchManagers": ["dockerfile"],
// Node.jsは偶数バージョンのみアップデートする
"allowedVersions": "/^[0-9]*[02468]([.-].*)?$/"
},
{
// Playwrightのnpmパッケージとdocker imageのバージョンを揃えるため、PRをまとめる設定にする
"matchPackagePatterns": ["playwright"],
"groupName": "playwright"
},
{
// ViteとVitestのPRをまとめる
"matchPackagePatterns": ["vite"],
"groupName": "vite"
},
{
// majorアップデートにはタグをつける
"matchUpdateTypes": ["major"],
"labels": ["major"]
},
{
// minorアップデートにはタグをつける
"matchUpdateTypes": ["minor"],
"labels": ["minor"]
},
{
// patchアップデートにはタグをつける
"matchUpdateTypes": ["patch"],
"labels": ["patch"]
}
]
}設定で工夫している点をいくつか紹介します。
baseBranchesの設定
月1回のリファクタリングデーでmainブランチにリリースする前に、RenovateのPull RequestをまとめてQAできるブランチが必要です。
そこでrelease-renovate というブランチを起点に、RenovateがPull Requestを作成するようにbaseBranches を設定しています。
{
"baseBranches": ["release-renovate"],
}作成されたPull Requestは手動でrelease-renovateにマージしていきます。ブランチ運用は以下のようなイメージです。

Groupingの設定
デフォルトの設定でRenovateを動かしたところ、約70件のPull Requestが作成されたのですが、これらを全て手動でマージするのが大変なのと、これらに対してGitHub Actionsが回ることでbillable timeを消費してしまう問題がありました。

そこで以下のようにgroup:allNonMajor というpresetをextendsして、minorとpatchアップデートを1つのPull Requestにまとめる(= Groupingする)ようにしました。
ただconfig:best-practices より後にgroup:allNonMajor をextendsしてしまうと、デフォルトでGroupingされているNext.jsなどのminor・patchアップデートもまとめて1つのPull Requestに入ってしまい、extendsする順番を以下のように調整する必要がありました。
config:best-practices はgroup:nextjsMonorepo というNext.jsに関するアップデートをGroupingするruleを内包しています。 このruleがgroup:allNonMajor と競合する場合、手元で確認したところrenovate.json内で後にextendsした方のruleが優先されるようでした。
{
// OK
"extends": [
"group:allNonMajor", // minorとpatchアップデートを1つのPRにまとめる
"config:best-practices",
],
// NG(config:best-practicesに含まれるGrouping設定が上書きされてしまう)
"extends": [
"config:best-practices",
"group:allNonMajor", // minorとpathcアップデートを1つのPRにまとめる
]
}
またデフォルトのGrouping設定以外でGroupingしたい場合には、以下のようにして独自のGroupingを作ることができます。弊社ではそこまで複雑なルールは使ってないですが、matchPackagePatterns ではライブラリ名に正規表現でマッチさせることができるので、柔軟なGrouping設定が可能です。
{
{
// Playwrightのnpmパッケージとdocker imageのバージョンを揃えるため、PRをまとめる設定にする
"matchPackagePatterns": ["playwright"],
"groupName": "playwright"
},
{
// ViteとVitestのPRをまとめる
"matchPackagePatterns": ["vite"],
"groupName": "vite"
},
}CI実行時間の削減を目的とした設定
Groupingの設定の他にも、CI実行時間の削減を目的にRenovateの設定を調整しています。
{
"rebaseWhen": "conflicted", // CIの実行時間を抑えるため、rebaseをconflict時のみにする
"draftPR": true, // CIの実行時間を抑えるため、renovateがdraftPRを作成するようにする
}Renovateのデフォルト設定では、baseBranches で設定したブランチに1つでもPull Requestがマージされたら、Renovateが作成した他のPull Requestのブランチを全てrebaseして、変更に追従しようとします。
弊社のCIはrebaseされることによっても発火する設定になっており、かなりの実行時間を消費してしまうので、rebaseWhen を設定してRenovateのrebase回数を減らすようにしました。
さらに、弊社のCI実行はdraft PRを対象外とするような設定になっているので、”draftPR”: true として開発者が手動でdraftを解除しない限り、RenovateのPull RequestではCIが実行されないようにしました。
これらの設定を行ったことで1ヶ月間のRenovateによるGitHub Actions使用時間(billable time換算)が、約14000分削減できました。
| 設定前 | 設定後 | |
|---|---|---|
| 1ヶ月間のRenovateのPR上でのbillable time | 14459分 | 321分 |
ライブラリごとの個別設定
prtimes-frontendではNode.jsの最新LTS(偶数バージョン)を使用するようにしているので、Node.jsの奇数バージョンにアップデートするPull Requestはマージする必要がありません。
そこで以下のようにallowedVersions で許可するNode.jsのバージョンを正規表現で指定することで、偶数バージョンへのアップデートのみ、自動でPull Requestが作成されるように設定しました。
{
{
"matchPackageNames": ["node"],
"matchManagers": ["dockerfile"],
// Node.jsは偶数バージョンのみアップデートする
"allowedVersions": "/^[0-9]*[02468]([.-].*)?$/"
},
}設定ファイルのバリデーション
上記の設定を行ったrenovate.jsonが、正しい設定になっているかをバリデーションするrenovate-config-validator というCLIがRenovate公式から提供されています。
そこで、prtimes-frontendでは以下のようなGitHub Actionsのworkflowを用意して、renovate.jsonに変更があれば自動でバリデーションを行うようにしています。
name: Renovate Config Validator
on:
workflow_dispatch:
pull_request:
paths:
- 'renovate.json'
- '.github/workflows/renovate-config-validator-ci.yaml'
jobs:
renovate-config-validator:
timeout-minutes: 10
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: validate renovate.json
run: npx --yes --package renovate@latest -- renovate-config-validator --strict結果
Renovateを導入し、リファクタリングデーを使ったリリースフローを整備したことで、今では1ヶ月平均10個のライブラリをメジャーアップデートすることができるようになりました。
またライブラリの新しい機能がリリースされてすぐに使えるため、開発者体験も向上しました。例えば、TypeScript v4.9で導入されたsatisfiesですが、当時最新だったVite v3では使うことができませんでした。しかし、Viteのバージョンアップを最新に保っていたため、v4がリリースされてすぐにv4に上げることができ、satisfiesを使った開発ができるようになりました。
さらに最新バージョンを使って開発することが当たり前になったことで、バージョンアップできていないライブラリのバージョンを上げようとする意識が自然と高くなり、例えばエディターチームではTiptapのバージョンを上げるための取り組みが進められています。
We are hiring!
フロントエンドはもちろん、各種ポジションで採用を行っています!

