こんにちは、CTOの小林です。年々歳をとるにつれて、1年がどんどん短く感じるようになってきました。12月なんて体感で3日くらいしかないような気がします。
kickflowではこの1年は通常の機能開発と並行して、Nuxt 2から3へのバージョンアップを行っていました。本格的な移行作業は今年の10月ぐらい始めたのですが、移行のための調査や事前準備はNuxt 3がリリースされた2022年の11月から開始しているので、1年以上かかった長期プロジェクトとなります。今日はNuxt 3へのバージョンアップにkickflowがどのように立ち向かっていったかをご紹介します。
- 前提:kickflowフロントエンドの構成
- Nuxt Bridgeを使うか、直接Nuxt 3に上げるか
- 影響範囲の洗い出し
- Nuxt 2の状態で対応したこと
- Nuxt 3に上げるときに対応したこと
- 移行してみて
- 最後に
前提:kickflowフロントエンドの構成
kickflowはバックエンドがRailsのAPIモード(JSON APIのサーバー)、フロントエンドがNuxtのシングルページアプリケーション(SPA)という構成です。SPAモードなのでNode.jsのサーバーは立てておらず、ビルドしたアセットをRailsのpublicディレクトリに配置することでRailsの静的ファイル配信の仕組みでアセットを配信しています。なお、Railsから配信されるアセットはCloudfrontを経由して配信しております。
フロントエンドのUIフレームワークにはVuetifyを採用しています。アプリケーション全体のテーマや一般ユーザー向けの重要な画面は業務委託のデザイナーに依頼して設計してもらっていますが、それ以外の各画面のUIは担当するエンジニアがVuetifyのコンポーネントを組み合わせて実装しています。
なお、アプリケーションの規模はNuxt 3移行後の状態でページ数が110、コンポーネント数が454という大きさでした。
Nuxt Bridgeを使うか、直接Nuxt 3に上げるか
NuxtにはNuxt 3への移行用にNuxt BridgeというNuxt 3との互換APIを提供するレイヤーがあります。移行作業全体の方針を決める上で、まずNuxt Bridgeを使ってNuxt 3へ段階的に移行するのか、それともNuxt Bridgeを経由せずに直接Nuxt 3へ移行するのかを決める必要があります。
kickflowでは、Nuxt Bridgeを使わずに直接Nuxt 3へ移行する方針としました。 その理由としては、kickflowで利用しているNuxtに依存したモジュールのいくつかがNuxt Bridgeには非対応であること、Bridgeを導入したタイミングとNuxt 3に切り替えたタイミングでアプリケーション全体のリグレッションテストを2回実施する手間が大きいことがあります。
結果、kickflowでは 「まずNuxt 2の状態のままでNuxt 3に向けて対応できることはできるだけ対応してから、最後に一気にNuxt 3に上げる」 という方針で進めることになりました。
影響範囲の洗い出し
次に、移行作業全体のToDoとボリューム感を把握するため、Vue / Nuxt / Vuetifyのマイグレーションガイドをすべて確認して、kickflowのNuxt 3移行でやらないといけないことをスプレッドシートでリストアップしました。また、各作業がNuxt 2のままできるのか、Nuxt 3に上げるときに対応しないといけないのか、それともNuxt 3に上げる上で必須ではないのかもスプレッドシートにまとめています。
Nuxt 2の状態で対応したこと
Nuxt3に対応していないライブラリを剥がしていく
はじめに着手したのは、Nuxt 3に対応していないライブラリのアプリケーションからの削除です。Nuxt 2ではコミュニティにより多くのモジュールが開発されており、各種ライブラリを簡単にNuxtアプリケーションに追加できたのですが、これらモジュールの多くはNuxt 3に追従できておらずメンテナンスも活発ではない状況なので、ほとんどを剥がして自作のプラグインに置き換えていきました。
kickflowでは例えば以下のようなモジュールを削除しました(他にもたくさんあります)
VuexをPiniaに置き換える
Nuxt 2では状態管理用にVuexがデフォルトで追加されていましたが、Nuxt 3からはPiniaが標準の状態管理ライブラリになっています。Nuxt 2のままでもPiniaは使うことができるため、VuexからPiniaへの移行は先に実施しました。
PiniaはVuexよりもAPIがわかりやすくTypeScriptとの相性もよいので、移行したことによって開発の生産性が向上したのはよかったです。
axiosをofetchに置き換える
Nuxt 2ではHTTPクライアントとしてaxiosがデフォルトで追加されていましたが、Nuxt 3にはofetchが標準で入っています。kickflowではaxiosをラップしたAPIクライアントを実装していたのですが、これをofetchをラップするように修正しました。修正作業自体はそこまで大変ではなかったのですが、すべてのバックエンドとの通信に影響があるためリリース時はかなり緊張しました。
Vue 3のAPI変更への対応
Vue 2からVue 3の間に、APIの破壊的変更があります。以下の移行ガイドに沿って、まずはNuxt 2(Vue 2)で事前に対応できるものを対応しました。
kickflowの場合、具体的には以下のような対応を行いました。
- コンポーネントにemitsオプションを追加
- イベントAPI(
$on
や$off
など)の廃止 - グローバルAPIの呼び出し変更
Vue.nextTick
をnextTick
に変更など
- 配列を監視する際は
deep
オプションを追加
なお、emitsオプションはESLintのルールを追加しておくことで、emitsの定義漏れを検知することが可能です。kickflowでもNuxt 2プロジェクトのESLintのルールに以下を追加し、宣言していないイベントをemitできないように強制しました。
// .eslintrc.jsに以下を追加 'vue/require-explicit-emits': 'error'
Composition API(script setup構文)への書き換え
これについては暇なときにちょっとずつ進めればいいやというノリで進めていたので、Nuxt 2時代に終わらせるつもりはまったくなかったのですが、後述するVuetify 3の対応を待っている間にほとんどのコンポーネントに対してComposition API化が完了してしまいました。Composition APIに書き換えることでTypeScriptの型補完がより強力に効くようになったのは、開発する上で大いに助かりました。今後はコンポーネント間で共通するロジックをComposableに切り出すなど、よりComposition APIらしい書き方を模索していきたいです。
なお、kickflowではGitHub Copilotをエンジニア全員に付与しているのですが、Options APIからComposition APIへの書き換えのような比較的単純な作業には大いにCopilotが役に立ってくれました。
Nuxt 3に上げるときに対応したこと
新プロジェクトを作ってちょっとずつ移植していく
さて、ここまで来るとあとはNuxt 3に実際に上げていくことになります。kickflowでは既存のNuxt 2プロジェクトをNuxt 3に上げようとすると大量にエラーが発生して解決が困難だったため、既存のNuxt 2プロジェクトとは別に新規にNuxt 3プロジェクトを作成し、そこに必要なライブラリやプラグインなどを順に追加していくことにしました。 概ね、以下の順序で新しいNuxt 3プロジェクトをセットアップしていきました。
- nuxt.config.tsの修正
- ライブラリ
- アプリケーション全体に関わるものから順番に(例: Vuetify, ESLintなど)
- ユーティリティ
- プラグイン
- ミドルウェア
- レイアウト
- ストア
- 共通コンポーネント
/components/global
以下
- 各画面のページ・コンポーネント
Vue 3 / Nuxt 3に対応したライブラリの追加
まず、Vue 3やNuxt 3に対応したライブラリを追加していきます。前述のとおり、Nuxt用のモジュールは多くがNuxt 2用に書かれておりNuxt 3に対応したものはそんなに多くありません。kickflowで追加したNuxt 3対応モジュールは以下の3つくらいでした。
Nuxtモジュールがないその他のライブラリについては、自分でNuxt 3のプラグインを実装することでライブラリを追加していきました。基本的には各ライブラリのドキュメントを見ながら、プラグインの中で初期化処理を実装していくだけです。プラグインやミドルウェアの移行については、以下のNuxt公式の移行ガイドの通りに進めました。
コンポーネントやページを移植していく
コンポーネントやページを移植する段階になると、Nuxt 2プロジェクトからNuxt 3プロジェクトにファイルを1つずつコピーして動作確認していくのですが、このときにVue 3のAPI変更、Nuxt 3のAPI変更、Vuetify 3のAPI変更に対応してビルドが通るように修正していきます。Vue 3とNuxt 3のAPI変更については、主に以下のような対応を行いました。
- Vue 3のAPI変更
value
propsと@input
eventをmodelValue
propsと@update:modelValue
eventにリネーム
- Nuxt 3のAPI変更
useHead
を使って<head>
タグを設定definePageMeta
を使ってレイアウトやミドルウェアを指定する- 動的パスを持つページのファイル名を、
_id.vue
から[id].vue
にリネーム
さて、実は一番大変だったのはVue 3でもNuxt 3でもなくVuetify 3のAPI変更でした。
Vuetify 3はVuetify 2に比べて大幅にAPIが変更になっており、さらにVuetify 2で提供されていたコンポーネントやAPIがこの記事執筆時点でもまだ全てが利用できる状態になっていません。kickflowでは、頻繁に使用しているデータテーブル(v-data-table)と日付ピッカー(v-date-picker)が2023年11月に正式リリースになったことで、ようやく移行作業を本格的に開始できる状態になりました。しかし、kickflowでも使用していたツリービュー(v-treeview)については移行スケジュールに間に合わなかったため、ツリービューを自分たちで実装し直してから移行作業に取り掛かりました。
現在はNuxt 3への移行作業が一通り完了してQAを実施しているのですが、Vuetifyの細かいバグもかなり踏んでしまっており、正直かなり苦労しています。もし次に同じような大規模バージョンアップがあるなら、先にUIコンポーネントを内製化してVuetify依存を脱却してから取り掛かることを検討しようと思います。
Jestからvitestへの移行
Nuxt 3に上げる上でマストではないのですが、テストフレームワークをJestからVitestに移行しました。kickflowのフロントエンドはユーティリティや複雑なビジネスロジックに対しては単体テストが書いてあるのですがコンポーネントのテストはあまり書けておらずテストコードの数が多くなかったため、移行自体は大して苦もなく終わりました。なお、NuxtのAPIに依存したコンポーネントに対するテストはまだ書けていないため、今後nuxt-vitestなどを利用してテストを書ける環境を整備していく予定です。
移行してみて
現在QA中でひたすらバグを修正しているところなので、まだNuxt 3版のアプリケーションは安定性に問題があるのですが、開発者的に一番嬉しいのはデプロイ時間が大幅に短縮されたことになります。kickflowではGitHubのmainブランチにPull RequestがマージされるとGitHub ActionのCI/CDがトリガーされ、Herokuに対してデプロイを開始します。Nuxt 2版だとデプロイ全体*1で約11分かかっていたのですが、Nuxt 3版になるとデプロイ全体で4分程度にまで短縮されました。
最後に
本記事では、kickflowのNuxt 3移行プロジェクトの全体像をご紹介しました。 kickflowのフロントエンドはNuxt 3移行以外にも技術的な課題がたくさんあります。これまでkickflowでは私がフロントエンド領域は主にリードしてきたのですが、そろそろCTOの他の業務がしんどくなってきたので代わりに誰かやってくれないかなーと思っています。 めちゃくちゃ仕様が複雑なB2Bアプリケーションのフロントエンド改善にご興味のある方は、是非以下のリンクからご応募お待ちしております!
*1:Nuxtのビルド時間だけでなく、Railsのデプロイにかかる時間も含みます。