Skip to content

build: migrate from create-react-app (craco) to Vite - #157

Merged
jamliaoo merged 11 commits into
mainfrom
migrate-vite
Sep 15, 2026
Merged

jamliaoo merged 11 commits into
mainfrom
migrate-vite

Conversation

@jamliaoo

@jamliaoo jamliaoo commented Aug 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

移除 AsciiMath 之後,專案不再需要綁定 create-react-app 的 webpack 特殊處理,因此把建置工具鏈換成 Vite 7。對外行為與產物結構維持不變:

  • 產物仍輸出到 build/(含 index.html、version.txt、locales/、access8math-web-template.zip),main.js(electron-serve)、Forge、deploy script 與 GitHub workflows 都不需要改。
  • dev server 固定在 port 3000(strictPort),npm run start:desktop 流程不變。
  • npm run build / build:staticFiles / build:windows / build:macOS / test / lint / format:check / storybook 指令名稱不變。

Changes

  1. refactor: rename JSX-containing .js files to .jsx — 37 個含 JSX 的檔案純 git mv(Vite 預設只對 .jsx 做 JSX 轉換)。ESLint 加 root: true 並忽略 build/、out/。
  2. build: migrate from create-react-app/craco to Vite
    • 移除 react-scripts、@craco/craco、webpack、web-vitals、@storybook/preset-create-react-app、@storybook/react-webpack5;新增 vite、@vitejs/plugin-react、vite-plugin-svgr、vitest、jsdom、@storybook/react-vite、autoprefixer。
    • index.html 移到根目錄;vite.config.mjs 提供 @ alias、port 3000、outDir: build、以及寫 version.txt 的小 plugin(取代 craco VersionPlugin;寫入實際 resolved outDir,所以 storybook build 只會寫到 storybook-static/)。
    • SVG:import { ReactComponent as X } from 'a.svg' → import X from 'a.svg?react'(172 處,機械替換);純 URL 用法(toast 的 mask-image)不變。
    • 測試:jest → vitest。fake timers 改為在 beforeEach 啟用(模組頂層呼叫會把 vitest 自己的逾時計時器也假造掉),globalThis.jest = vi 讓 testing-library 的 waitFor 能自動推進假時鐘;修正一個過時的 class 斷言(bg-green-500 → bg-status-success-bg,該測試在 main 上原本就失敗)。
    • Storybook 改用 @storybook/react-vite,alias/svgr/PostCSS 從 vite.config.mjs 繼承。
  3. ci: require Node >=20.19 for Vite 7 — Windows publish workflow 改 Node 22,package.json 加 engines(最後一個 commit 把範圍對齊為 ^20.19.0 || ^22.12.0 || >=24.0.0,與 Vite 7、jsdom 27 一致)。
  4. docs: bump Node requirement to 20.19 in README
  5. build: address review findings for the Vite migration — jsdom 27(engines 與 Node ≥20.19 一致)、build:staticFiles alias、jest 全域只暴露 advanceTimersByTime facade、測試檔清理。

為什麼把 .js 改成 .jsx

Vite 以 esbuild 轉譯,預設只對 .jsx / .tsx 啟用 JSX;含 JSX 的 .js 會在載入時報語法錯誤。CRA 底層是 Babel 會把所有 .js 當 JSX 解析,所以過去不需區分。改副檔名是 Vite 官方建議的遷移方式。

雖然可以用 esbuild loader 加自訂 plugin 讓 .js 走 JSX 轉換,但所有 .js 都會變慢、屬社群 workaround 易壞,且 ESLint 等工具依副檔名判斷語法,.js 裝 JSX 正是先前 lint 漏掉 36 個檔案的根源,因此不採用。

只改內容含 JSX 的 37 個檔案,全部 git mv 零內容變更,blame 歷史可延續;純邏輯檔(store.js、i18n.js 等)維持 .js。

Verification

  • npm run build:產物齊全,build/version.txt 正常;vite preview 於瀏覽器手動驗證編輯器、svgr 圖示、i18n(zh-TW)、MathJax 渲染,console 無錯誤。
  • npm test:8/8 通過。
  • npx storybook build:成功。
  • npm run lint、npm run format:check:通過。
  • npm run start:確認綁定 http://localhost:3000/。
  • npx electron-forge package:成功。

