create-react-app から Vite への移行
概要
担当プロジェクトでは基本モブプログラミングに取り組んでいるのですが、必要技術の研究や個人でやる方が向く作業用の時間を取っており、その時間を使って React の初期構築周りの知識習得も兼ねて create-react-app で作成したプロジェクトを Vite へ移行する作業を行いました。
参考にした記事とは React のバージョンなど異なる点もありましたので参考になる人もいるかと思い行った作業についてまとめ共有したいと思います。
参考
-
Vite
- Vite 公式
-
CRAからViteへ移行して190倍高速なdev server起動を得る - inSmartBank
- Vite がどういったものかと移行作業が参考リンクも含めて一番まとまっていた
環境
- React: 17.0.2
- craco: 6.4.3
- typescript: 4.5.5
- node.js: 16.14.2
Vite への移行手順
パッケージ追加と初期設定
Vite と Vite で React を動かすのに必要なパッケージを追加します。
yarn add --dev vite @vitejs/plugin-react
package.json の起動コマンドを変更します。引数なしの vite
が開発サーバーの起動コマンドです。Vite は build 時に型検査を行わないので build 前に tsc --noEmit
を追加して別途型検査を行うようにしています。
// package.json
"scripts": {
- "start": "craco start",
- "build": "craco build",
+ "start": "vite",
+ "build": "tsc --noEmit && vite build",
+ "serve": "vite preview",
"test": "craco test",
}
"devDependencies": {
...
+ "@vitejs/plugin-react": "^1.3.2",
...
+ "vite": "^2.9.8"
},
プロジェクトルートに Vite の設定ファイル vite.config.ts を以下の内容で作成します。React のプラグインロードと CRA と同じように起動時にブラウザを開くようにしています。
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
server: {
open: true
},
plugins: [react()]
})
index.html の Vite 対応
index.html
はプロジェクトルートに置く必要があるので public/index.html
から移動させ、中身も Vite に対応できるよう以下の修正を加えます。
-
%PUBLIC_URL%
を削除する- Vite は絶対パスはプロジェクトルートを基底に置き換える
-
public
ディレクトリにある静的アセットも/
でアクセスできる
- React のエントリーポイントとなる index.tsx を script タグで指定する
// index.html
<head>
<meta charset="utf-8" />
- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+ <link rel="icon" href="/favicon.ico" />
- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+ <link rel="apple-touch-icon" href="/logo192.png" />
- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+ <link rel="manifest" href="/manifest.json" />
- <!--
- Notice the use of %PUBLIC_URL% in the tags above.
- It will be replaced with the URL of the `public` folder during the build.
- Only files inside the `public` folder can be referenced from the HTML.
-
- Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
- work correctly both with client-side routing and a non-root public URL.
- Learn how to configure a non-root public URL by running `npm run build`.
- -->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
- <!--
- This HTML file is a template.
- If you open it directly in the browser, you will see an empty page.
-
- You can add webfonts, meta tags, or analytics to this file.
- The build step will place the bundled scripts into the <body> tag.
-
- To begin the development, run `npm start` or `yarn start`.
- To create a production bundle, use `npm run build` or `yarn build`.
- -->
+ <script type="module" src="/src/index.tsx"></script>
</body>
</html>
リセットCSSの適用方法の変更
index.css
だとパスが解決できずにエラーとなったので index.tsx
に import を移動しました。恐らく設定で解決できると思うのですが css での import にこだわる理由もないので。
// index.css
-@import "~sanitize.css";
-@import "~sanitize.css/forms.css";
-@import "~sanitize.css/typography.css";
// index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
+import 'sanitize.css'
+import 'sanitize.css/forms.css'
+import 'sanitize.css/typography.css'
import './index.css'
import App from './App'
環境変数の Vite 対応
必要な作業は以下の 3 点です
- 環境変数の prefix を
REACT_APP_
からVITE_
に変更する -
src/react-app-env.d.ts
をsrc/vite-env.d.ts
にリネームし参照先を変える- Vite がこのファイル名で特別処理をしているわけではないので別名・別場所でも大丈夫です
- コードで参照する際の
process.env
をimport.meta.env
に変更する
src/vite-app-env.d.ts
の内容は以下の通りでこれを行わないとimport.meta.env
の型の解決負荷エラーが出ます。
// src/vite-app-env.d.ts
-/// <reference types="react-scripts" />
+/// <reference types="vite/client" />
コード上の変更例です。
-const port = process.env.REACT_APP_PORT
+const port = import.meta.env.VITE_PORT
tsconfig.json の baseUrl 対応
aleclarson/vite-tsconfig-paths: Support for TypeScript's path mapping in Vite
vite-tsconfig-paths は tsconfig.json の allowJs, baseUrl, include/exclude の設定を Vite にも反映してくれる plugin です。移行したプロジェクトでは baseUrl を設定していたので導入しました。
yarn add --dev vite-tsconfig-paths
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
server: {
open: true
},
- plugins: [react()]
+ plugins: [react(), tsconfigPaths()]
})
動作確認
ここまでの作業で Vite による開発サーバーの起動やビルドなどを試したところアプリも問題なく動作したのでアプリの動作周りに関しては移行作業が完了です。ただこのままだと Jest が CRA に依存した状態なので続いてそちらの移行作業を行います。
Jest の CRA 依存を外す
-
yarn eject
を実行して内部の設定やスクリプトを出力する -
package.json
に出力された babel と jest の設定をファイル化する -
package.json
に出力された本来はdevDependencies
にあるべきパッケージを移動する -
package.json
の test コマンドを"test": "jest",
に変更する - eject されたパッケージ・設定・スクリプトを読み解き必要なものを残す
eject されたファイルの中身など全て書き出すと長くなり過ぎるので要点を絞って順番に説明します。
1. yarn eject を実行して内部の設定やスクリプトを出力する
react-scripts
yarn eject
で出力される設定やスクリプトは react-scripts
パッケージの中身をフィルタリングして出力したものなので、プロジェクトに出力せずに参考にしたい場合はリポジトリ上のコードを直接参考にすることもできます。その際はプロジェクトで使ってるバージョンのタグを探してください。@remove-on-eject-begin
と @remove-on-eject-end
で囲まれたコードは出力時に除外される部分です。
2. package.json に出力された babel と jest の設定をファイル化する
package.json が長くなり過ぎるし専用のファイルにあった方が扱いやすいので個別のファイルにわけました。
3. package.json に出力された本来は devDependencies にあるべきパッケージを移動する
react-scripts の package.json の内容が反映される影響で jest や @types 系のパッケージが dependencies に定義されてしまうので devDependencies に移動しました。後述しますが jest に必要ないとわかっているパッケージがあれば、この時点で削除しても良いと思います。
4. package.json の test コマンドを "test": "jest", に変更する
// package.json
- "test": "craco test",
+ "test": "jest",
5. eject されたパッケージ・設定・スクリプトを読み解き必要なものを残す
eject される jest の設定の transform の部分が以下のようになっています。
// jest.config.ts
transform: {
'^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': '<rootDir>/config/jest/babelTransform.js',
'^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
'^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': '<rootDir>/config/jest/fileTransform.js'
},
最初の babelTransform.js
の内容は babel-jest の設定を生成して実行するものになっています。行っている処理は React 17 の新しい JSX トランスフォームを利用するかを判定して babel-preset-react-app の runtime オプションを 'automatic' と 'classic' のどちらにするかの切り替えと babel.config.json や .babelrc といった設定ファイルの無効化です。どちらの処理不要でしたので babel-jest を直接呼び出す形式に変更しました。
// jest.config.ts
transform: {
- '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': '<rootDir>/config/jest/babelTransform.js',
+ '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': 'babel-jest',
...
},
Jest は Vite の import.meta.env
での環境変数へのアクセスに対応していないので babel で変換する必要があり、その為の babel-preset-vite というプラグインを追加します。
yarn add --dev babel-preset-vite
babelTransform.js
の内容も合わせた最終的な babel の設定がこちらです。
// babel.config.json
{
"presets": [
["react-app", { "runtime": "automatic" }],
"babel-preset-vite"
]
}
出力された Jest の設定では dotenv が反映されていなかったので設定を追記します。
// jest.config.ts
setupFiles: [
+ 'dotenv/config',
'react-app-polyfill/jsdom'
],
ここまででテストが通るようになったので他の transform の内容も確認したところ利用していないものでしたので、最終的に Jest の transform の設定はデフォルト値で十分ということがわかり項目ごと削除しました。
// jest.config.ts
- transform: {
- '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': 'babel-jest',
- '^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
- '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': '<rootDir>/config/jest/fileTransform.js'
- },
transform 以外の設定も <rootDir>/src
以下が対象となるように明示的にしたものが多くデフォルトでも問題はなさそうでしたが、現在はそのままで影響を把握できたら徐々に変更する予定です。
Vite も Jest も問題なく動作する状態になりましたので不要そうなパッケージを順次削除していき最終的な差分が以下の通りです。
"dependencies": {
- "@craco/craco": "^6.4.3",
+ "dotenv": "^10.0.0",
+ "dotenv-expand": "^5.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
...
},
...
"devDependencies": {
+ "@babel/core": "^7.17.10",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
...
"@vitejs/plugin-react": "^1.3.2",
+ "babel-jest": "^28.1.0",
+ "babel-loader": "^8.2.3",
+ "babel-plugin-named-asset-import": "^0.3.8",
+ "babel-preset-react-app": "^10.0.1",
+ "babel-preset-vite": "^1.0.4",
...
+ "jest": "^27.4.3",
+ "jest-watch-typeahead": "^1.0.0",
+ "ts-node": "^10.7.0",
"typescript": "^4.6.4",
"vite": "^2.9.8"
その他
- ESLint は CRA に依存してなかったので影響なしでした
- JavaScript/TypeScript/React 経験が浅かったので CRA の内部の処理や設定が知れて勉強になりました
- プロジェクト初期なので感じた速さの恩恵は少なめでした
- テストの実行時間は 5s ちょいから 2s ちょいと半減した
Discussion
しばらく前に参考にさせていただいたのですが、viteを使って色々触っていると
vite-app-env.d.ts
ではなく、vite-env.d.ts
の方が正しそうに感じます。コメントありがとうございます。
Vite のドキュメントには確かに
vite-env.d.ts
と書かれているのですが、自分の知る限りでは Vite はこのファイル名のファイルに対して特別何か処理を行っているわけではありません。恐らくcreate-vite
のテンプレートがsrc/vite-env.d.ts
になっているので、そこも考慮した説明としてこのファイル名及び場所が使われているのだと思います。この型定義ファイルは TypeScript の為に用意するのですが、TypeScript は
tsconfig.json
のfiles
やincludes
などのオプションでコンパイル対象に含まれている.d.ts
の型定義ファイルを自動的に認識してくれるので、実はファイル名はなんでもよかったりします。ちなみに外部パッケージの型定義ファイルについてはまたちょっと事情が違うのですが説明すると長くなるのでここでは割愛させて頂きます。この記事を書いた当時はそこまで知識がなかった事から参考にさせて頂いた記事の通りにしたのですが、公式ドキュメント通りの方が余計な混乱がなくて良さそうにも感じたので訂正とコメントを添えておこうと思います。
改めてご指摘ありがとうございました!
言われてみればそうですね。勉強になります。
ありがとうございます!