主力製品の Vue 3 & Vuetify 3 へのマイグレーション全記録
ロンラン株式会社 CEO 兼 CTO の武部です。
Vue 2 & Vuetify 2 で開発していた当社の主力製品のひとつを Vue 3 & Vuetify 3 へと移行しました。
記念すべき CLOSE 済み PR
今現在は、最新の Vuetify 3.0.4 で安定動作しており、開発も Vue 3 & Vuetify 3 を前提としたワークフローへ移行し、快適です。
コード変化量は +21,471 行、-14,585 行。修正ファイル数は 183 ファイルで収まりました。
リードタイムとしては 2 週間程度、作業時間としては、うーん...合計 60 時間ぐらいかな?
フロントエンドエンジニア二名で対応しましたが、95% ぐらいはひとり(私)が対応しています。
事前に、移行を見据えた情報キャッチアップやできる範囲での移行は進めていたため、割とスピーディに対応できた方だと思います。
このページの情報があれば、みなさんのプロジェクトではもっとスムーズにマイグレーションが進むはず!参考にしていただければと思います。
事前準備
製品マイルストーン計画面での調整
当たり前のことではあるのですが、マイグレーション作業中に並行で新機能を追加するのを止めましょう。
『Vue 2, Vuetify 2 側に新機能を追加してそれを都度 Vue 3, Vuetify 3 側へも反映』...この作業のためには 2 チーム必要ですし、移行作業の効率はただただ下がります。フロントエンドエンジニア全員を Vue 3 移行に全振りする方がよいと私は思います。
PdM やマネジメントとは事前に「マイグレーション期間中の新機能開発は原則できない」と認識をすり合わせたうえで、製品戦略上いつ移行するのがベストタイミングかを決めましょう。
となると「本当に移行しきれるか」が重要です。たとえば v-data-table
に依存している機能があり、代替手段も無い場合は、時期尚早です。ぐっとこらえましょう。
逆に、そういった要素が見つからない場合は、可能な限り早く移行してはいかがでしょうか。 Vue 2 & Vuetify 2 版に機能を追加したら、その分、将来の移行コストが増えるだけですので...
「代替手段」とは
「代替手段も無い場合は」と書いた点について。Vuetify 3 が提供する新しい API やスタイルでは「リリースができなさそうな場合」は、別のコンポーネントを模索する必要が出てくると思います。
怪しいものがある場合は、事前に Awesome Vue サイトなどでアタリを付けておくことをお勧めします。
もう一点。「リリースができなさそうな場合」と書きましたが、この移行ではリリースブロッカーの見極めも重要になってきます。
多少スタイルが変化したり、やや操作性が劣化したとて、ユーザー体験やビジネスに影響がないなら、リリースブロッカーにすべきではないでしょう。
「Vuetify 2 の完全再現」を目指すのは正直難しいと思いますし、長期化します。そもそもこだわる必要もないと思います。このあたりは、UI デザイナーと事前に手を握り合いながら話しておきましょう。
対応を済ませておくべき改修対応
すでに私の過去記事で取り上げていますが、次のこれらは Vue 3 化以前に対応できるものなので、先に終わらせておくことを強くお勧めします。今回の本丸の移行作業をなるべく短時間で終わらせるためです。
- Vue 2.7, Composition API
- vue-router/composables(useRouter, useRoute 利用)
- vue-i18n-bridge(useI18n 利用)
- beforeRouteEnter, afterRouteEnter を使っている場合、なくなることへの対策
- ESLint エラー解消
自分の場合は上記に加えて、Options API スタイルをほぼ廃止して setup script スタイルへの移行も済ませておきました。
ESLint エラー解消について
事前のチェックリストに記載していますが、ESLint エラーが出ていないことを最終チェックしておきます。この後の修正中に出てくる ELint エラーには散々助けてもらうことになります。Vuetify 3 向けの修正も、完全ではないにしろ ESLint が代行してくれる部分が多くあります。
その確認・対応中に既知の未解決のエラーがあると、解消すべきエラーなのか、放置してよい既知のエラーなのかの区別が付かなくなってしまいます。
ですので、事前に ESLint エラーは極力解消しておきましょう。
担当分担の戦略
ローカルで Dev サーバが起動できるようになるまでは、また、一律一括置換で沢山のファイルを触るうちは、ひとりで対応を進める方がよいです。中途半端に分業すると Conflict 地獄が待っています...!
ブランチ戦略
この改修作業自体、小さなバッチ方式 の意識で進めることをお勧めします。
ブランチについては、main
から今回の対応用の branch を切り、GitHub 設定で保護設定しておきます。
ここでは仮に next
という名の branch とします。
main
<- next
の DRAFT PR も作成しておきます。
以後は、git switch -c 作業用branch next
で作業用ブランチを作成し、これを次々と next
にマージしながら作業を進めてゆきます。
CI/CD 設定は適宜調整・設定してください。エラーがうるさいかもしれませんが、その場合は一次的に無効化しておくとよいでしょう。
採用すべき Material Design のバージョンを決める
Vuetify 3 は Blueprints
と呼ばれる新機能によって、Material Design のバージョンを選択できるようになりました。スゴイ!!
しかも、ななななんともう Material Design 3 に対応しているのです。これは驚きです。
事前に UI デザイナーと相談し、どの Material Design バージョンを採用するかを決めておきましょう。
当社では、主要コンポーネントのスタイルが元々 m3 に近かったこともあり、m3 を採用することにしました。
さあ、改修対応をはじめよう
っとその前に!この移行作業中に「修正しない方がよい」ことをひとつ。
Vue 2 までは .vue ファイル内の <template>
の直下に複数のタグを書けなかったため、適当に wrap するための div などを書いていました。次のようなコード例です。
<template>
<div>
...
</div>
</template>
「やったこれをやめられるぜ」と修正したくなりますが、こらえましょう。ここを修正すると、ブロック全体が差分となり、修正行差分を確認できなくなってしまいます。wrap しているブロックの削除対応は、すべて終わった後に別作業でちまちまとリファクタリングしましょう。
それと、Vuetify 3 公式のガイドどおりに、空の Vuetify 3 プロジェクトをどこか適当な場所に作成しておくことをお勧めします。主要な設定ファイル(package.json, tsconfig.json, vite.config.ts, index.html など)がどのように変わるか把握できるためです。
主要パッケージのアップデート
もちろんプロジェクトによって違いますので、参考情報としてください。当社製品の場合は下記でした。
Vue 2 & Vuetify 2 | Vue 3 & Vuetify 3 |
---|---|
vue-i18n-bridge | 削除 |
@vitejs/plugin-vue2 | @vitejs/plugin-vue |
vue-template-compiler | 削除 |
@vue/test-utils@1 | @vue/test-utils@2 |
vue-i18n@8 | vue-i18n@9 |
vue-router@3 | vue-router@4 |
vuex@3 | vuex@4 |
vue-navigation-bar@4 | vue-navigation-bar@5 |
vue2-masonry-wall | vue-masonry-wall |
これらは、package.json を元に整理しておいた上で、パッケージマネージャを使ってえいやっと変えてしまいましょう。
主要なファイルの修正
main.ts 修正
待ちに待ったこの日。main.ts(main.js)内のコードを createApp
, app.use()
に書き換えてゆきます。
Vue 3 公式ドキュメントの次のページを参考に対応します。私から詳解することはないかなと思いますので割愛します。
vue-i18n, vue-router, pinia, vuex など、ご自身が利用しているライブラリについても、公式ドキュメントを読みながら createXxx へ書き換えてゆきます。コードの作りによっては、多少リファクタリングしながらになる場合もあるでしょう。
vue-i18n 修正
vue-i18n に関するその他の修正です。
-
vue-i18n-bridge
はvue-i18n
に一括置換 -
dateTimeFormats
->datetimeFormats
に修正必要です
vue-router 修正
vue-router に関するその他の修正です。
-
vue-router/composables
はvue-router
に一括置換
history モードと scrollBehavior を修正します。 createWebHistory()
を import して使う方式に変わっています。次の公式ドキュメントも参考にしてください。
参考コードです。
Before
mode: 'history',
scrollBehavior: (to, from, savedPosition) => {
if (to.hash) return { selector: to.hash }
if (savedPosition) return savedPosition
return { x: 0, y: 0 }
},
routes: [
...
]
After
history: createWebHistory(),
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth',
}
}
if (savedPosition) return savedPosition
return { top: 0 }
},
routes: [
...
]
router 設定において、修正が必要な場所はわずかです。
-
Before:
path: '/*'
-
After:
path: '/:pathMatch(.*)*'
vite.config.ts 修正
@vitejs/plugin-vue2
を @vitejs/plugin-vue
に変更します。
Before
import vue from "@vitejs/plugin-vue2";
After
import vue from "@vitejs/plugin-vue";
src/vue-shims.d.ts 修正
まるっと書き換えます。
Before
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
After
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent;
export default component;
}
その他一括置換
beforeDestroy
は beforeUnmount
に一括置換できます。
vue-demi コンテキストスイッチ切り替え
vue-demi を使っているライブラリのコンテキストスイッチを切り替えます。
npx vue-demi-switch 3
Vuetify 3 向けの修正、引き続きひとりで。
Vuetify 3 向けの修正に入ってゆきます。
次の Vuetify 3 ドキュメントのアップグレードガイドをざっくり読みます。どうせこのあと、ESLint エラーが出たり、実行時エラーが出たらこのページを何度も参照することになるので、ざっくりで大丈夫です。
API ページへのリンクがすべて 404 Not Found で臆しますが、次のページにヒントを記しましたので参考にしてください。
【すごく重要】 Vuetify 3 向けの ESLint 設定
eslint-plugin-vuetify
、これは移行対応の最重要ツール(設定)です。これがなくては今回の Vuetify 3 アップグレードは成功しないです。
README のとおりに導入しておきます。
yarn add eslint-plugin-vuetify -D
eslinrc.json の extends
に設定を追加します。
extends: [
"plugin:vuetify/base"
]
勇気があれば、ESLint で fix 掛けてしまってもよいですね。私は普段 fix を使っておらず、ちょっと不安だったので、ESLint エラーが出ているファイル(ほぼ全部ですが...)を開いて、確認 → 保存 → 確認の作業を繰り返しました。
これによって、アップグレードガイドに書かれていた変更点の多くが自動修正されます。たとえば次のようなものです(多すぎて挙げきれない!)。
-
primary--text
=>text-primary
への変換 -
small
,large
といったコンポーネントプロパティのsize="small"
,size="large"
修正
いろいろ一括置換
ESLint では解決できない仕様変更のうち、文字列の一括置換や、正規表現での一括変換で対応できる箇所をやっつけます。
文字列の一括置換
-
$vuetify.breakpoint.
は$vuetify.display.
に一括置換 ※正規表現置換しないように -
v-slide-item
はv-slide-group-item
に一括置換
正規表現での一括置換
-
.sync
の修正を正規表現の一括置換で。:([^.]+).sync=
をv-model:$1=
へ置換します。 - スタイルの darken, lighten のスタイル仕様変更もこれでいけました →
[\s](darken|lighten)-
=>-$1-
- これは少し乱暴なので、手修正が必要な箇所もあるでしょう
確認しながらの修正
-
@input
を@update:Variable
(Variable
は適切な変数) に修正してゆきます。※ESLint がスタイルだけは修正してくれていますが、Variable
は自身でコーディングします
Vuetify 3 向け対応、分担作業開始
このあたりで、Dev サーバ起動してみましょう。スムーズに起動しなかった場合は、ターミナルに出ているエラー関連情報を拾いながら地道に修正を続けます。
「見た目はまだまだ壊れているし、いろいろ JS console にエラー出てるけど Dev サーバが起動した!」これがリード担当者のゴールです 🎉
ここからは、分担して作業してゆきます。
Material Design 設定
先に記載したとおり、Blueprints 機能を利用して Material Design のバージョンを設定します。この例は md3 の例です。
import { createVuetify } from 'vuetify'
import { md3 } from 'vuetify/blueprints'
export default createVuetify({
blueprint: md3,
...
})
テーマ設定
createVuetify()
時のテーマ設定は Vuetify 3 と Vuetify 2 で仕様変化がありますので、見比べながら修正します。
アイコンフォントの設定
アイコンフォントについては、当社製品においてはインパクトがありました。
当社製品では Line Awesome と Material Design Icons を併用しており、前者を多く使いながら、ないものは後者を使っています。
ですが、Vuetify 3 ではこういったアイコンフォントの併用が暗黙的にできなくなっています。
最終的には、上記ページに掛かれている情報を参考に、カスタムアイコンセットを用意することで解決できました。
Vuetify 2 の内部 CSS を hack していたような箇所
Vuetify 2 の内部 CSS を hack していたような箇所があれば、表示が崩れているケースが多い... というかほとんど全滅なので、頑張ってください。
当社の場合は Vuetify 3 化を見越してなるべく局所的に使っていたので、修正影響は少なかったり、比較的容易な対応で解決できました。
ヤバすぎる修正量の場合は、UI デザイナーに、デザインシステム自体を修正できないか相談してみては...。
そうはいかないという場合は、あらためてビルトインの SASS variables 活用ができないかや、その部分だけ Vuetify 以外のコンポーネントを使う、あるいは自作するなども視野に検討する必要があるでしょう。
コンポーネントやディレクティブごとの個別対応
v-bottom-sheet はまだない
v-bottom-sheet
まだありません。経緯こちらです。
v3.2.0 で来そうですね。待ってられないので、他のコンポーネントで代替しました。当社製品の場合は、ユースケースごとに次のような設定の v-navigation-drawer
と v-dialog
でまかないました。
<v-navigation-drawer
v-model="searchDrawer"
left
temporary
floating
width="100%"
></v-navigation-drawer>
<v-dialog
v-model="bottomSheet"
fullscreen
persistent
no-click-animation
transition="dialog-bottom-transition"
></v-dialog>
v-list-item-avatar, v-list-item-content がない
廃止かもしれない?適当に div などに置き換えたり、コード調整をしました。
このタイミングで m3 Material Design サイトのコンポーネント仕様 を確認し、本来どういった余白が必要なのかも確認しながら修正しましたが、そもそもいらない要素だったのかもしれないです。
directive の v-intersect の引数の仕様が変化
Intersection Observer を使っている場合、引数が変化しているので修正が必要です。この手のものは、動かしてみて console エラーなどで拾ってゆきます。
onIntersect (entries, observer, isIntersecting) {}
(
isIntersecting: boolean,
entries: IntersectionObserverEntry[],
observer: IntersectionObserver,
)
その他、手ごわいコンポーネント
v-overlay
は、今ちょっと壊れているような気もします。結構トライしたのですが無駄な時間に感じてきたので、CSS コードを直接書くなどで回避しました。
v-autocomplete
はがらっと変わりましたかね...。少し時間が掛かりました。
v-slider
も、仕様変更への追従でいろいろとコード修正が必要でした。
テスト(UT)
もともと vitest でコンポーネントのテストを書いていましたが、router や i18n の注入などをローラー対応修正し、比較的スムーズに終わりました。
動作確認
小さなバッチ方式でどんどん改修してゆきながら、あわせて動作も確認してゆきますが、注意点があります。
動作速度が遅い場合はビルドしてみる
何気ない画面遷移(ルーティング)や、 v-carousel
中の v-img
はやたら表示が遅いなどの症状に焦りますが、Dev サーバモードだと動作が遅く、ビルドした後は軽快ということがあります。
ですので、動作速度が気になる場合は、ビルド版でチェックしましょう。
当社ではこれがリリースブロッカーだったので、しばらくコンポーネントのコードを追ったり、解析して時間を無駄にしてしまいました 👻
事後対応の判断
vue-meta
を @vueuse/head
へ移行するとか、一部シャドウが出ていないスタイル上の問題などは、リリース後でも許容できたので後回しにして、マージとリリースを優先しました。
このあたりの「見切り」ができないと、ズルズルと移行作業が続くため、判断大事です。
おわりに
... なんか振り返ると、半分ぐらいは eslint-plugin-vuetify
が修正してくれたような気もします笑
冒頭に書いたとおりで、今は本当に快適です。ついに本格的に Vue 3 で楽しく開発できるようになりました。
Vuetify 3 についても、実は心の中には品質懸念を持っていたのですが(爆)、かなり品質高く仕上げてきたなと感心しています。開発チームの努力を労いたいです 👏
ちなみに当社は、複数の SPA 製品を開発しており、それらをワークスペース(具体的には yarn workspaces)で管理しています。
今回はそのうちのひとつ、優先度が高く、移行が可能なものだけを対象にしました。
ですので、まだ Vue 2 & Vuetify 2 のままの製品もあります。
このあたり、Vue2, Vuetify 2 と Vue 3, Vuetify 3 をひとつのワークスペースで管理し、動作させることも問題なくできているので、必要な方は是非トライしてみてください。
Discussion