Notes

  • 版本選擇:Storybook 9.1 的 peer 只接受 Vite ≤7,因此採用 Vite 7 + @vitejs/plugin-react 5 + vitest 3。
  • package.json 的 browserslist / homepage 為 CRA 時代殘留,這次刻意不動;Vite 預設 target 為 baseline-widely-available,未加 @vitejs/plugin-legacy。
  • build:staticFiles 已移除(build:windows 直接呼叫 npm run build)。sourcemap 部分已確認非行為變更:main 上 Windows 產物帶 .map 只是 GENERATE_SOURCEMAP=false 在 cmd 下無法解析的意外,macOS 產物本來就沒有 sourcemap。
  • 既有問題(非本 PR 造成):打包後的 Electron app 在 app:// 下會持續重試載入不存在的 locales/*/translation.json(i18next 預設命名空間),以同一探針測試 main 上的 CRA 打包版也是相同行為。之後另外開 issue 處理。

Prepares for the Vite migration (Vite only applies JSX transform to .jsx
by default). Pure git mv, no content changes. Also sets ESLint root: true
and ignores build/out output directories.
- Replace react-scripts/craco with vite + @vitejs/plugin-react; drop webpack,
  web-vitals and the CRA babel plugin.
- Move index.html to the project root and point it at src/index.jsx.
- vite.config.mjs: `@` alias, dev server pinned to port 3000 (main.js), outDir
  kept as build/ (electron-serve, forge, deploy scripts and CI), and a small
  plugin that writes version.txt into the resolved outDir (replaces the craco
  VersionPlugin).
- SVG components: `import { ReactComponent as X }` -> `import X from '...svg?react'`
  via vite-plugin-svgr; plain .svg imports keep resolving to URLs.
- Rename the remaining 5 JSX-containing .js files to .jsx.
- Tests: jest -> vitest (jsdom, globals, jest-dom/vitest). Fake timers are now
  enabled per test so Vitest's own timeout timer is not faked; alias
  `globalThis.jest = vi` so testing-library's waitFor auto-advances fake timers.
  Fix a stale class assertion (bg-green-500 -> bg-status-success-bg).
- Storybook: @storybook/react-webpack5 + preset-create-react-app ->
  @storybook/react-vite (alias/svgr/PostCSS inherited from vite.config.mjs).
- Add postcss.config.js (tailwind + autoprefixer).
Bump the Windows publish workflow to Node 22 and declare engines.node so
local installs on older Node fail early.
- Use jsdom 27 so the test environment matches the declared Node >=20.19
  range (jsdom 30 requires Node 22.22+/24.15+).
- Make build:staticFiles an alias of build instead of a duplicate command.
- Narrow the `jest` global shim in setupTests to the advanceTimersByTime
  facade testing-library needs.
- Drop the redundant jest-dom import and the no-op runOnlyPendingTimers
  call in toaster.test.jsx.
- Correct the Storybook config comment about PostCSS discovery.
Vite inlines small SVGs as data: URIs containing single quotes; an
unquoted CSS url() rejects those as bad-url tokens, so production builds
lost the toast status/close icons. Wrap the interpolation in double
quotes (Vite's SVG encoding never emits double quotes).
- lint: pass --ext .js,.jsx,.mjs so the renamed .jsx files are actually
  linted (ESLint 8 eslintrc mode only picks up .js by default).
- version plugin: resolve outDir against config.root and write from
  writeBundle so nothing is emitted when the build fails.
- CI: pin Node 22 via setup-node in the three ubuntu workflows instead of
  relying on the runner default (Vite 7 needs Node 20.19+/22.12+).
- Regenerate package-lock.json so its root engines match package.json.
- Remove unused dependencies core-js, i and @heroicons/react (nothing
  imports them; forge would ship them in every installer).
- Remove src/__mocks__ (only referenced by the deleted craco Jest config)
  and the CRA sample manifest.json plus its index.html link.
- Remove build:staticFiles (pure alias of build); build:windows calls
  npm run build directly.
- Correct the fake-timer comment in toaster.test.jsx: Vitest's own test
  timeout runs on real timers either way; per-test arming exists to give
  each test a fresh fake clock.
Nothing ran npm test in CI before; the stale assertion on main proved
regressions could land silently.
- toast.jsx: keep the dismiss setTimeout in a ref and clear it in the
  unmount cleanup so it cannot fire against the store after removal.
- setupTests: stub the whole vi object as the jest global; the narrow
  facade let ESLint-accepted jest.* calls explode at runtime.
- Replace the per-file eslint-env jest directive with a vitest globals
  override in .eslintrc.js; drop the now-redundant clearAllTimers in
  the toaster suite.
@jamliaoo
jamliaoo marked this pull request as ready for review September 13, 2026 09:37

@wendyyuchensun wendyyuchensun left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我沒有完全每一行都看,但檢查了大方向覺得沒問題。
感謝,辛苦了 👍

@jamliaoo
jamliaoo merged commit dfedc01 into main Sep 15, 2026
1 check passed
@jamliaoo
jamliaoo deleted the migrate-vite branch September 15, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants