Vue 2からVue 3への移行とは、書き方の一部が変わった箇所(破壊的変更)を直しながら、Vue本体と周辺ライブラリ(Vuex・Vue Router・ビルドツール)を新しい版へ入れ替える作業です。 一度に書き換える必要はなく、Vue 2.7でComposition APIを先に取り入れ → 移行ビルド(@vue/compat)で警告を見ながら直す → 通常のVue 3.5に切り替える、という段階的な進め方が公式に用意されています。

この記事では、Vue 2.7.16 + Vuex 3 + Vue Router 3 + Vue CLI 5(webpack) で作った小さなタスク管理アプリを、Vue 3.5.43 + Pinia 4.0.3 + Vue Router 5.3.1 + Vite 8.3.1 へ実際に移行しました。途中で出た警告・ビルドエラーの実際の文面と直し方、移行前後のバンドルサイズとビルド時間の実測値を載せています。

Vue 2は長く現場を支えてきたフレームワークで、今も多くのシステムで安定して動いています。すぐに移行しない判断にも、稼働の安定や改修コストといった理由があります。 そのため、サポート終了後の選択肢も中立に整理しました。

破壊的変更の早見表(Vue 2 → Vue 3)

「移行ビルドの警告」は、@vue/compat が出す警告コードです。今回のサンプルで実際に出たものに ✓ を付けています。

Vue 2 の書き方 Vue 3 の書き方 移行ビルドの警告 今回
new Vue({ render }).$mount('#app') createApp(App).mount('#app') GLOBAL_MOUNT ✓
Vue.use(Plugin) / Vue.component() app.use() / app.component() GLOBAL_...
Vue.prototype.$xxx app.config.globalProperties.$xxx GLOBAL_PROTOTYPE ✓
フィルター {{ v | dateJa }} 関数 {{ dateJa(v) }} FILTERS / COMPILER_FILTERS ✓
イベントバス($on / $off) mitt などのライブラリ、またはストア INSTANCE_EVENT_EMITTER ✓
v-model(value + input) modelValue + update:modelValue(defineModel) COMPONENT_V_MODEL ✓
:title.sync="x" v-model:title="x" COMPILER_V_BIND_SYNC ✓
$listeners $attrs に統合 INSTANCE_LISTENERS ✓
$children テンプレート参照(ref)や provide/inject INSTANCE_CHILDREN
同じ要素の v-if と v-for(v-forが優先) v-ifが優先。computedで絞り込む COMPILER_V_IF_V_FOR_PRECEDENCE ✓
<template v-for> の子に key <template v-for> 自体に key COMPILER_V_FOR_TEMPLATE_KEY_PLACEMENT
<template functional> 普通のコンポーネント (ビルドエラー) ✓
非同期コンポーネント () => import() defineAsyncComponent(() => import()) COMPONENT_ASYNC ✓
beforeDestroy / destroyed beforeUnmount / unmounted OPTIONS_BEFORE_DESTROY ✓
@keyup.13 @keyup.enter V_ON_KEYCODE_MODIFIER ✓
h('p', { attrs: { id } }) import { h } from 'vue'、h('p', { id }) RENDER_FUNCTION ✓
.v-enter / .v-leave .v-enter-from / .v-leave-from (今回は警告なし) ✓
<transition><router-view/></transition> <router-view v-slot> の中に <transition> Vue Routerの R0060 ✓
Vue.set / this.$set 通常の代入でOK GLOBAL_SET
:checked="false" で属性が消える false は "false" として出力(消すなら null) ATTR_FALSE_VALUE ✓
new VueRouter({ mode: 'history' }) createRouter({ history: createWebHistory() }) (Vue Router側) ✓
path: '*' path: '/:pathMatch(.*)*' (Vue Router側) ✓
new Vuex.Store() Vuex 4 の createStore()、または Pinia (Vuex側) ✓

全項目は公式「Vue 3 移行ガイド」の破壊的変更一覧で確認できます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Vue2からVue3への移行手順|2.7→移行ビルド→3.5を実測」

Vue 2のサポート終了と、その後の選択肢

Vue 2は2023年12月31日にサポートが終了しました(EOL:End of Life)。 Vue公式の「Vue 2 LTS, EOL & Extended Support」ページには、次のように書かれています。

  • npmやCDNでの配布は続く(今あるアプリがすぐ動かなくなるわけではない)
  • セキュリティやブラウザ互換の修正を含む更新は行われない
  • 延長サポートが必要な場合の選択肢として、HeroDevs社の「Never-Ending Support(NES)for Vue 2」 が案内されている

