RemitAid Tech Blog
🌅

Sunsetting Create React App, and sunrise Vite.

に公開

こんにちは!
RemitAid CTOの@iTakacです。

約4ヶ月前から始めたRemitAid Tech Blog
https://zenn.dev/remitaid/articles/266417e6d1d08d

当番制でコツコツ毎週公開してきましたが、いよいよネタが枯渇し始め、どうしようかなーと思っていた矢先、Linc'well,inc.さんのテックブログでCreate React AppからViteへの移行のお話が公開されました。

https://zenn.dev/lincwell_inc/articles/migrate-cra-to-vite

弊社でもCreate React Appのdeprecatedに伴って、ビルドツールを選定、移行せねばなーとぼんやり思っていながら、日々の機能開発に追われ後回しになっていました。

テックブログのネタにもなるし、必要性に迫られているし、この際やっちまおう!ということで今回弊社でもVite移行に踏み切りました。

今回はそんなテックブログドリブンなシステムの保守活動をお届けできればと思います笑

Create React Appはdeprecatedしました

https://react.dev/blog/2025/02/14/sunsetting-create-react-app

Reactの開発に携わったことのある開発者であれば、必ずと言っていいほどお目にかかったことはあるほど便利なツールでした。
WebpackやBabelの細かい設定を隠蔽して、コマンド一発でReactアプリケーションを起動できる手軽さが、Reactとそのエコシステムの普及に貢献したと筆者は考えています。
そのような発展に寄与した立役者が今、大きな役目を終えようとしています。

Viteの選定理由

これは第一想起です。すみません。それしかありません笑
日常から流れてくるSNSタイムラインや勉強会、カンファレンスなどの情報源においてCRAの次はViteという第一想起が自分の選定を後押ししていることは間違いありません。
(プロダクトを開発運営している法人としては第一想起の威力を実感させられ、今後のマーケティング、プロモーション戦略の参考になりました)

移行 with AI

ここからは具体的なexecutionについて解説します。
この手のタスクはAIの方が絶対に得意だろうという勘が働いたので、雑なプロンプトで修正計画を提案してもらいました。

Create React Appのdeprecatedに伴い、ビルドを切り替える必要があるので
要件と切り替え手順を提案してください。ultrathink

(どうでもいいですが、私が2025年最もタイピングした単語はultrathinkです笑)

最初はViteの指定もしませんでしたが、LLMの第一想起もとれているのかViteを推奨してきました。
パッケージのインストールから環境変数のプレフィックス変更、Viteコマンドへの移行などステップバイステップで細かい計画をアウトプットしてくれました。

フェーズ1: パッケージ管理

  - npmキャッシュの権限問題に遭遇 → 一時キャッシュディレクトリで解決
  - Vite、@vitejs/plugin-react、vite-plugin-svgrをインストール
  - react-scripts(701パッケージ)を削除

  フェーズ2: Vite設定

  - vite.config.tsを作成
  - React、SVGサポート、環境変数の設定

  フェーズ3: HTMLファイル

  - public/index.html → プロジェクトルートの index.html に移動
  - %PUBLIC_URL%を削除
  - Viteのエントリーポイント<script type="module" src="/src/index.tsx">を追加

  フェーズ4: 環境変数

  - 5つの.env.*ファイル: REACT_APP_* → VITE_* に変更
  - 6つのソースファイル: process.env.REACT_APP_* → import.meta.env.VITE_*

  フェーズ5: package.jsonスクリプト

  - すべてのscriptsをViteコマンドに変更
  - react-scripts start → vite
  - react-scripts build → vite build

  フェーズ6: TypeScript設定

  - tsconfig.jsonをVite向けに更新(moduleResolution: "bundler"など)
  - tsconfig.node.jsonを作成
  - src/vite-env.d.tsで環境変数の型定義を追加

  フェーズ7: MSW設定

  - 既存の設定で動作確認(問題なし)

あとは承認して、淡々と修正作業を任せました。
実際の作業時間は15分程度だったと思います。人間だったらありえない速度ですね。さすがです。

