既存の Vue SPA プロジェクトのビルドを vue-cli 3 に切り替えた話

既存の Vue SPA プロジェクトのビルドを vue-cli 3 に切り替えた、いい話。

## モチベーション

Upgradeable の一言に尽きる。

実は A は過去に一度、 webpack 2 へのアップグレードを実施している。
しかし、以前の vue-cli の雛形は build ディレクトリに webpack の設定や vue-loader の設定が詰め込まれる構成となっていて、そのコードを読まないことにはアップグレードも何もないという状況だった上、 webpack 1 -> 2 は破壊的な変更が多かったので、完了までかなりの時間を要した。

vue-cli 3 ではその点を解消すべく、 `@vue/cli-service` というパッケージとそれ用の幾つかのプラグインが build, lint, test 等を行ってくれるように変更されている。
そのため、 cli-service とプラグインをアップデートするだけで、ビルド環境をアップグレードすることが可能とされている。

また、 `@vue/cli-plugin-*` のエコシステムも魅力的で、例えば PWA に対応したりする場合には `@vue/cli-plugin-pwa` というものがある。
この辺りは PWA 化がうまくいったらまたどこかで書きたい。

## 移行する時の心構え

### 2018/05/08 時点、ベータ版である

ベータ版であることには注意したい。
とはいえ、中で使っている webpack とか vue-loader がベータだというわけじゃないので、現時点でもあまり変なことにはなりにくいと思われる。

### 泥臭く、大変

移行した Vue プロジェクトは元々 CLI で作った雛形を使っていたため、まだ移行しやすかったほうだと思うが、とても泥臭く大変な作業になる。
それなりの時間を割くつもりでいた方がいいが、移行した後だから言えるが、上記のモチベーション通りの結果を得られたので、それなりの時間を割いて損はないと思われる。

### 「何もかも今まで通り」は諦める

「この webpack プラグインが動かない」「成果物のディレクトリ構成が変わっちゃってる」など色々出てくるはず。だが、レールに乗るつもりで、諦められるところは積極的に諦めるのが肝心。でないと移行に時間がかかりすぎる。

## 手順

### 移行すべきか判断する

まずは vue-cli への移行が可能かどうかを判断すると良いと思う。
公式のドキュメントどおりにディレクトリを作ってみて、どういうディレクトリ構成でどういうモジュールが入っているのか等を確認する。
この時点で、既存のプロジェクトとあまりに構成が違い過ぎたら、移行を考え直した方がよい。
A は src ディレクトリ以下の構成がほぼ同じだったので、移行できそうだと判断した。

あとは、移行の工数と、現状の環境をアップデートしていく工数とを秤にかけて判断になる。
A は webpack 2 で懲りていたのであんまり迷わなかった。