npmの最後の版は vue 2.7.16(2023年12月24日公開) です。今回の移行前サンプルで npm audit を実行すると、次の表示になりました。

$ npm audit --omit=dev
vue  2.0.0-alpha.1 - 2.7.16
ReDoS vulnerability in vue package that is exploitable through inefficient regex evaluation in the parseHTML function - https://github.com/advisories/GHSA-5j4c-8p2g-v4jx
fix available via `npm audit fix --force`
Will install vue@3.5.43, which is a breaking change
...
2 low severity vulnerabilities

深刻度は「low」で、テンプレートを文字列から実行時にコンパイルする処理(parseHTML)に関するものです。 修正版は「Vue 3への更新」と表示されます。影響の有無は、アプリが実行時にテンプレートをコンパイルしているかで変わるため、自分の構成で確認します。

選択肢 向いている状況 考えておくこと
現状のまま運用 利用者が限られる社内システム、更新予定が少ない 脆弱性情報の定期確認、周辺ライブラリの更新停止
延長サポート(NES)を利用 規程で「サポートのあるソフトウェア」が必要、移行に時間がかかる 費用と契約。公式ページから案内されている
Vue 3へ段階的に移行 今後も機能追加を続ける この記事の手順。UIライブラリの対応状況
作り直し 画面の大部分を作り直す予定がある 範囲とスケジュール

どれが正解ということはなく、システムの寿命と更新頻度で選びます。 移行する場合は、次の手順で少しずつ進めると、動く状態を保ちながら作業できます。

移行の全体像

flowchart LR
  A["Vue 2.6以前"] --> B["Vue 2.7<br>script setup を先に導入"]
  B --> C["Vite + @vue/compat<br>Vuex 4 / Router 4以降"]
  C --> D["警告を1つずつ解消"]
  D --> E["Vue 3.5<br>Pinia / Router 5"]
段階 やること 状態
1. Vue 2.7 <script setup> や Composition APIを先に使い始める Vue 2のまま動く
2. 移行ビルド vue を @vue/compat に差し替え、Vuex・Routerを上げる Vue 3上でVue 2の書き方がほぼ動き、警告が出る
3. 警告の解消 警告コードごとに直す 警告が減っていく
4. Vue 3.5 @vue/compat を外す。VuexをPiniaへ 通常のVue 3

検証したサンプル(移行前)

Vue 2の代表的な書き方をあえて含めた、タスク一覧アプリです。

ファイル 含めたVue 2の書き方
main.js new Vue().$mount()、Vue.filter、Vue.prototype.$bus = new Vue()
router.js new VueRouter({ mode: 'history' })、path: '*'、h('p', { attrs })
store.js new Vuex.Store()、Vue.set
App.vue <transition> の中に <router-view>、.fade-enter
TaskInput.vue value / input の v-model、$listeners
DoneBadge.vue <template functional>
TaskListPage.vue フィルター、v-if と v-for の併用、.sync、@keyup.13、$on、beforeDestroy、非同期コンポーネント
{
  "dependencies": { "vue": "^2.7.16", "vue-router": "^3.6.5", "vuex": "^3.6.2" },
  "devDependencies": { "@vue/cli-service": "^5.0.9", "vue-template-compiler": "^2.7.16" }
}

動作はPlaywright(Chromium)で、「2件追加(ボタンとEnterキー)→ 1件完了 → 未完了だけ表示 → 別ページ → 存在しないURL」を操作して確認しました。移行前の結果です。

一覧: [ '備品を発注する(2026年10月01日)', '会議室を予約する(2026年10月20日)', '名刺を補充する(2026年10月20日)' ]
ログ: 「名刺を補充する」を追加しました
残り: 残り 2 件 / バッジ: 1件完了
未完了だけ: 2 件
非同期コンポーネント: Enterキーでも追加できます
about: このアプリはタスクを管理します
404: ページが見つかりません
--- console warnings/errors: 0

移行後も、この出力がまったく同じになることをゴールにします。

手順1:Vue 2.7で Composition API を先に使う

Vue 2.7では <script setup>・ref・computed などがVue 2のまま使えます。 新しく書く部品や、触る予定の部品から切り替えておくと、後の移行で直す量が減ります。

<!-- src/components/FilterTabs.vue(Vue 2.7 で動作、Vue 3 でも無修正で動いた) -->
<script setup>
defineProps({ filter: String })
const emit = defineEmits(['update:filter'])
const filters = ['all', 'open']
</script>

