Vue2 ⇒ Vue3への移行日記 ～ビルド編～

# はじめに
この記事は [DENSO アドベントカレンダー 2023](https://qiita.com/advent-calendar/2023) の18日目の記事です。

私は、社内でWebアプリを開発しているのですが、Rails6×Vue2で作っています。
ただ、Vue2のサポートが今年いっぱいで切れてしまうので、Vue3へ移行することになりました。
せっかくなので、移行の経緯を残しておこうと思い、筆を執っています！

[前回](https://qiita.com/usk0513/items/f4d6b4f221d7cf221cd5)Vue3対応のライブラリに更新したので、今回はVue3でビルドできるようにしていきます。

# 現状のビルド
現状の環境は下記のようになっています。
- Rails 6.1.7.6
- webpack 5.78.0
- jsbundling-rails 1.2.1
- Vue 3.3.12

Rails6でよく使われるWebpackerはRails7で不要になったことにより引退済みなので、`jsbundling-rails` + `webpack`にすでに切り替えてあります。

`webpack.config.js`は`jsbundling-rails`の[Github](https://github.com/rails/jsbundling-rails/blob/main/docs/switch_from_webpacker.md)の内容をベースにVue用に追加・修正しています。

``` webpack.config.js
const path = require("path");
const webpack = require("webpack");
const mode = process.env.RAILS_ENV === 'production' ? 'production' : 'development';
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');

module.exports = {
  mode,
  entry: {
    // add your css or sass entries
  },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, '..', '..', 'app/assets/builds')
  },
  optimization: {
    moduleIds: 'deterministic',
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.(sa|sc|c)ss$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
      },
    ]
  },
  resolve: {
    alias: {
      'vue': path.resolve('./node_modules/vue')
    },
    extensions: ['.js', '.jsx', '.scss', '.css'],
  },
  plugins: [
    new VueLoaderPlugin(),
    new FixStyleOnlyEntriesPlugin(),
    new MiniCssExtractPlugin(),
  ],
}
```

# エラーを1つずつ解決していく
現状でビルドすると、下記エラーが出てきます。
```terminal
[webpack-cli] Error: Cannot find module 'vue-loader/lib/plugin'
```
`vue-loader`のライブラリをバージョンアップ影響ですね。
[公式ドキュメント](https://vue-loader.vuejs.org/guide/#manual-setup)に`webpack.config.js`の書き方が載っていたので、そのとおりに修正します。

また、[方針決定編](https://qiita.com/usk0513/items/df7ad70969d63f402154)に記載した通り、Vue3に移行しますが、Options APIを使い続けることにしています。Vue3でOptions APIを使うには`webpack.config.js`に設定が必要です。
加えて、ビルドのエラーを解消した後にブラウザから画面を確認するとコンソールに下記のようなメッセージが表示されます。
```console
Feature flags __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__ are not explicitly defined
```
こちらも解消するために、`__VUE_PROD_DEVTOOLS__`も`webpack.config.js`に明示的に設定しておきます。

```webpack.config.js
const webpack = require("webpack");
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
    // 省略
    plugins: [
        new VueLoaderPlugin(),
        new webpack.DefinePlugin({
            __VUE_OPTIONS_API__: true,
            __VUE_PROD_DEVTOOLS__: false,
        }),
    ]
}
```
この修正で、`webpack.config.js`の設定自体は問題がなくなりました。

再度ビルドしてみると、エラーもワーニングも大量に出ています。だいぶ気分が萎える量のエラーとワーニングですが、こちらも1つずつ解決していきます。
一番多いワーニングは、各javascriptファイルでvueをインポートする箇所です。

```terminal
export 'default' (imported as 'Vue') was not found in 'vue'
```

調べてみるとVue2からVue3で[Vueインスタンスの生成方法](https://ja.vuejs.org/guide/essentials/application)や[コンポーネント登録](https://ja.vuejs.org/guide/components/registration)・[プラグインインストール方法](https://ja.vuejs.org/api/application.html#app-use)が変わったようです。

```javascript
// Vue2
import $ from 'jquery';
import Vue from 'vue';
import App from '../components/App.vue';
import MyComponent from 'mycomponent';
import MyPlugin from './plugins/MyPlugin';

Vue.component('my-component', MyComponent);
Vue.use(MyPlugin);

document.addEventListener('DOMContentLoaded', () => {
  new Vue({el: '#app', render: h => h(App, {props: {}})}).$mount();
});
```

```javascript
// Vue3
import $ from 'jquery';
import { createApp, h } from 'vue';
import App from '../components/App.vue';
import MyComponent from 'mycomponent';
import MyPlugin from './plugins/MyPlugin';

document.addEventListener('DOMContentLoaded', () => {
  createApp({render: () => h(App, {})}).component('my-component', MyComponent).use(MyPlugin).mount('#app');
});
```

大量にあるVueファイルをすべて書き換えていきます。
これでワーニングは全てなくなりました。

残りのエラーですが、前回更新した`vue-multiselect`が読み込めないとエラーが出ます。
どうも元々の読み込み方が良くなかったようで、こちらも、公式ドキュメントを参考に、インポートする箇所を修正します。

```vue
<template>
</template>

<script>
import VueMultiselect from 'vue-multiselect'
import 'vue-multiselect/dist/vue-multiselect.css'
export default {
  components: { VueMultiselect },
  data () {
    return {
    }
  }
}
</script>
```

最後に残ったエラーは`<`をHTMLの平文で使っているというエラーでした。
```terminal
VueCompilerError: Illegal tag name. Use '&lt;' to print '<'.
```
エラーメッセージにあるように`&lt;`に置き換えたらすんなりエラーが消えてくれました。

# 終わりに
これで無事にビルドでのエラー・ワーニングは全て対処できました。
ビルドが一番苦戦するかと思ったのですが、思っていたよりはすんなり解決できました。

残りは、画面を1つずつ確認していきながら動作がおかしいところを直していく作業になります！
記事にできそうなことがあったら、続編を執筆します。