Vue3・Vuetify3 アップグレードへの壮絶な道のりを振り返る
はじめに
こんにちは。株式会社コミュニティオで主にフロントエンドの開発を担当している上田です。コミュニティオ社ではまだ 2 年目のペーペーですが、フロントエンドエンジニアに転身して以来 Vue.js と Vuetify.js を書き続け、早いもので 5 年を迎えました。
この記事では、弊社の主力サービスの一つである TeamSticker のフロントエンドで使用していたウェブ開発フレームワーク Vue2 および UI フレームワーク Vuetify2 を、その後継バージョンである Vue3 および Vuetify3 にアップグレードするまでの壮絶な道のりを振り返ります。
Vue2 は 2023 年末に EOL(End Of Life)を迎えました。また、EOL に先駆けて、Vue3 向けに開発された機能のいくつかをバックポートした Vue2.7 が 2022 年 7 月にリリースされていました。
アップグレードを終えてみて
本当に大変でした。しかし、それを差し引いてもアップグレードの功績は有り余るほどに大きいと感じています。
弊社ではアジャイル開発を採用しています。アジャイル開発では、ユーザーにとってインパクトの大きい価値を短期間で提供することが求められます。しかし、Vue2 から Vue3 へのアップグレードそのものがサービスのユーザーに与える価値はあまり大きくありません。スプリント計画の中にアップグレード作業を盛り込むためには、プロダクトオーナーに対して Vue3 へのアップグレードに伴うユーザー価値を示す必要がありました。
弊社のテックチームでは、プロダクトオーナーに対し『Vue3 にアップグレードすることで、Vue3 向けの Storybook が導入できるようになる。Storybook を導入すれば UI に関するフィードバックが容易になり、サービスの品質と開発効率が向上する』という立てつけで説明を行いました。しかし、単なる説得材料としてのそれ以上に Storybook の導入には価値がありました。これについては別の記事で触れられればと思っています。
これからアップグレードに挑む方へ
- アップグレードの下準備が重要
- グローバルな CSS や依存ライブラリのコンポーネントの内部実装に依存したコードを取り除く
- Vue3 に非対応のライブラリを Vue3 に対応したライブラリで代替する
- Vue3 で廃止された機能をあらかじめ取り除く
- AI を活用する
- Vuetify2 から Vuetify3 へのアップグレードに伴う仕様変更に注意
- eslint-plugin-vuetify を使う
- ESLint プラグインだけでは対処しきれない部分もそれなりにある
- 無理に Composition API 化する必要はない
TeamSticker の抱えていた課題
TeamSticker の歴史は意外と古く、その原型となる コミュニティオ は社内仮想通貨を構築するサービスでした。コミュニティオは 2017 年に開発が開始され、フロントエンドは PlayFramework を使って実装されていました。その後、紆余曲折あり、2019 年にはデジタルサンクスカードを基軸とする社内コミュニケーション活性化のためのサービス TeamSticker として生まれ変わることになります。このとき、ウェブ開発フレームワークとして採用されたのが Vue2 でした。また、その 1 年後には UI フレームワークとして Vuetify2 が導入されました。
このように複雑な経緯を経て生まれた TeamSticker の実装は、有り体に言えば キメラ的 な様相を呈していました。Vuetify 導入以前から導入していた Bootstrap と、何重にもパッチされた内製グローバル CSS が Vuetify のスタイルと競合し、まさにカオスなコードになっていたのです。また、Vue の開発経験が豊富な熟達したエンジニアがいなかったこともあり、Vue のアンチパターンが蔓延していました。
- グローバル CSS、Bootstrap、Vuetify のスタイルが競合
- Vuetify の sass 変数やテーマ機能を使わないスタイルの上書き
- Vue Class Component の利用
-
v-model
を使わず、prop
で渡されたオブジェクトを直接変更 - 不適切なライフサイクルフックの利用や、根拠のないタイマーによる非同期処理
-
slot
や CSS の機能で代替可能な UI の切り替えやカスタマイズロジックのv-if
による実装 etc.
これらの他にも、TeamSticker は大小様々な課題を抱えていました。それにも関わらずこれらの課題が放置されていたのには、Vue から React への移行が検討されていたという背景もありました。
Vue に育てられた男―上田の入社
そんな状態のまま 2024 年に突入し、ついに Vue2 は EOL を迎えました。そして、それと時を同じくして、私がコミュニティオに入社しました。
私は、前職で未経験からいきなりフロントエンドエンジニアに転身してから 3 年間、ほぼ独学で Vue2・Vuetify2 の修行 を積んできました。生まれたばかりの小鳥が、初めて動くものを見るとそれを親だと思う ように、私が初めて触れて動かすことのできた Vue というフレームワークは、私にとってまさに 親のような存在 なのです。
私が入社した直後の Vue2 は、テックメンバーの ある種の諦観を帯びた鈍い視線 に見守られ、静かに息を引き取ろうとしていました。親とも言える Vue2 の、今にも消えてしまいそうな命の灯を前に、私は深い悲しみに包まれました。そして同時に、どうしてこんなにもお葬式なムードが漂っているのか、不思議でなりませんでした。そうしてしばらく TeamSticker のコードをメンテナンスしていくうち、私は先に述べたような課題の数々を目の当たりにし、お悔やみを申し上げる他ない現状 を理解しました。
当時テックチームでは、マイクロフロントエンドフレームワーク qiankun を用いて、Vue2 のコンポーネントを段階的に React へと置き換える ための技術調査を行っていました。しかし TeamSticker のコンポーネントの構成は非常に複雑で、調査は難航し、置き換えの見通しが立たない状態が続いていました。このまま突き進むか、道を引き返し別の道を探すか、まさに瀬戸際に立たされていたのです。
私は、ここぞとばかりに声を上げました。 『Vue の真価はこんなものじゃない! Vue、まだやれます!!』 と。
それから数か月間、私は Vue の真価を伝えるべく奮闘を始めるのですが、それはまた別のお話。奮闘の末、テックチームは Vue2 から Vue3 へのアップグレード路線に踏み切る ことを決めました。
そして 2024 年 10 月ごろ、テックチーム全員でアップグレードの計画を立て、それに従って作業を進めていくことになりました。
アップグレードの計画を立てる
Vue3 へのアップグレードは、基本的に 移行ガイド に従って作業を進めていくことになります。
Vue3 は、Vue2 からの破壊的な仕様変更を多数含みます。そのため、Vue2 向けの外部ライブラリの中には Vue3 に対応していないものも多く、そういったライブラリは Vue3 へのアップグレード後に別のライブラリに置き換えるか、自前で実装し直す必要があります。
また、Vue2 で公式に提供されていた Vue Class Component は、Vue3 で廃止されました。TeamSticker は全てのコンポーネントを Vue Class Component で実装していたため、Options API か Composition API のいずれかに置き換える必要があります。Class Component の構造は比較的 Options API の構造に近く、ほぼ機械的に置き換えが可能で、コード生成 AI のサポートも受け易いというメリットもあります。そのため、まずは Options API に置き換えます。
Vuetify2 から Vuetify3 へのアップグレードは、ネット上に散見される先人たちの報告から Vue2 から Vue3 へのアップグレードよりも遥かに大きな破壊的変更を伴うということが分かっていました。Vuetify3 の公式から提供されている eslint-vuetify-plugin
を用いることで、ある程度はアップグレードを自動化することはできるのですが、Vuetify2(Vue2)の想定する実装 になっていることが前提となります。
自動でアップグレードできない部分については 型のエラーを見ながら修正 していくことになりますが、型に表現されない CSS やコンポーネントの内部実装の仕様変更 については、実際の画面を見ながら修正 していくことになります。見た目の意図せぬ変更を自動検出するビジュアルリグレッションテストが無い ので、本来ならば アップグレードの前にテストの仕組みを導入しておくべき ですが、全てのコンポーネントに対してテストを導入するコストは非常に高いため移行する前の導入は見送りました。その代わり、移行後に Storybook を導入し、UI コンポーネントごとに Story を作りながら段階的にエラーを修正していく ことにしました。
このように、アップグレードにはそれなりの工数が掛かります。アップグレード計画当初、コミュニティオでは(検証期間を除き)移行に 3 ヶ月ほど掛かるものと見積もっていました。Vue3 への移行期間中は、ウェブフロントエンドのリリースができないため、その間は Vue2 のまま開発を継続するブランチ を作成し、両ブランチを同時並行で開発 していくことにしました。
アップグレードを実施する
まず、Vue3 に非対応のライブラリをリストアップし、それぞれについて既存の別のライブラリに置き換えるか、自前の実装に置き換えるかを決めていきました。一つ具体例を挙げると、TeamSticker ではモーダルウィンドウを構築するライブラリ vue-js-modal
を多用していましたが、このライブラリは Vue3 に対応していないため置き換えが必要でした。Vuetify2・Vuetify3 に VDialog
というダイアログコンポーネントがあり、これに乗り換えたのですが、これが後に大きな火種となります…。
次に、Vue Class Component を Options API に置き換えていきました。ほぼすべてのコンポーネントと Vuex のストアが Vue Class Component で実装されており、置き換え対象のファイルは 200 以上ありましたが、Github Copilot のサポートを受けながらほぼ脳死でひたすら物量をこなしていくような作業になります。
これまでの作業で Vue2 のうちにできる下準備が完了したので、いよいよ Vue3 ブランチを作成し Vue3 にアップグレードしていきます。アップグレードは移行ガイドに従いつつ、eslint-vuetify-plugin
を用いて可能な限り自動でアップグレードを行います。この時点では、当然ビルドは通りません。エラーに次ぐエラー。コンソールは、まるで C++ のテンプレートのコンパイルエラーが起きているかのような有様です。
ここからは、まずこれらのエラーを一つずつ潰し、アプリ全体をビルドして「画が見られる状態」まで持っていく作業になります。画が見られる状態になったら、アップグレードの前後で動作や見た目が変わってしまった箇所を見つけて修正していきます。ビルドエラーが解消された直後は目を塞ぎたくなるような状況になっていますが、まずは一旦心を落ち着かせます。
ここで Storybook の出番です。まず汎用のコンポーネントを中心に Stories を作り、Storybook の画面上で動作や見た目について Story ごとに検証し、不具合箇所を修正していきます。依存ライブラリの仕様変更がある箇所の他、CSS の deep セレクタを用いた実装箇所も影響を受けている可能性があるので、慎重な検証が必要です。Storybook による検証と修正を繰り返し、ある程度アプリが動く状態になったら、アプリ上からも動作や見た目を検証し、不具合を修正していきます。単体テストと結合テストを並行するようなイメージです。
このような流れでアプリを多方面から叩いていくことで、徐々に Vue3 版の TeamSticker は安定に向かってはいくのですが、Vuetify2 から Vuetify3 への凶悪な仕様変更の影響 がなかなか解消せず、相対的に目立つようになっていきます。例えば先に述べた VDialog
は、Vuetify2 と Vuetify3 で同じ名前のコンポーネントでありながら、ダイアログのサイズや表示位置のオフセット、開閉トランジションの挙動など、根本的な作りが大きく異なっており、Vuetify2 の実装と同じ振る舞いをさせるためにダイアログ以外のレイヤーも修正する必要があることが分かってきました。今振り返ると、ダイアログコンポーネントの乗り換えを Vue3 へのアップグレード後にやっていたら、アプリ全体の不具合の収束はもう少し早かったかもしれません。
また、アップグレードに想定以上の時間を要してしまったことで、Vue2 と Vue3 ブランチの並行開発のコスト が顕在化していきます。コードの差分が大きくなり、Vue2 ブランチから Vue3 ブランチへのマージの回数が増え、その際に eslint-vuetify-plugin
が自動的に行ったマイグレーションの考慮漏れによる不具合が新たに埋め込まれてしまっていました。
ついに、アップグレード完了
紆余曲折を経て、長期戦となった Vue3 へのアップグレードは、最終的に約 5 ヶ月を掛けてようやく完了 し、2025 年 3 月 18 日に無事リリースを迎えました。5 ヶ月もの間、みっちり叩かれまくった TeamSticker は、心なしか以前よりも すっきりした様子 に見えました。Vue3 へと進化を遂げ、新たな可能性を手にした TeamSticker は、これからもお客様に価値あるサービスを提供し続けていくため、さらなる進化を続けていきます。
この記事が、これから Vue3 へのアップグレードに挑む方々の参考になれば幸いです。
Discussion