【Rails×Vue.js×Webpacker】|Vue2系から3系のアップデートとTypeScript 導入の手順

フロントエンドを Vue.js、バックエンドを Rails で開発している[個人サービス](https://github.com/yuki0920/supplebox)の、フロントエンド環境をアップデートしました。
具体的には、Vue.js を 2 系 から 3 系 にアップデートし、新たにTypeScript を導入しました。

Vue.js 3 系と TypeScript の組み合わせについては、新しく規開発し始める場合の方法に関する情報は豊富なものの、既に動いている既存のサービスに導入する方法はほとんどありませんでした。
また

また、現サービスはバンドルツールとしてRails標準の Webpacker を利用していますが、Webpacker に言及している情報もかなり少なかったです。

Vue のアップデートと TypeScript の導入に際して、いくつかハマりどころがありましたので、私が実施した手順について簡単に紹介します。

## Vue2系 から Vue3 系へのアップデート手順

### ライブラリのインストール

vue, vue-loader compiler-sfc について、最新バージョンをインストールします。
なお、vue3 に対応しているライブラリは、next のタグが付いているケースが多いです。

```bash
yarn add vue@next vue-loader@next @vue/compiler-sfc
```

コマンド実行後の package.json は下記のように更新されているはずです。

```diff
// package.json
{
  // ...
  "dependencies": {
+    "@vue/compiler-sfc": "^3.0.11",,
-    "vue": "^2.6.12",
-    "vue-loader": "^15.9.6",
+    "vue": "^3.0.11",
+    "vue-loader": "^16.2.0",
    // ...
  }
  // ...
}
```

### Tree Shaking の有効化(任意)

Tree Shaking を適切に利用するために、Bundler Build Feature Flag を定義します。

Bundler Build Feature Flag とは`__VUE_OPTIONS_API__`や`__VUE_PROD_DEVTOOLS__`のことで、バンドルツールがバンドルする際のフラグです。
ちなみに、Tree Shaking とは Webpack でファイルをバンドルする際に、利用されていない不要なコードを除去して、バンドルサイズを小さくする仕組みのことです。

Webpack の設定に下記を追加します。

なお、従来の Options API を利用する場合は、`__VUE_OPTIONS_API__: false,`は定義してはいけません。(Default で true になっています。)

```config/webpack/environment.js
//...
const { DefinePlugin } = require('webpack')
environment.plugins.prepend(
  'Define',
  new DefinePlugin({
    __VUE_OPTIONS_API__: false,
    // or __VUE_OPTIONS_API__: true,
    __VUE_PROD_DEVTOOLS__: false
  })
)
//...
```

### Composition API に書き換える(任意)

Options API に変わり Composition API を利用する場合は SFC を書き変えましょう。

Composition API の利用方法については、[ドキュメント](https://v3.ja.vuejs.org/guide/composition-api-introduction.html) に詳しく記載がありますので、ご参考ください。

### 参考

- [Vue.js 3 · Issue #2751 · rails/webpacker · GitHub](https://github.com/rails/webpacker/issues/2751)
- [Vue3 on Rails - DEV Community](https://dev.to/vannsl/vue3-on-rails-l9d)
- [Migrating Vue 2 to Vue 3](https://zenn.dev/shippokun/articles/d14ce4989e3c4f)
- [Vue 2で書かれた個人プロジェクトをVue 3に書き換えてみた - クラシル開発ブログ](https://tech.dely.jp/entry/migrate_a_small_project_to_vue3)

## Typescript の導入手順

### Webpacker を使い TypeScript 追加する
TypeScript の導入は Webpacker を利用する方法が一番簡単です。

Webpacker 前提ではない場合は`yarn add ts-loader`で ts-loader を追加して一からセットアップする方法が主流のようですが、せっかく Webpacker を使っているので Webpacker の恩恵に預かることにします。

```bash
rails webpacker:install:typescript
```

### ts-loader の設定追加

ts-loader の設定を追加します loader の設定ファイルは Webpack の設定ファイルに定義しても良いですが、外部ファイル化するほうが見通しが良くなるため、外部ファイル化しています。

```config/webpack/loaders/ts.js
module.exports = {
  test: /\.tsx$/,
  loader: "ts-loader",
  options: {
    appendTsSuffixTo: [/\.vue$/],
  },
  exclude: /node_modules/,

}
```

```config/webpack/environment.js
const ts = require("./loaders/ts")
//...
environment.loaders.prepend("ts", ts)
```

### tsconfig の修正をする

`rails webpacker:install:typescript` コマンド実行時に追加された tsconfig.json に コンパイル対象のパスを定義します。

なお、 tsconfig.json とは TypeScript の設定ファイルで、コンパイルのオプションや TypeScript ファイルの対象や対象外のパスの設定することができます。

プロジェクトごとに異なりますが`.ts` と SFC のために `.vue` のパスを追加します。
下記は私が定義したパスの一例です。

```app/javascript/types/shims-vue.d.ts
{
  //...
  "include": [
    "app/javascript/**/*.ts",
    "app/javascript/**/*.vue",
  ],
  //...
}
```

### 型定義ファイルを追加する

上記の`app/javascript/**/*.ts`のパス内に SFC からエクスポートしたコンポーネントの型定義ファイルを追加します。

```app/javascript/types/shims-vue.d.ts
declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}
```

この型定義をしないと、VSCode 上(私の使用エディタ)で`モジュール 'app/javascript/components/ContactForm/index.vue' またはそれに対応する型宣言が見つかりません。`のような Warning が出ます。
上記で定義した tsconfig.json の include に定義したパスを間違えている場合も同様の Warning が出ますので、 Warning が出たらパスを確認してみると良いです。

### TypeScript の動作確認

これまでの手順で、 TypeScript のセットアップは完了しています。
動作確認のために、 `app/javascript/packs/example.js`などの既存のエントリーファイルを拡張子を .ts に書き換えてみましょう。

また。`app/javascript/components/example.vue` などの既存の SFC ファイルの lang を ts に指定してみましょう。

```app/javascript/components/example.vue
//...
<script lang='ts'>
  //...
</script>
//...
```

コンポーネントの振る舞いがこれまで通りであれば、TypeScript の導入が完了です。お疲れさまでした。

### 参考

- [Webpacker の概要 - Rails ガイド](https://railsguides.jp/webpacker.html#webpacker%E3%81%A7%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%92%E7%B5%B1%E5%90%88%E3%81%99%E3%82%8B)]
- [Webpacker, Vue 3, and TypeScript - DEV Community](https://dev.to/vannsl/webpacker-vue-3-and-typescript-1i99)
- [Rails + Vue + TypeScript な環境を構築してみる。Part1 | 🕊 tmtrnt.](https://notes.tmtr.jp/2019/09/30/vue-ts-rails/)
- [tsconfig.json - TypeScript Deep Dive 日本語版](https://typescript-jp.gitbook.io/deep-dive/project/compilation-context/tsconfig)