また、これはちょっと極論かもしれないが、小規模な SPA プロジェクトなら [nuxt](https://nuxtjs.org/) に切り替えてしまうのもアリだと思っている。 SSR も PWA もサックリ入るし。

### コマンドを追加する

既存のプロジェクトに `npm i @vue/cli-service` を行った後、 package.json の scripts にコマンドを追加する。

```json
{
  "scripts": {
    "serve": "vue-cli-service serve --open",
    "test": "vue-cli-service test",
    "lint": "vue-cli-service lint",
    ...
  }
}
```

私の場合はそれぞれ serve2, test2, lint2 として追加し、既存のビルドツールもそのまま実行出来るようにしておいた。

### 必要なプラグインを追加する

`vue add` というコマンドで、既存のプロジェクトに必要なプラグインを導入する。

- [vue add ドキュメント](https://github.com/vuejs/vue-cli/blob/dev/docs/cli.md#installing-plugins-in-an-existing-project)
- [プラグイン一覧](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue) : cli-plugin-x という形で配布されている

ただ **beta.6 では src/assets 以下の画像ファイルが破壊される不具合がある** ので、 beta.9 まで上げた方が良い。

また、全てがうまい具合に入ってくれるとは限らない。 Vue CLI 移行後に cli-plugin-typescript の導入をしてみたが、 .js が全部 .ts にリネームされたり、 ts-jest が入ってくれなかったりした。あまり `vue add` に頼らず、 `vue create` でお目当てのプラグインを入れた初期環境を作ってみて、既存のプロジェクトとの差分を確認していくといった泥臭いやり方も必要。

### vue.config.js の設定

vue.config.js という新しい設定ファイルが必要になる。
プロジェクトの基本的な設定、 PostCSS の設定、ローカルサーバの設定、 lint の設定、 webpack のプラグイン追加・設定変更などが行える。
「なんだ結局設定ファイルで辛みが発生するのか」と思うかもしれないが、例えば webpack.config.js で書いたら何十行にもなるような lint の設定を `lintOnSave: true` と書くだけで完了できるのだから、これまでに発生していた辛みとは比にならない。

A では webpack 周りの調整も行っている。下記に書く。

### webpack まわりの修正

vue-cli は webpack の設定をデフォルトでいい感じに調整してくれている。
が、既存のプロジェクトに適用するには、デフォルトのままでは使えないことがある。
A では ServiceWorkerWebpackPlugin, ContextReplacementPlugin, SriPlugin, BundleAnalyzerPlugin を追加し、 DefinePlugin, HtmlWebpackPlugin, alias を修正した。
追加するものについては configureWebpack を使い、修正するものについては chainWebpack を使うのがよい。
下記は一部編集・削除しているが、雰囲気を掴んでもらえるかと思う。

```js
  configureWebpack: config => {
    const plugins = [
      // moment.jsのロケールファイルは日本語だけ使う、あるある設定
      new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /ja/)
    ]

    if (isProduction || isDevelopment) {
      plugins.push(
        // HtmlWebpackPlugin と組み合わせて使うことで、scriptタグにintegrityとcrossoriginを追加する
        new SriPlugin({
          hashFuncNames: ['sha256', 'sha512']
        }),
        // どのライブラリが容量デカイかを調べる (stats.html を吐き出す)
        new BundleAnalyzerPlugin({
          analyzerMode: 'static',
          reportFilename: `../stats.html`,
          openAnalyzer: false
        })
      )
    }

    return {
      plugins
    }
  },
  chainWebpack: config => {
    // alias を追加
    config.resolve.alias
      .set('src', path.resolve(__dirname, './src'))

    // PreloadPlugin を無効化
    config.plugins.delete('preload')
    config.plugins.delete('prefetch')

    // define plugin の設定をいじる
    config.plugin('define').tap(args => {
      args[0]['process.env.HOGE'] = 123
      return args
    })
  },
```

上記の webpack の調整には `vue inspect` というコマンドが重要になる。
諸々設定を加えた後の webpack.config.js を見ることが出来るコマンドで、ちゃんと意図した wepback.config.js になっているかを確認できるだけでなく、 vue-cli が plugin をどんな name で登録しているかも確認できる。
たとえば、上記の chainWebpack の方では plugin を preload, prefetch, define 等といった文字列で呼び出しているが、これは vue-cli がそういう名前で plugin を追加しているため。

## 自動テスト

多分あまり参考にならないので詳細は書かない。
まず A は E2E のテストはやってなかったので移行作業自体が発生しなかった。
また unit テストについても jest で動かせるように書き換えた位にとどめていて、 [vue-test-utils](https://vue-test-utils.vuejs.org/en/) を使ったモダンなテストに書き換えたりはしていない。
（本当は書き換えを試みたんだけど A については難しかった）

もし自動テストがないなら、移行するとテスト環境が一気に揃うので、いいきっかけになるかもしれない。
~~（という何とも適当なまとめ……）~~

## 困ったら

### `vue create sample-project`

導入したいプラグインがうまく入らない、 lint が効かない等、大体の問題は vue create でそれらを備えたプロジェクトを作成し、それと見比べることで対応出来る。

### `vue inspect`

途中でも言及した、設定済みの webpack.config.js を見るコマンド。

### vue-cli の Issue を検索してソースを読む

人気のあるプロジェクトなんで、バグは大体誰かが踏んでるし、致命的なものは数日後に直る算段が立ってたりする。
beta.6 の途中で serve が動かなくなった事があったけど、 [割と一瞬で直ったりしてる](https://github.com/vuejs/vue-cli/issues/1162) 。
あとソースが読みやすいので、変な挙動すんなとか、ここどんな風に動くんだろと思ったらぶっちゃけソース読むほうが早い。
特に beta のうちはこれをよくやることになりそう。

## まとめ

泥臭さが滲んでてあんまり vue cli の魅力を語れてない感があるので、移行しての感想を。
結論、まだ数週間しか経ってないが **移行してよかった** と言えると思う。

- devDependencies がものすごく減り、アップデート作業の大変さが緩和された
- [早速 webpack 4 と vue-loader 15 へのアップデートが入りそう](https://github.com/vuejs/vue-cli/issues/1205)
- 最初に書いた pwa plugin など、やりたいことが増えた

ただ、ビルド環境をガンガンアップデートしたいという需要があり、 vue-cli に移行しやすかったという A の状況があったからこその話ではあり、この記事も A 以外で役に立つのかは若干疑問 :sweat_smile:
もし似たようなプロジェクトを抱えている方がいたら、移行を検討してみてはいかがだろうか。