初めに
弊社のプロダクトであるHirameki 7の開発当時はNuxt3がまだ正式リリースされていなかったため、Nuxt2で開発を進めていました。
Nuxt3が正式リリースされた際、すぐに移行作業を開始できれば良かったのですが、新機能開発が短いスパンで続いていたため、移行作業に着手できずにいました。そのままNuxt2のEOL(End of Life)を迎えることになり、HeroDevsによるNES(Never-Ending Support)を利用して運用を継続していました。
その後、徐々に移行作業に取り組める体制が整い、最近ようやくNuxt3移行対応が完了しました。本記事では、大規模アプリケーションにおけるNuxt3移行対応の工数や具体的な方法について共有いたします。
移行対象のファイルについて
主な移行対象のフォルダ:pages,components,layout,plugin,store,mixin
ファイル数
約1300
コード行数
約25万行
移行手法
下記の2つで考えていました。
- Nuxt Bridgeを使用して出力されるエラーを片っ端からつぶしていく
- 真っ新な状態で1からNuxt3バージョンで作り直す
システムの規模が大きすぎて"1."の方法だとエラーが大量に表示され画面を表示できるところまでたどり着くのが難しいと判断し、
"2."の方法を選択しました。Nuxt3のディレクトリ構造に合わせて1ファイルずつ移行していきました。
ソース管理はgitを使用していましたが、新規開発も同時進行で発生していたため、リポジトリごと分けるようにしました。
Nuxt2からNuxt3で行った作業
Nuxt3移行作業にて行った主な作業としては下記となります。
・OptionsAPIからCompositionAPIに修正
・mixinsからcomposablesに修正
・状態管理ライブラリの変更(Vuex→Pinia)
・axiosからfetchに変更
OptionsAPIからCompositionAPIに修正
nuxt3ではCompositionAPIが推奨されているため、全vueファイルを修正しました。
下記はOptionAPIからCompositionAPIに書き換えた場合のサンプルコードとなります。
// OptoinsAPI
<template>
<div>
<p>カウント: {{ count }}</p>
<button @click="increment">増やす</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
};
</script>
// CompositionAPI
<template>
<div>
<p>カウント: {{ count }}</p>
<button @click="increment">増やす</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
function increment() {
count.value++;
}
</script>
mixinsからcomposablesに修正
Nuxt2では機能ごとの共通処理をmixinsで定義していましたがNuxt3では非推奨となっているため、composablesに移行しました。mixinsで定義していたときは変数や関数がどこから呼び出されているのかが分かりにくかったのですが、composablesに移行したことで分かりやすくなりました。
mixinsのサンプルコード
// mixins/counterMixin.js
export const counterMixin = {
data() {
return {
count: 0
}
},
computed: {
isPositive() {
return this.count > 0
}
},
methods: {
increment() {
this.count++
}
}
}
// 呼び出し側
<template>
<div>
<p>Count: {{ count }}</p>
<p>Is Positive: {{ isPositive }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script>
import { counterMixin } from '~/mixins/counterMixin'
export default {
mixins: [counterMixin]
}
</script>
composablesのサンプルコード
// composables/useCounter.js
export const useCounter = () => {
// リアクティブな状態
const count = ref(0)
// 計算されたプロパティ
const isPositive = computed(() => count.value > 0)
// メソッド
const increment = () => {
count.value++
}
return {
count,
isPositive,
increment
}
}
// 呼び出し側
<template>
<div>
<p>Count: {{ count }}</p>
<p>Is Positive: {{ isPositive }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script setup>
const { count, isPositive, increment } = useCounter()
</script>
状態管理ライブラリの変更(Vuex→Pinia)
VuexがNuxt3では非推奨となったため、Piniaに変更しました。
書き方は大きく変わらないのですぐに馴染みました。
Vuexにてmapヘルパーを使用していたので、重複したstate名が定義などといった問題が解消されてよかったです。
Vuexのサンプルコード
export const state = () => ({
counter: 0
})
export const mutations = {
INCREMENT(state) {
state.counter++
},
DECREMENT(state) {
state.counter--
},
SET_COUNTER(state, value) {
state.counter = value
}
}
export const actions = {
increment({ commit }) {
commit('INCREMENT')
},
decrement({ commit }) {
commit('DECREMENT')
},
setCounter({ commit }, value) {
commit('SET_COUNTER', value)
}
}
export const getters = {
doubleCounter: (state) => {
return state.counter * 2
}
}
Piniaのサンプルコード
export const useCounterStore = defineStore('counter', {
state: () => ({
counter: 0
}),
getters: {
doubleCounter: (state) => state.counter * 2
},
actions: {
increment() {
this.counter++
},
decrement() {
this.counter--
},
setCounter(value) {
this.counter = value
}
}
})
axiosからfetchに変更
Nuxt3ではaxiosが非推奨になったため、fetchに変更しました。
レスポンス形式などが変わったことにより、地味にこちらの移行対応でハマることが多かったです。
修正期間
移行作業:1年、テスト:3か月
作業者は3人で約1年かけて移行作業をしておりました。
機能単位で役割分担を行い、新規開発と並行で進めていきました。
テストについて
全機能に対してplaywrightを用いたE2Eテストとシナリオテストを実施。
playwrightではVRT(Visual Regression Testing)を中心にイベント操作確認などを行いました。
VRTは2つの開発環境を用いてNuxt2バージョンとNuxt3バージョンで比較を行うようにしました。
シナリオテスト前などにVRTを行うことにより、細かいバグなどはあらかた潰すことが出来ました。
まとめ
大規模アプリケーションでのNuxt2からNuxt3のバージョンアップはとても大変でした。
ただAI技術の発達により、そこまでハマることがなかったので想定してたよりかは時間を短縮できました。
ようやくNuxt3移行が終わったと思いきや、Nuxt4の正式リリースがされたので、
次の記事では以前作った小規模Nuxt3アプリをNuxt4移行してみようかと思います。