<template>
  <div>
    <button v-for="f in filters" :key="f" :id="'tab-' + f" @click="emit('update:filter', f)">{{ f }}</button>
  </div>
</template>

Vue CLI 5でビルドは成功し、画面の動作も変わりませんでした。このファイルは最後まで一度も直さずにVue 3.5で動きました。

手順2:移行ビルド(@vue/compat)に切り替える

ビルドツールをViteにし、vue を @vue/compat に差し替える

公式の「移行ビルド」の手順どおり、vue の読み込み先を @vue/compat に向け、互換モード(MODE: 2)を指定します。今回はあわせてビルドツールをVue CLIからViteへ変えました(index.html をルートに置き、<script type="module" src="/src/main.js"> を書く)。

// vite.config.js(移行ビルドの間だけ)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  resolve: { alias: { vue: '@vue/compat' } },
  plugins: [
    vue({ template: { compilerOptions: { compatConfig: { MODE: 2 } } } }),
  ],
})

つまずき1:Vuex 3のままではインストールできない

最初はVuex 3とVue Router 3のまま入れようとして、npmが止まりました。

$ npm i vue@3.5.43 @vue/compat@3.5.43 vuex@3.6.2 vue-router@3.6.5   (出力は抜粋)
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error Found: vue@3.5.43
npm error Could not resolve dependency:
npm error peer vue@"^2.0.0" from vuex@3.6.2

Vuex 3・Vue Router 3はVue 2専用なので、移行ビルドの段階で一緒に上げます。 Vuex 4(createStore)とVue Router 5を入れ、作り方を変えました。Vue Router 5は、unplugin-vue-routerを使っていない4からならコード変更なしで上げられると公式の移行ガイドに書かれています(主な変化はファイルベースルーティングの統合)。

// router.js(Vue Router 3 → 5)
import { createRouter, createWebHistory } from 'vue-router'
import TaskListPage from './views/TaskListPage.vue'

