Create React AppからVITEへマイグレート
経緯
今のプロジェクトは2年前ほどから始まっていて、Create React Appが使用されています。毎回devサーバーを起動するときに2-3分がかかってしまい、結構困っています。それでVITEとかでどうかなと思ってやってみました。
CRAはwebpackを使っているので、module graphの構築でファイルIO、tansform, parseのステップで時間がかかってしまい、devサーバー起動時にこれらの作業を終わるまで待たなければなりません。プロジェクトが大きくなると、待つ時間も増えていきます。
VITEでは全く違う策略をとっていて、起動する前に一回全てのファイルに対してtransform, parseなどする操作がありません。代わりに、ブラウザーからリクエストが届いたときに、ミドルウェアでこれらの仕事を行うらしい。そのため、devサーバーの起動は非常に速くなります。
どのくらい早くなるかを期待しながら、まずはマイグレーションをやっていこうと。
マイグレートステップ
ライブラリー
まずは必要なライブラリーをインストールします。
npm install -D vite @vitejs/plugin-react vite-tsconfig-paths
# or
yarn add -D vite @vitejs/plugin-react vite-tsconfig-paths
ts使っているかによって最後のものは適宜削除しますが、あればtsでのパス解析ができますので、ts使っている場合はおすすめです。例えばsrc
フォルダーからの相対パスが分かるので、インポートするときに、src/components/Menu/Nav
フォルダーをcomponents/Menu/Nav
に書けます。
CRAは使わないので、関連パッケージを削除します。
npm uninstall react-scripts
#or
yarn remove react-scripts
index.html
ファイル
Reactデフォルトでは、このファイルをpublic
フォルダーに置いていますが、VITEを使う場合、こちらのファイルをルートフォルダーに移動する必要があります。なお、%PUBLIC_URL%
といったプレースホルダーも不要になるため、削除で良いです。
<!-- これを -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- こちらに -->
<link rel="icon" href=".public/favicon.ico" />
上記は一例ですが、他にプレースホルダーがある箇所も全部入れ替えます。
次にjsxの入り口ファイルをインポートします。
<div id="root">...</div>
<!-- インデックスjsx/tsxファイルを追加 -->
<script type="module" src="/src/index.tsx"></script>
スクリプト
CRAのスクリプトでサーバー起動、ビルドしていますが、今はVITE用のものに入れ替えます。
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
から次のように
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
vite preview
ではプロダクションビルドが実行されるので、例えば、tsconfigで設定されたoutdir
のファイルが対象となります。vite
だけであればdevサーバーが立ち上げられます。ポート番号などの設定について、次の節で詳しくみてみたいと思います。
ts関連アップデート
tsを使う場合、いくつか変更点があります。まずvite関連のタイプをtsconfig
に追加します。
{
"compilerOptions": {
"types": ["vite/client"],
}
}
次にタイプ宣言ファイルを作ります。これはsrc
フォルダーもしくは、src/types
フォルダーといったタイプ管理しているところにおけば良いです。
// vite-env.d.ts
/// <reference types="vite/client" />
代わりに既存のreact-app-env.d.ts
は削除でOKです。
コンフィグファイル
以上のステップが終わったら、次にコンフィグファイルを作成します。プロジェクトのルートフォルダーに、touch vite.config.ts
を実行します。とりあえず次に下記のコードを追加し、用途によって徐々に増やしていきます。
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import viteTsconfigPaths from 'vite-tsconfig-paths';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), viteTsconfigPaths()],
});
環境変数
VITEのプロジェクトで環境変数の扱いがだいぶ違います。2つのステップがありますが、まずREACT_APP_
で始まるものを、VITE_
に入れ替えて、次にprocess.env
をimport.meta.env
へ変更します。
ただし、REACT_APP
のプリフィックスをそのままにしたい場合は一応可能です。
まずは一つプラグインを追加:
npm install -D vite-plugin-env-compatible
# or
yarn add -D vite-plugin-env-compatible
次にconfigファイルを更新
import env from 'vite-plugin-env-compatible'
export default defineConfig({
envPrefix: 'REACT_APP_',
plugins: [react(), viteTsconfigPaths(), env()],
});
DEV環境
開発環境の設定は、server
項目にあります。
export default defineConfig({
plugins: [react(), viteTsconfigPaths(), env()],
server: {
host: 'localhost',
port: 3000,
},
});
他の設定はこちらに参照。
Docker使用時のホットリロード
VITEにはホットリロード機能がありますが、ファイルに変更がある場合、変更箇所のファイルを更新して、rerenderなどを行なってくれます。これのおかげで開発がだいぶ快適になりますが、dockerを利用するときに少し設定が必要です。
server: {
open: true,
host: '0.0.0.0',
port: 3000, // docker container port
hmr: {
port: 3000, // host machine port mapping
},
watch: {
usePolling: true,
},
},
プロダクションビルド
プロダクションビルドするときに、build
という項目で変更します。
build: {
outDir: 'build',
rollupOptions:{
plugins: []
}
},
NodejsのPolyfill
例えば、nodejs環境依存のAPI、Buffer
, process
とかがコード内またはインストールされたライブラリーに利用されるケースがあります。これはCRAには問題なかったのですが、VITEではデフォルトのポリフィルがないので、何もしないとコンパイルされたコードでは、Buffer is undefined
などのエラーが出て、アプリが起動できなくなります。
FE側にもnodejsのAPIを使うようにするために、関連するポリフィルのライブラリーなどをDEV環境とプロダクション両方に追加します。
import GlobalsPolyfills from '@esbuild-plugins/node-globals-polyfill';
import rollupNodePolyFill from 'rollup-plugin-node-polyfills';
//...
build: {
outDir: 'build',
rollupOptions:{
plugins: [rollupNodePolyFill()]
}
},
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis',
},
plugins: [GlobalsPolyfills({ process: true, buffer: true })],
},
},
@xxx
でインポートしたい
普段はvite-tsconfig-paths
があるから、tsconfigで設定されたソースフォルダーからインポートするのが多いのですが、一応より広い用途があるパス解析の設定が可能です。
import { resolve } from 'path';
// ...
resolve: {
alias: {
'@': resolve(__dirname, '/src/types'),
},
},
最後に
これでようやく完成:
import { resolve } from 'path';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import viteTsconfigPaths from 'vite-tsconfig-paths';
import GlobalsPolyfills from '@esbuild-plugins/node-globals-polyfill';
import rollupNodePolyFill from 'rollup-plugin-node-polyfills';
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, '/src'),
},
},
plugins: [react(), viteTsconfigPaths()],
build: {
outDir: 'build',
rollupOptions:{
plugins: [rollupNodePolyFill()]
}
},
server: {
open: true,
host: '0.0.0.0',
port: 3000, // docker container port
hmr: {
port: 3000, // host machine port mapping
},
watch: {
usePolling: true,
},
},
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis',
},
plugins: [GlobalsPolyfills({ process: true, buffer: true })],
},
},
});
今回の導入に置いて、一番予想外の問題はポリフィルのところでした。CRAはすでに揃えているので、気づいていませんでしたが、最初にBuffer is undefined
を見たときに???との気持ちでした。公式のドキュメントにも追加して欲しい内容ですね。
導入後の初回ロード以外、devサーバーの起動、ホットリロードなどが爆速になっているので、CRAよりはだいぶ快適になっています。
ではでは、今回はこれで。
Discussion
CRAでdevサーバーの起動に2-3分かかっていたのがViteを使った結果何秒くらいになったのでしょうか
コメントありがとうございます!
CRAを使う場合、localhostが立ち上がるまで3分弱かかっていました。VITEでもしlocalhost立ち上がりだけであれば、10秒以内がほとんどですが、初回ロード時に時間がかかるので、結局ページがレンダーリングされるまでに40-60秒はかかります。
ただ、VITEのこの初回ロードはキャッシングされるので、それ以降はdocker compose down -> compose upしても、次回からは10秒程度で画面がレンダーリングされます。CRAの場合は毎回3分弱なのでここはだいぶ差が出てきますね。
しっかりとベンチマークとか測っていないのですが、ご参考になれると嬉しいです。