reactで開発中のアプリケーションのビルド基盤をcreate-react-appからviteに移行した

## まえがき
現在、業務でtoB向けサービスの開発を行なっています。
このプロジェクトのフロントエンドはReact+typescriptで構築されており、ビルド基盤はcreate-react-app(CRA)を利用しています。

この度、このプロジェクトのビルド基盤をCRAからViteへ移行したため、その記録を書いていきます。

### Viteとは
[公式サイト](https://ja.vitejs.dev/guide/)より

> Vite（フランス語で「素早い」という意味の単語で /vit/ ヴィートのように発音）は、現代の Web プロジェクトのために、より速く無駄のない開発体験を提供することを目的としたビルドツールです。2 つの主要な部分で構成されています:
> * 非常に高速な Hot Module Replacement (HMR) など、ネイティブ ES モジュールを利用した豊富な機能拡張を提供する開発サーバ。
> * Rollup でコードをバンドルするビルドコマンド。プロダクション用に高度に最適化された静的アセットを出力するように事前に設定されています。

CRAを使用している場合、コード量が増加するにつれてビルド時間も増加してしまうという問題点があります。
Viteであればその問題点を解消できる(コード量が増えても高速でビルドできる)ので、開発効率を高めるためにも移行を決定しました。

### プロジェクトについて
モノレポで開発を行なっています。
ディレクトリ構成は以下の通りです。
```
project
　　┣━　client　…　フロントエンド
　　┣━　server　…　bff
　　┣━　hasura …　バックエンド（データベース）
　　┗━　sharedTypes　…　フロントエンド、バックエンドの両方で使用する型を置いておくディレクトリ
```
Viteへの移行は、`client`ディレクトリで行いました。

バージョンは以下の通りです。
* react: 17.0.2
* typescript: 4.1.2
* vite: 2.9.5

## 移行にあたってやったこと
### パッケージの整理
#### 必要なパッケージ
* `vite`
* `@vitejs/plugin-react`

この2つをインストールするために、
`yarn add -D vite @vitejs/plugin-react`を実行します。

#### 不要になるパッケージ
* `react-scripts`

これを削除するために、`yarn remove react-scripts`を実行します。

### package.jsonのscriptsの修正
package.jsonのscriptsを以下のように修正しました。
```diff_json
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
- "eject": "react-scripts eject",
+ "start": "vite",
+ "build": "tsc && vite build",
```
このプロジェクトがモノレポであるが故に、scriptsを後に修正する必要があるとは、今はまだ知る由もなかった…

### vite.config.tsの作成
`client`の直下(`package.json`や`tsconfig.json`と同じ階層)に、`vite.config.ts`ファイルを作成します。

```typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  server: {
    host: true,
    open: true,
  },
  resolve: {
    alias: {
      src: path.resolve(__dirname, './src/'),
    },
  },
  build: {
    outDir: 'build',
  },
  plugins: [react()],
});

```
* server.host
[こちらのDiscussions](https://github.com/vitejs/vite/discussions/3396)によると、ファイルシステムを外部に公開するために必要なようです(曖昧)。
* server.open
trueにしておくと、サーバーを起動した時に自動でブラウザを開いてくれるようになります。
* resolve.alias
絶対パスを使用するために設定しました(曖昧)。
* build.outDir
ビルドされたファイルを出力するディレクトリ名を設定する項目です。
CRAでのビルドファイルの出力先が`build`であるため、それに合わせて`build`に設定しました。
(Viteでのビルドファイルの出力先は、デフォルトでは`dist`)

### index.htmlの修正
`client/public`にある`index.html`を、`client`直下に移動します。

そして、`index.html`にscriptタグを追加します。
```diff_html
<body>
  <noscript>JavaScriptを有効にしてください</noscript>
  <div id="root"></div>
+  <script type="module" src="/src/index.tsx"></script>
</body>
```

また、`<link />`タグの`href`から`%PUBLIC_URL%`を削除します。
```diff_html
- <link rel="icon" href="%PUBLIC_URL%/favicon.svg" type="image/svg+xml" />
- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
+ <link rel="manifest" href="/manifest.json" />
```

### 環境変数の変更
CRAの場合は`REACT_APP_`で始まる環境変数を使用していましたが、Viteでは`VITE_`で始まる環境変数を使用するため、変更します。
```diff
- REACT_APP_XXXXXX=hoge
+ VITE_XXXXXX=hoge
```

そして、環境変数の読み込み方法も変わります。
CRAの場合は`process.env`でしたが、Viteでは`import.meta.env`で読み込みを行うため、書き換えます。
```diff_typescript
- process.env.REACT_APP_XXXXXX
+ import.meta.env.VITE_XXXXXX
```

### env.d.tsの作成
自分が定義した環境変数を読み込む際にtypescriptに自動補完してもらうために、`client/src`に、`env.d.ts`を作成します。
```typescript
interface ImportMetaEnv {
  readonly VITE_XXXXXX: string
  // その他の環境変数...
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}
```

### tsconfig.jsonの修正
`tsconfig.json`の`compilerOptions.types`に`vite/client`を追加します。
```
"types": ["vite/client"]
```
`.svg`や`.png`などのアセットをインポートする箇所でts2307エラーが発生していたのですが、これを追加したら直りました。

## 終了！　と思いきや
これにて移行作業は終了です。`yarn start`をして、動作を確認していきます。
ローカルの開発環境では問題なく動きました。

次に、`client`ディレクトリをビルドして動作を確認します。
するとどうでしょう。ビルド時にエラーが出ました。

### エラーの原因
エラーメッセージは
```
TS2307: Cannot find module 'sharedTypes/hoge/fuga' or its corresponding type declarations.
```
と、`sharedTypes`ディレクトリからのインポート部分で発生していました。tsエラーですね。

`sharedTypes`はビルドせずに`client`のビルドのみを行ったため、`tsc`が`sharedTypes`を見つけられなかったのが原因のようです。


### エラーの修正
`package.json`のビルドコマンドから`tsc`を削除します。
```diff_json
  "start": "vite",
- "build": "tsc && vite build",
+ "build": "vite build",
```
ビルド時のエラーは出なくなりますが、これだと`tsc`による型チェックが行われなくて困ってしまいます。
ですので、ビルド時ではなく開発中に型チェックを行ってもらえるようにします。

`npm-run-all`というパッケージ(コマンドラインツール)を使用するため、まず`yarn add -D npm-run-all`を実行してパッケージを追加します。
そして、`start`コマンドを以下のように修正します。
```diff_json
- "start": "vite",
+ "dev": "vite",
+ "type-check": "tsc --noEmit",
+ "start": "run-p \"dev\" \"type-check --watch\"",
```
(`run-p`は、複数のコマンドを並列実行するためのコマンドである`npm-run-all --parallel`のショートカットです)
これで、ローカルのサーバーを起動した際に`tsc`の型チェックが行われるようになりました。

## 今度こそ終了！
再びビルドを行うと、今度はエラーも出ず、無事にビルドが完了しました。
デプロイされたものも問題なく動いているようですので、今度こそ晴れて終了です！

## あとがき


移行作業にあたって、以下のサイト・記事を参考にしました。
* [Vite | 次世代フロントエンドツール](https://ja.vitejs.dev/)
* [create-react-app で作成したアプリを Vite に移行する](https://zenn.dev/kou_pg_0131/articles/cra-migrating-to-vite)
* [ビルドシステムをcreate-react-appからViteに移行した話 - Sansan Builders Blog](https://buildersbox.corp-sansan.com/entry/2022/03/24/110000)
* [dockerにviteを入れてvueで開発しようとしたときlocalhostに繋がらなかった話 | CHOICE](https://choice-site.com/2021/06/24/docker%E3%81%ABvite%E3%82%92%E5%85%A5%E3%82%8C%E3%81%A6vue%E3%81%A7%E9%96%8B%E7%99%BA%E3%81%97%E3%82%88%E3%81%86%E3%81%A8%E3%81%97%E3%81%9F%E3%81%A8%E3%81%8Dlocalhost%E3%81%AB%E7%B9%8B%E3%81%8C/)