export default createRouter({
  history: createWebHistory(),              // mode: 'history' の代わり
  routes: [
    { path: '/', component: TaskListPage },
    { path: '/about', component: () => import('./views/AboutPage.vue') },
    { path: '/:pathMatch(.*)*', component: NotFound },   // path: '*' の代わり(NotFound は後述)
  ],
})
// main.js(移行ビルドの段階)
import Vue, { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'   // Vuex 4 の createStore() で作り直したもの

// そのまま残したもの(移行ビルドが警告を出すか確認する)
Vue.filter('dateJa', (v) => v.replace(/^(\d{4})-(\d{2})-(\d{2})$/, '$1年$2月$3日'))
Vue.prototype.$bus = new Vue()

createApp(App).use(router).use(store).mount('#app')

つまずき2:functional テンプレートはビルドエラー

[plugin vite:vue] src/components/DoneBadge.vue
SyntaxError: <template functional> is no longer supported in Vue 3, since functional components no longer have significant performance difference from stateful ones. Just use a normal <template> instead.

移行ビルドでも <template functional> だけはビルドが止まりました。 普通のコンポーネントにします。

<!-- 直した後 -->
<template>
  <span class="badge">{{ count }}件完了</span>
</template>

<script setup>
defineProps({ count: Number })
</script>

移行ビルドで出た警告(実際の一覧)

開発サーバー(vite)で画面を操作すると、ブラウザのコンソールと端末に次の警告が出ました(一部を抜粋)。

[Vue warn] (deprecation COMPILER_V_BIND_SYNC) .sync modifier for v-bind has been removed. Use v-model with argument instead. `v-bind:filter.sync` should be changed to `v-model:filter`.
[Vue warn] (deprecation COMPILER_V_IF_V_FOR_PRECEDENCE) v-if / v-for precedence when used on the same element has changed in Vue 3: v-if now takes higher precedence and will no longer have access to v-for scope variables.
[vite] (client) warning: (deprecation COMPILER_FILTERS) filters have been removed in Vue 3. The "|" symbol will be treated as native JavaScript bitwise OR operator.
[Vue warn]: (deprecation GLOBAL_MOUNT) The global app bootstrapping API has changed: vm.$mount() and the "el" option have been removed.
[Vue warn]: (deprecation GLOBAL_PROTOTYPE) Vue.prototype is no longer available in Vue 3. Use app.config.globalProperties instead.
[VUE_ROUTER_R0060] <router-view> can no longer be used directly inside <transition>.
[Vue warn]: (deprecation INSTANCE_EVENT_EMITTER) vm.$on/$once/$off() have been removed. Use an external event emitter library instead.
[Vue warn]: (deprecation COMPONENT_V_MODEL) v-model usage on component has changed in Vue 3.
[Vue warn]: (deprecation COMPONENT_ASYNC) Async component <HelpBox> should be explicitly created via `defineAsyncComponent()` in Vue 3.
[Vue warn]: (deprecation INSTANCE_LISTENERS) vm.$listeners has been removed.
[Vue warn]: (deprecation V_ON_KEYCODE_MODIFIER) Using keyCode as v-on modifier is no longer supported.

deprecation の警告コードは14種類でした(抜粋外の ATTR_FALSE_VALUE・OPTIONS_BEFORE_DESTROY・RENDER_FUNCTION を含む。アプリ本体は createApp に変えていたので、GLOBAL_MOUNT はイベントバスの new Vue() から出たもの)。各警告には公式移行ガイドの該当ページのURLが付くので、それを見ながら1つずつ直せます。

気をつけたい点が2つありました。

  1. v-if と v-for の併用は、移行ビルドでもVue 3の動き(v-if優先)になりました。 「未完了だけ」を押すと Property "t" was accessed during render but is not defined on instance. と Cannot read properties of undefined (reading 'done') が出て、未完了だけの件数が「3 件」(本来は2件)になりました。互換モードでも動きが変わる箇所があるので、警告が出たら動作確認までセットで行います。
  2. トランジションのクラス名(.fade-enter → .fade-enter-from)には警告が出ませんでした。 CSSは実行時の警告の対象外なので、grep -rn "enter|leave" src のように文字列で探して直します。

手順3:警告を1つずつ直す(直す前と後)

イベントバスとフィルター

// 前:main.js
Vue.filter('dateJa', (v) => ...)
Vue.prototype.$bus = new Vue()
// コンポーネント内:this.$bus.$on('added', ...) / {{ t.due | dateJa }}
// 後:src/bus.js(mitt は gzip で200バイト未満のイベントライブラリ)
import mitt from 'mitt'
export const bus = mitt()

// 後:src/format.js(フィルターは普通の関数に)
export const dateJa = (v) => v.replace(/^(\d{4})-(\d{2})-(\d{2})$/, '$1年$2月$3日')

イベントバスは、画面をまたいで状態を共有しているなら Pinia に移す方が見通しがよくなります。 通知だけなら mitt で十分です。

v-model・$listeners(TaskInput.vue)

<!-- 前(Vue 2) -->
<template>
  <input :value="value" v-on="listeners" />
</template>
<script>
export default {
  props: ['value'],
  computed: {
    listeners() {
      return { ...this.$listeners, input: (e) => this.$emit('input', e.target.value) }
    },
  },
}
</script>
<!-- 後(Vue 3.5) -->
<script setup>
const model = defineModel()
</script>

<template>
  <!-- 親の @keyup などは $attrs として自動でこの要素に渡る -->
  <input v-model="model" />
</template>

Vue 3では親が付けたイベント(@keyup.enter など)は $attrs に含まれ、ルート要素へ自動で渡ります。 $listeners を合成する処理はまるごと不要になりました。

テンプレート(TaskListPage.vue)

<!-- 前 -->
<TaskInput id="title" v-model="title" @keyup.13="add" />
<FilterTabs :filter.sync="filter" />
<li v-for="t in tasks" v-if="filter === 'all' || !t.done" :key="t.id" class="task">
  {{ t.title }}({{ t.due | dateJa }})
</li>

<!-- 後 -->
<TaskInput id="title" v-model="title" @keyup.enter="add" />
<FilterTabs v-model:filter="filter" />
<li v-for="t in visibleTasks" :key="t.id" class="task">
  {{ t.title }}({{ dateJa(t.due) }})
</li>
// 後:script(Options APIのまま。TaskInput等のimportは省略)
import { defineAsyncComponent } from 'vue'
import { mapState, mapActions } from 'pinia'
import { useTaskStore } from '../stores/tasks'
import { bus } from '../bus'
import { dateJa } from '../format'

export default {
  components: {
    TaskInput, DoneBadge, FilterTabs,
    HelpBox: defineAsyncComponent(() => import('../components/HelpBox.vue')),
  },
  data() { return { title: '', due: '2026-10-15', filter: 'all', log: '' } },
  computed: {
    ...mapState(useTaskStore, ['tasks', 'remaining']),
    visibleTasks() { return this.tasks.filter((t) => this.filter === 'all' || !t.done) },
  },
  created() { bus.on('added', this.onAdded) },
  beforeUnmount() { bus.off('added', this.onAdded) },   // beforeDestroy から改名
  methods: {
    ...mapActions(useTaskStore, { addTask: 'add', toggle: 'toggle' }),
    dateJa,
    onAdded(title) { this.log = `「${title}」を追加しました` },
    add() {
      if (!this.title) return
      this.addTask({ title: this.title, due: this.due })
      bus.emit('added', this.title)
      this.title = ''
    },
  },
}

Options API(data / methods で書く方式)はVue 3でも公式にサポートされています。 移行と同時にComposition APIへ書き換える必要はなく、今回も TaskListPage.vue はOptions APIのまま動かしました。

トランジションと404ページ

<!-- App.vue 後 -->
<router-view v-slot="{ Component }">
  <transition name="fade" mode="out-in">
    <component :is="Component" />
  </transition>
</router-view>

<style>
.fade-enter-from, .fade-leave-to { opacity: 0; }   /* .fade-enter から改名 */
.fade-enter-active, .fade-leave-active { transition: opacity .2s; }
</style>
// router.js 後:render 関数は h を import し、属性は直接書く
import { h } from 'vue'
const NotFound = { render: () => h('p', { id: 'notfound' }, 'ページが見つかりません') }

手順4:@vue/compat を外し、Vuex を Pinia へ

警告がなくなったら、vite.config.js の alias と compatConfig を消し、@vue/compat をアンインストールします。

// vite.config.js(最終)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({ plugins: [vue()] })

Vue公式は新規の状態管理にPiniaを推奨しています。 Vuex 4はVue 3で動くので、移行ビルドの段階ではVuex 4のまま進め、最後にPiniaへ移すと作業を分けられます。

// 前:Vuex 3(store.js)
export default new Vuex.Store({
  state: { tasks: [...] },
  getters: { remaining: (s) => s.tasks.filter((t) => !t.done).length },
  mutations: {
    add(state, task) { state.tasks.push(task) },
    toggle(state, id) { const t = state.tasks.find((x) => x.id === id); Vue.set(t, 'done', !t.done) },
  },
  actions: { add({ commit }, { title, due }) { commit('add', { id: Date.now(), title, due, done: false }) } },
})
// 後:Pinia 4(src/stores/tasks.js)
import { defineStore } from 'pinia'

export const useTaskStore = defineStore('tasks', {
  state: () => ({ tasks: [{ id: 1, title: '備品を発注する', due: '2026-10-01', done: false }] }),
  getters: { remaining: (s) => s.tasks.filter((t) => !t.done).length },
  actions: {
    add({ title, due }) { this.tasks.push({ id: Date.now(), title, due, done: false }) },
    toggle(id) { const t = this.tasks.find((x) => x.id === id); t.done = !t.done },
  },
})
// 後:main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'

createApp(App).use(createPinia()).use(router).mount('#app')
Vuex Pinia
state / getters そのまま(state は関数で返す)
mutations と actions actions に一本化(this で直接変更)
this.$store.commit('toggle', id) store.toggle(id)
mapState / mapActions(vuex) mapState(useStore, [...]) / mapActions(pinia)
Vue.set(obj, key, v) 普通の代入

移行後の結果(動作・サイズ・ビルド時間)

移行後のアプリで同じPlaywrightの操作をした結果、出力は移行前と完全に一致し、Vueの警告は0件になりました(出たのは favicon.ico の404のみ)。

一覧: [ '備品を発注する(2026年10月01日)', '会議室を予約する(2026年10月20日)', '名刺を補充する(2026年10月20日)' ]
ログ: 「名刺を補充する」を追加しました
残り: 残り 2 件 / バッジ: 1件完了
未完了だけ: 2 件
...
404: ページが見つかりません

バンドルサイズ(JSの合計、gzip -9 で測定)

段階 構成 JSファイル数 圧縮前 gzip
移行前 Vue 2.7.16 + Vuex 3 + Router 3 + Vue CLI 5 4 118.7 KiB 41.5 KiB
移行ビルド @vue/compat 3.5.43 + Vuex 4 + Router 5 + Vite 8 3 229.9 KiB 83.6 KiB
移行後 Vue 3.5.43 + Pinia 4 + Router 5 + mitt + Vite 8 3 105.9 KiB 41.1 KiB

移行ビルドの段階では互換用のコードが入るため約2倍になりましたが、@vue/compat を外すと移行前とほぼ同じ大きさ(41.1 KiB)に戻りました。 移行ビルドはあくまで途中の足場で、本番へ出す前に外すことを前提にします。

ビルド時間(本番ビルド、キャッシュを消して各3回)

段階 1回目 2回目 3回目
移行前(Vue CLI 5 / webpack 5) 4.21秒 4.24秒 4.06秒
移行ビルド(Vite 8) 0.94秒 0.89秒 0.93秒
移行後(Vite 8) 0.92秒 0.89秒 0.84秒

コマンドの起動から終了までで、約4.2秒 → 約0.9秒になりました(2コアの共有環境での参考値)。

UIライブラリを使っている場合

UIライブラリの移行は、Vue本体より作業量が大きくなりやすい部分です。 先に対応版と変更点を確認し、画面単位で進めます。

Vue 2で使っていたもの Vue 3の対応版 ひとこと
Vuetify 2 Vuetify 3 → 4(最新は4.2.2) 2→3でコンポーネントのAPIが大きく変わった。4ではCSSレイヤー、テーマ既定値(system)、ブレークポイントの値なども変更
Element UI Element Plus(最新は2.14.6) Vue 3版として別パッケージ。コンポーネント名は似ているが、props名の変更などを公式ドキュメントで確認

よくある失敗と直し方

症状 原因 直し方
ERESOLVE ... peer vue@"^2.0.0" from vuex@3.6.2 Vuex 3・Router 3はVue 2専用 Vuex 4(またはPinia)とVue Router 4以降に上げる
<template functional> is no longer supported functionalテンプレートの廃止 普通のコンポーネントにする
一覧の絞り込み結果がおかしい v-if が v-for より先に評価される computedで絞り込んでから v-for
フェードが効かない .v-enter の改名(警告なし) .v-enter-from に直す。文字列で検索
<router-view> can no longer be used directly inside <transition> Router 4以降の書き方 <router-view v-slot="{ Component }">
入力値が [object InputEvent] になる $listeners をそのまま渡し、input イベントが素通り Vue 3では defineModel と $attrs に任せる

最後の行は、実はVue 2のサンプルを作る段階で起きたものです。 v-on="$listeners" と @input を併用すると、親の v-model にイベントオブジェクトが渡ってしまいます。Vue 3の defineModel ではこの組み立て自体が不要になります。

よくある質問

Q. Vue 2のサポートが終わったら、すぐに移行しないといけませんか? A. 配布は続いており、アプリがすぐに動かなくなるわけではありません。更新が止まった点を踏まえ、脆弱性情報の確認、延長サポートの利用、段階的な移行のどれを選ぶかを、システムの寿命と更新頻度で判断します。

Q. 移行と同時にComposition APIへ書き換えるべきですか? A. 必須ではありません。Options APIはVue 3でも公式にサポートされており、今回もOptions APIのまま移行しました。触る機会のある部品から少しずつ <script setup> にしていく進め方が現実的です。

Q. Vue CLIのままVue 3へ移行できますか? A. 公式の移行ビルドの手順にはvue-loader 16以上へ上げる方法も書かれています。今回はビルド時間の短縮も狙ってViteへ同時に移しましたが、作業を分けたい場合はビルドツールの変更を後回しにする選択もあります。

まとめ

  • Vue 2は2023年12月31日にサポート終了。配布は続くが更新はなし。選択肢は 現状運用・延長サポート(NES)・段階移行・作り直し
  • 進め方は Vue 2.7で <script setup> を先に導入 → @vue/compat で警告を見ながら修正 → Vue 3.5
  • 移行ビルドの段階で Vuex 3・Router 3は上げる必要がある(ERESOLVEで止まる)。<template functional> はビルドエラー
  • 今回の警告は14種類。v-if と v-for の併用は互換モードでも動きが変わり、トランジションのクラス名は警告が出ない
  • Options APIのままでもVue 3で動く。Vuexは最後にPiniaへ移すと作業を分けられる
  • 実測で gzipは41.5 KiB → 41.1 KiB(移行ビルド中は83.6 KiB)、ビルドは約4.2秒 → 約0.9秒
  • 移行後も Playwrightの操作結果が移行前と完全一致することをゴールにすると、安心して切り替えられる

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Vue2からVue3への移行手順|2.7→移行ビルド→3.5を実測」

株式会社RJCでは、長く動いてきたシステムを大切に保守しながら、新しいバージョンへ安全に移す方法もプロジェクトの中で共有しています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?