移行中に個別具体的に対処したこと

とはいえ、生成AIが完成した成果物がそのまま動かないのは、周知の事実。
このプロジェクトでも同様に動かない部分を細かく微調整したのでいくつかピックアップします。

ビルドコマンドの調整

CRAでは以下のようにビルドコマンドをエイリアスしていました。

package.json(CRA)
  ...(略)
  "scripts": {
    "start": "react-scripts start",
    "start:mock": "dotenv -e .env.mock react-scripts start",       // MSWで起動
    "start:local": "dotenv -e .env.local react-scripts start",     // localで立ち上げたAPIサーバーと接続する
    "start:dev": "dotenv -e .env.development react-scripts start",
    "start:prd": "dotenv -e .env.production react-scripts start",
    "build:mock": "dotenv -e .env.mock react-scripts build",
    "build:local": "dotenv -e .env.local react-scripts build",
    "build:dev": "dotenv -e .env.development react-scripts build",  // dev環境にデプロイするときに利用
    "build:prd": "dotenv -e .env.production react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "format": "prettier --write './src/**/*.{js,ts,tsx}'",
    "format:check": "prettier --check './src/**/*.{js,ts,tsx}'",
    "lint": "eslint './src/**/*.{js,ts,tsx}'",
    "lint:fix": "eslint './src/**/*.{js,ts,tsx}' --fix",
    "deadcode": "ts-prune -u",
    "deadcode:count": "ts-prune | wc -l"
  },
  ...(略)

Vite用に以下のようにAIによって修正されました。

package.json(Vite)
"scripts": {
    "start": "vite",
    "start:mock": "vite --mode mock",               // MSWで起動
    "start:local": "vite --mode local",             // localで立ち上げたAPIサーバーと接続する
    "start:dev": "vite --mode development",
    "start:prd": "vite --mode production",
    "build:mock": "vite build --mode mock",
    "build:local": "vite build",
    "build:dev": "vite build --mode development",   // dev環境にデプロイするときに利用
    "build:prd": "vite build --mode production",
    "preview": "vite preview",
    "test": "vitest",
    "format": "prettier --write './src/**/*.{js,ts,tsx}'",
    "format:check": "prettier --check './src/**/*.{js,ts,tsx}'",
    "lint": "eslint './src/**/*.{js,ts,tsx}'",
    "lint:fix": "eslint './src/**/*.{js,ts,tsx}' --fix",
    "deadcode": "ts-prune -u",
    "deadcode:count": "ts-prune | wc -l"
  },

ところがViteはいかなる場合でも.env,.env.localは読み込む仕様かつ、特定モードを指定する場合は、.env.[mode]が読み込まれる仕様になっていました。
https://ja.vite.dev/guide/env-and-mode.html#env-files

従って上記のstart:localの場合、.env.localが競合するためうまくビルドできません。
start:localエイリアスはデフォルトであるdevelopmentモードで起動させるため、viteとシンプルにし、.env.development.localの環境変数ファイルを用意することで対処しました。

globalオブジェクトの除去

Create React Appが使用しているwebpackでは、Node.js環境のglobalオブジェクトが
ブラウザ環境でも使えるようにポリフィルが提供されます。
https://gist.github.com/ef4/d2cf5672a93cf241fd47c020b9b3066a#polyfilling-globals

従って例えばクリップボードにコピーなどの機能で以下のような実装をしていましたが、
Viteではglobalオブジェクトのポリフィルが提供されないため、この実装が動かなくなりました。
そこで、globalオブジェクトへの参照を削除しました。

  const copyToClipBoard = useCallback(() => {
-    global.navigator.clipboard.writeText(handleUrl(GetUrlData()?.url))
+   navigator.clipboard.writeText(handleUrl(GetUrlData()?.url))
    onCopied(true)
    setTimeout(function () {
      onCopied(false)
    }, 5000)
  }, [])

CRA時代の依存パッケージのクリーンアップ

react-scriptはもちろん、webpackに代表される複数のパッケージが不要になったため、package.jsonを修正しました。

