前回: CRAからViteへの移行 - テスト移行編 - harryのブログ
前書き
前回create-react-app(CRA)に依存せずjest単体でテストできるようにしました。
今回から本格的にVite移行を進めます。参考にしたのは以下の記事や公式ドキュメントです。
移行前環境
- CRA: 5.0.1
- React: 18.2.0
- TypeScript: 5.3.3
- jest: 29.7.0
- ESLint: 8.56.0
ビルド環境
- Node.js: 20系
デプロイ先
- GitHub pages
移行作業
手順
基本的な流れは以下の通り。
- 既存設定の見直し
- CRAアンインストール
- ESLint plugin アップデート
- Vite導入
- 個別のエラー対応
既存設定の見直し
こんなタイミングでもないと見直さないので、Vite移行とは無関係に直せそうなところを直しておきます。
tsconfig.json
の target
.eslintrc.json
の env が "es2021"
になってたり、tsconfig.json
の target が "es2020"
になってたり微妙だったので、"es2021"
に統一。
自分のコードではURL safe base64の変換時に String.prototype.replaceAll()
を使用しているので、ES2021以降のfeatureが必要ですが、Can I use を見る限り、もう target は ES2021 で問題ないとの判断。
src/service/UrlParamConverter.ts:19:6 - error TS2550: Property 'replaceAll' does not exist on type 'string'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2021' or later. 19 .replaceAll('+', '-') ~~~~~~~~~~ src/service/UrlParamConverter.ts:30:8 - error TS2551: Property 'replaceAll' does not exist on type 'UrlSafeBase64String'. Did you mean 'replace'? 30 .replaceAll('-', '+') ~~~~~~~~~~
lint script修正
なんか eslint のコマンドが微妙だったので修正。Viteのサンプルプロジェクトからパクったをインスパイアしました。
package.json
- "lint": "eslint ./src/**/*.{ts,tsx} --max-warnings=0", - "lint:fix": "eslint --fix ./src/**/*.{ts,tsx}" + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings=0", + "lint:fix": "eslint . --ext ts,tsx --fix"
--report-unused-disable-directives
によってエラーになった箇所も一緒に修正した。*1
CRAアンインストール
後で必要なライブラリをインストールする際にエラーが出るので、先にアンインストールしておきます。そういうとこやぞ、CRA。
> npm rm -D react-scripts removed 820 packages, and audited 965 packages in 14s 138 packages are looking for funding run `npm fund` for details found 0 vulnerabilities
ばいばい create-react-app!
100年振りに found 0 vulnerabilities
になりました。
CRA削除したので、 babel.config.js
から 'react-app'
を削除します。なぜ前回のテスト移行で追加してしまったのか、コレガワカラナイ。
babel.config.js
presets: [
- 'react-app',
'@babel/preset-env',
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript'
],
ESLint configのCRA依存の設定も不要になったので削除。
package.json
- "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - },
また、以下のissueのために追加してた overrides
も不要になったので削除。
(react-scripts) Support for TypeScript 5.x · Issue #13080 · facebook/create-react-app · GitHub
package.json
- }, - "overrides": { - "typescript": "^5.3.3" }
何故か dependencies
にいる @types/node
もCRA由来で入ってた可能性があるので消しておきます。
npm rm @types/node
そしてCRAに入ってたnanoidは一緒に消えてしまったので、改めてインストールします。*2
npm i nanoid
この時点でちゃんとテスト(≒ CI)が通ることを確認しておきます。CDは浜で死にました。
> npm run test > last-origin-unit-viewer@1.2.21 test > jest PASS src/data/unitBasicData.test.ts PASS src/domain/skill/SkillAreaOfEffectMatcher.test.ts PASS src/data/equipmentData.test.ts PASS src/App.test.tsx Test Suites: 4 passed, 4 total Tests: 103 passed, 103 total Snapshots: 0 total Time: 5.622 s Ran all test suites.
> npm run lint > last-origin-unit-viewer@1.2.21 lint > eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings=0 ============= WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree. You may find that it works just fine, or you may not. SUPPORTED TYPESCRIPT VERSIONS: >=3.3.1 <5.2.0 YOUR TYPESCRIPT VERSION: 5.3.3 Please only submit bug reports when using the officially supported version. =============
ESLint plugin アップデート
CRAを消し去った所で、npm run lint
実行時に出ている警告が気になるので、ESLint plugin をアップデート。
npm i -D @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
アップデート後に実行してみると、警告は消えましたが代わりにエラーが出ました。
> npm run lint > last-origin-unit-viewer@1.2.21 lint > eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings=0 D:\_git\last-origin-unit-viewer\src\state\squad\SquadHook.ts 254:9 error Unused eslint-disable directive (no problems were reported from '@typescript-eslint/no-non-null-assertion') ✖ 1 problem (1 error, 0 warnings) 1 error and 0 warnings potentially fixable with the `--fix` option.
これは、URLコピー機能に使用している inputRef.current!.select();
などに対してエラーを抑制していた所で*3、抑制が不要になったようなので、eslint-disable
と eslint-enable
のコメント行を削除しました。
Vite導入
準備が整ったのでViteを導入していきます。
Viteには各種テンプレートとそれを試せるオンラインエディタが準備されているので、それを参考に作業を進めます。
手順は以下の通りです。
- 必須ライブラリのインストール
- 設定ファイル配置/変更
- index.htmlの移動と編集
- 環境変数対応
必須ライブラリのインストール
今回は react-ts
テンプレートを参考に、必須となる vite
と @vitejs/plugin-react
をインストールします。
npm i -D vite @vitejs/plugin-react
設定ファイル配置/変更
設定ファイルの配置
ドキュメントやテンプレートを参考に、以下の設定ファイルを追加します。
vite.config.ts
src/vite-app-env.d.ts
- 既存の
src/react-app-env.d.ts
を変更
- 既存の
vite.config.ts
デプロイ先がGitHub pagesなので base
を指定する必要があります。この値は import.meta.env.BASE_URL
で参照可能です。
import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], base: '/last-origin-unit-viewer/' });
src/react-app-env.d.ts -> src/vite-app-env.d.ts
- /// <reference types="react-scripts" /> + /// <reference types="vite/client" />
設定ファイルの変更
package.json
の scripts
を以下のように変更します。
package.json
"scripts": { - "start": "react-scripts start", - "build": "react-scripts build", + "dev": "vite", + "build": "tsc && vite build", "test": "jest", - "eject": "react-scripts eject", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings=0", - "lint:fix": "eslint . --ext ts,tsx --fix" + "lint:fix": "eslint . --ext ts,tsx --fix", + "preview": "vite preview" },
特にこだわりはないのでテンプレに準拠しますが、test
と build
に変更はないので、CI/CDのコマンド変更は不要でした。
また、Viteではビルド後のファイルが dist
ディレクトリに格納されるので、参照してた箇所を変更します。
.gitignore
# production - /build + /dist
.github/workflows/gh-pages.yml
- name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./build + publish_dir: ./dist
index.htmlの移動と編集
index.htmlは色々対応が必要です。
1. index.htmlをrootディレクトリに移動
Viteではindex.htmlはrootディレクトリに配置するようなので移動します。
public/index.html
-> index.html
2. index.tsxをscriptタグで追加
テンプレートのプロジェクトと同様、#root
のdiv要素直下に追加します。CRAの index.tsx
はテンプレートに合わせて main.tsx
に変更してもいいと思います。*4
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
+ <script type="module" src="/src/index.tsx"></script>
</body>
3. 絶対パス指定のhref属性から %PUBLIC_URL%
を削除
前書きで紹介した参考記事でも言及されている通り、public
ディレクトリに配置されたassetはルートパス /
から提供されるので、%PUBLIC_URL%
が不要になります。
- 具体例:
href="%PUBLIC_URL%/manifest.json"
->href="/manifest.json"
4. URLで使用している %PUBLIC_URL%
を %BASE_URL%
に変更
元々 og:image
などのURLに %PUBLIC_URL%
を使用していたため、これを %BASE_URL%
に変更します。
BASE_URL
はShared Optionsの base
に設定した値のため、両端に /
が含まれていることに注意します。
- 具体例:
content="https://harry0000.github.io%PUBLIC_URL%/"
->content="https://harry0000.github.io%BASE_URL%"
アプリのURLはそもそも変更することがないので、元々 index.html
にハードコーディングしています。
環境変数対応
コード中の process.env
から参照している環境変数には undefined
が格納されるため、すべて書き換えていきます。
1. process.env.PUBLIC_URL
を import.meta.env.BASE_URL
に変更
index.htmlのhref属性などはルートディレクトリからのパスを書いておけばビルド時に変換が行われますが、コードで動的に生成してるassetのURLなどは変換が行われません。
基本的には、process.env.PUBLIC_URL
を import.meta.env.BASE_URL
に置換すればよいのですが、ここでも BASE_URL
の両端に /
が含まれていることに注意します。
2. REACT_APP
環境変数を変更
Viteでは envPrefix
のデフォルト値が VITE_
になっていて、このprefixから始まる環境変数が meta.env
に自動で読み込まれます。
そのため、REACT_APP_
prefixで設定していた環境変数を VITE_
prefix に書き換えていきます。
もちろん、CDで行っているbuildの環境変数も変更します。
だからGitHubに登録したsecretsにはツール固有のprefixを付けないようにする必要があったんですね(0敗)。
.github/workflows/gh-pages.yml
- name: Build env: - REACT_APP_FIREBASE_WEB_API_KEY: ${{ secrets.FIREBASE_WEB_API_KEY }} - REACT_APP_GA_MEASUREMENT_ID: ${{ secrets.GA_MEASUREMENT_ID }} + VITE_FIREBASE_WEB_API_KEY: ${{ secrets.FIREBASE_WEB_API_KEY }} + VITE_GA_MEASUREMENT_ID: ${{ secrets.GA_MEASUREMENT_ID }} run: npm run build
個別のエラー対応
やっとViteの導入が一通り終わったので、ビルドしてみて出たエラーに対処していきます。
SVG ファイル
ビルドしてみるとSVGファイルをコンポーネントとしてimportしている箇所でエラーとなりました。
> npm run build > last-origin-unit-viewer@1.2.21 build > tsc && vite build src/component/squad/SquadShareModal.tsx:10:10 - error TS2614: Module '"*.svg"' has no exported member 'ReactComponent'. Did you mean to use 'import ReactComponent from "*.svg"' instead? 10 import { ReactComponent as TwitterSocialIcon } from '../icon/TwitterSocialIcon.svg'; ~~~~~~~~~~~~~~ Found 1 error in src/component/squad/SquadShareModal.tsx:10
ViteではSVGをコンポーネントとして使うことを推奨していないようです。
- Don't transform SVGs into UI framework components (React, Vue, etc). Import them as strings or URLs instead.
ですが、
ので、エラーになってしまうと少し困ります。
そこでSVG画像をReactコンポーネントとしてimportできるようにする vite-plugin-svgr
を導入しました。
導入方法はREADME.mdに書かれている通りで、変更箇所などは以下の通りです。
npm i -D vite-plugin-svgr
vite.config.ts
import react from '@vitejs/plugin-react'; + import svgr from 'vite-plugin-svgr'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), svgr()], base: '/last-origin-unit-viewer/' });
src/vite-app-env.d.ts
/// <reference types="vite/client" />
+ /// <reference types="vite-plugin-svgr/client" />
src\component\squad\SquadShareModal.tsx
- import { ReactComponent as TwitterSocialIcon } from '../icon/TwitterSocialIcon.svg'; + import TwitterSocialIcon from '../icon/TwitterSocialIcon.svg?react';
ただし、この変更によりSVGのpathが微妙に変わってしまったので、jestの設定も変更しておきます。
jest.config.js
moduleNameMapper: { '\\.css$': 'identity-obj-proxy', - '\\.svg$': '<rootDir>/src/__mock__/SvgMock.tsx' + '\\.svg?(\\?react)$': '<rootDir>/src/__mock__/SvgMock.tsx' },
とりあえずビルドは通る様になりましたね!初回のビルドで16.52sは爆速です!
> npm run build > last-origin-unit-viewer@1.2.21 build > tsc && vite build The CJS build of Vite's Node API is deprecated. See https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details. vite v5.0.12 building for production... ✓ 662 modules transformed. dist/index.html 1.72 kB │ gzip: 0.80 kB dist/assets/UnitSkillList-8zQ9gu3V.css 0.69 kB │ gzip: 0.24 kB dist/assets/CoreLinkSelector-Ujaa1XhX.css 1.12 kB │ gzip: 0.37 kB dist/assets/SquadUnitStatusParameterTabPane-UCPTJ5C0.css 2.10 kB │ gzip: 0.66 kB dist/assets/EquipmentSelector-MCQdzsnC.css 4.05 kB │ gzip: 1.11 kB dist/assets/index-N5thVY8F.css 175.86 kB │ gzip: 27.21 kB dist/assets/SlotUnavailableOverlay-zgmMeYri.js 0.44 kB │ gzip: 0.33 kB dist/assets/Col-6c_Lo0lY.js 0.68 kB │ gzip: 0.46 kB dist/assets/FullLinkBonusDropdown-AgXuAz8M.js 1.64 kB │ gzip: 0.68 kB dist/assets/CoreLinkSelector-EyLr-jVq.js 3.48 kB │ gzip: 1.35 kB dist/assets/SquadUnitStatusParameterTabPane-GbDIXrvO.js 5.47 kB │ gzip: 1.92 kB dist/assets/EquipmentSelector-VEx41JlM.js 6.50 kB │ gzip: 2.28 kB dist/assets/SquadShareModal-b1nutW_c.js 23.97 kB │ gzip: 8.39 kB dist/assets/UnitSkillList-oQqVaHuM.js 29.95 kB │ gzip: 8.52 kB dist/assets/index-DA70HBsk.js 2,003.55 kB │ gzip: 346.85 kB (!) Some chunks are larger than 500 kB after minification. Consider: - Using dynamic import() to code-split the application - Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit. ✓ built in 16.52s
jestのエラー対応
ビルドが通ったのは良いのですが、またjest殿が死んでおられるぞ!という状況になってしまいました。
Details: D:\_git\last-origin-unit-viewer\src\service\ShareUrlGenerator.ts:18 var appSiteUrl = new URL(import.meta.env.BASE_URL, 'https://harry0000.github.io').toString(); ^^^^ SyntaxError: Cannot use 'import.meta' outside a module
このエラーの原因は前書きの参考リンクでも書かれている通り、import.meta
に対してBabelのトランスパイルが必要なためです。babel-preset-viteをインストールしましょう。
npm i -D babel-preset-vite
babel.config.js
presets: [ '@babel/preset-env', ['@babel/preset-react', { runtime: 'automatic' }], - '@babel/preset-typescript' + '@babel/preset-typescript', + 'babel-preset-vite' ],
またjestのテストが生き返りました。
> npm run test > last-origin-unit-viewer@1.2.21 test > jest PASS src/data/unitBasicData.test.ts PASS src/domain/skill/SkillAreaOfEffectMatcher.test.ts PASS src/data/equipmentData.test.ts PASS src/App.test.tsx (13.114 s) Test Suites: 4 passed, 4 total Tests: 103 passed, 103 total Snapshots: 0 total Time: 13.95 s, estimated 15 s Ran all test suites.
ビルド時間の比較
Vite移行後恒例のビルド時間比較のお時間です。
command | CRA | Vite | |
---|---|---|---|
npm i |
60 s *5 | 52 s *6 | 1.15 倍 |
npm run start (初回) |
59820 ms | 446 ms | 134.12 倍 |
npm run start (2回目) |
6500 ms | 288 ms | 22.57 倍 |
npm run build |
37.49 s | 16.52 s | 2.27 倍 |
※ Viteの開発サーバー起動のcommandは npm run dev
です
開発サーバーの初回起動がちょっとおかしいレベルで早くなってますね。最初 400 ms で起動してしまい、もう一度計り直した値を記載しましたが、それでも爆速です。今まで一体どれだけの時間を無駄にしていたんだ…。
Vite移行が完了して
Vite移行もなんやかんやでものすごい色々作業があって時間がかかりました。移行後に改めて npm outdated
で各種依存ライブラリを最新化した方が良いでしょう。
ただ時間がかかった分、移行したメリットはあったと思います。ついでに設定ファイルなど色々整理することもできました。
一旦移行が終わって満足しましたが、次回があるとすれば、jestからVitestへの移行かもしれません。
オレはようやくのぼりはじめたばかりだからな このはてしなく遠いVite坂をよ…