-  "eslintConfig": {
-    "extends": [
-      "react-app",
-      "react-app/jest"
-    ]
-  },
-  "browserslist": {
-    "production": [
-      ">0.2%",
-      "not dead",
-      "not op_mini all"
-    ],
-    "development": [
-      "last 1 chrome version",
-      "last 1 firefox version",
-      "last 1 safari version"
-    ]
-  },
  "devDependencies": {
-    "@types/jest": "^27.0.3",
    "@types/lodash": "^4.17.13",
    "@types/node": "^22.16.4",
    "@types/papaparse": "^5.3.14",
@@ -92,22 +73,17 @@
    "@types/react-router-dom": "^5.3.2",
    "@typescript-eslint/eslint-plugin": "^5.5.0",
    "@typescript-eslint/parser": "^5.62.0",
-    "dotenv-cli": "^4.1.1",
+    "@vitejs/plugin-react": "^5.0.4",
    "eslint": "^8.4.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.33.2",
-   "html-webpack-plugin": "^5.5.0",
-   "jest": "^27.4.3",
-   "json-loader": "^0.5.7",
    "msw": "^2.2.8",
-    "node-sass": "^7.0.1",
    "prettier": "^2.5.1",
-    "react-scripts": "^5.0.0",
-    "ts-jest": "^27.1.0",
    "ts-prune": "^0.10.3",
    "typescript": "^4.9.5",
-    "webpack": "^5.88.1"
+    "vite": "^7.1.11",
+    "vite-plugin-svgr": "^4.5.0"
  }

移行を終えて

まずはdeprecatedになった仕組みを無事に本番環境に適用できたことで集中してプロダクト開発ができるようになり、安心しました。
副次的ではありますが、以下のような効果があったことも個人的にはポジティブに捉えています。

ビルド時間の短縮

弊社はAWS Amplifyにフロントエンドアプリケーションをホスティングしています。
依存パッケージをクリーンアップしたことで、ビルドにかかる時間がほぼ半分になりました。

7分超から3分台に

検証時間の短縮

今回の移行ではAIエージェントをフル活用しました。

アプリケーション全体に影響を与える大規模な移行では、通常、技術選定や検証を念入りに行い、
リスクを認識した上で実行します。そのためには調査と検証のプロセスを繰り返し、
最終的な選択肢を絞り込むケースがほとんどです。

Create React Appの移行先もViteだけではなく、RsbuildやParcelなど、
複数の選択肢が公式のマイグレーションガイドを提供しています。

今回は実施しませんでしたが、AIエージェントを活用することで以下のような検証プロセスが可能になると感じました:

  1. 複数のAIエージェントに並行して3つの移行先(Vite、Rsbuild、Parcel)への移行用Pull Requestを作成させる
  2. それぞれのブランチ別の環境をAmplifyで立ち上げる
  3. 各環境でQAを実施し、比較検証する

このアプローチにより、技術検証と実装を同時進行させることができ、
最終的に選定した時点で既に実装も完了しているという状況を作り出せる可能性があります。

実際の現場では有識者によるレビューをしっかり挟んだり、他の機能開発をリリースする影響などを鑑みるので、プロジェクトキックオフから本番適用までは1週間ほどかかりました。
しかし本気を出せば1~2日で対応可能なポテンシャルを感じています。

今回はテックブログドリブンなプロダクト開発保守の裏側について語りました。
ではまた次回の記事も乞うご期待ください!

告知

Podcast 「RemiTalk」では普段の会社の様子を赤裸々に語っています。
もし良ければ聴いてみてください!
https://podcasts.apple.com/jp/podcast/remitalk/id1826516525
Podcast 文字起こしはこちら
https://note.com/remitaid

またRemitAid では一緒に働く仲間を募集しています。
興味がある方はこちらからどうぞ!
https://youtrust.jp/recruitment_posts/ad655de82471df86af4f19469fe4c0de
https://youtrust.jp/recruitment_posts/7141d690aaa5ed348de45757da069e81

RemitAid Tech Blog
RemitAid Tech Blog

Discussion