繁體中文(香港)
  • parcel
  • vite
  • js
  • esbuild
  • bundler
  • monorepo

從 Parcel 到 Vite:100K LOC 遷移的短篇故事

我們已將三個前端專案從 Parcel 遷移到 Vite,過程非常順利。

Gao
Gao
Founder

背後的故事

我們在 Logto 有三個主要的前端專案:登入體驗、控制台和即時預覽。這些專案都是用 TypeScript、React 和 SASS 模組編寫的;總共約有 100K 行代碼。

我們喜歡 Parcel 的簡單和零設置。我仍然記得第一次看到用 Parcel 設置新專案有多麼簡單,你只需運行 parcel index.html,然後所有的必要依賴就會被安裝,專案就運行起來了。如果你是一位「經驗豐富」的開發者,你可能會有同樣的感受,將其與設置 Gulp 和 Webpack 的舊時代相比較。Parcel 就像一根魔法棒。

Parcel 的簡單性是我們堅持使用它這麼久的主要原因,儘管有時它也會有點不穩定。例如:

  • Parcel 有時候會打包失敗,因為它找不到實際存在的某些文件塊。
  • 需要一些技巧來配置,以使其與我們的 monorepo 設置一起工作。
  • 不支持原生的 MDX 3,因此我們必須為其創建自定義的轉換器。
  • 不支持手動塊(截至撰寫本文時,手動塊功能仍在試驗階段),對大多數情況來說還好,但有時你需要它。

那麼我們為什麼決定遷移到其他平台?

  1. 我們被限於使用 2023 年 6 月發佈的 Parcel 2.9.3。每當新版本發布後,我們都嘗試升級,但總是以構建錯誤告終。
  2. 最新版本的 Parcel 是 2024 年 2 月發布的 2.12.0。雖然幾乎每天都有提交,但自那以來沒有任何新的版本發佈。

甚至有人在討論 Parcel 是否已死。官方回答是否定的,Parcel 仍然活著,只是處於「我們正在進行大規模重構,暫時無法發佈小版本」的狀態。對我們來說,這就像「鴨子死亡」:我們能用的最新版本已經是一年多前的,我們不知道下一個版本何時發布。看起來像死了,行為像死了,對我們來說就是死了。

Parcel 升級的拉請求

相信我,我們已經嘗試過了。

為什麼選擇 Vite?

我們通過 Vitest 知道了 Vite。數月前,我們對 Jest 的 ESM 支持(在測試中)感到厭倦,想要嘗試一些新東西。Vitest 憑藉其原生 ESM 支持和 Jest 兼容性贏得了我們的心。它提供了驚人的開發者體驗,並由 Vite 驅動。

現狀

在你的專案中可能會有不同的設置,但通常隨著 Vite 生態系統的興起,你會找到插件替代品。以下是我們遷移時的設置:

  • Monorepo: 我們使用 PNPM(v9)工作區管理我們的 monorepo。
  • 模組: 我們的專案全部使用 ESM 模組。
  • TypeScript: 我們的專案全部使用 TypeScript(v5.5.3)帶有路徑別名。
  • React: 我們的前端專案全部使用 React(v18.3.1)。
  • 樣式: 我們使用 SASS 模組進行樣式設計。
  • SVG: 我們將 SVG 用作 React 組件。
  • MDX: 我們使用 MDX,支持 GitHub 風味的 Markdown 和 Mermaid。
  • 延遲加載: 我們需要延遲加載部分頁面和組件。
  • 壓縮: 我們的生產構建中會生產壓縮資產(gzip 和 brotli)。

遷移

我們通過創建一個新的 Vite 專案開始遷移,並進行一些測試來了解它是如何工作的。過程很順利,實際遷移只用了幾天。

原生支持

Vite 原生支持 monorepo、ESM、TypeScript、React 和 SASS。我們只需要安裝必要的插件和配置使其正常運行。

路徑別名

Vite 內建支持路徑別名,例如,在我們的 tsconfig.json 中:

我們只需要在 vite.config.ts 中添加相同的解析:

注意,替換路徑應為絕對路徑,雖然它是相對於專案根目錄的。另外,你可以使用 vite-tsconfig-paths 插件從 tsconfig.json 中讀取路徑別名。

React 快速更新和 HMR

雖然 Vite 內建支持 HMR,但啟用 React 快速更新仍需安裝一個插件。我們使用了由 Vite 團隊提供的 @vitejs/plugin-react 插件,對 React 特性如快速更新有很好的支持:

SVG 作為 React 組件

我們使用 vite-plugin-svgr 插件將 SVG 轉換為 React 組件。只需將插件添加到 Vite 配置中:

然而,我們不指定在何種條件下將 SVG 轉換為 React 組件,因此所有的導入都被轉換了。該插件提供了一個更好的默認配置:僅將導入的 .svg?react 擴展名的 SVG 轉換。我們相應地更新了導入。

SASS 模組

雖然 Vite 原生支持 SASS 模組,但有一點需要注意:類名的格式。如果類名格式不一致,對用戶和我們的集成測試來說可能會很麻煩。在 vite.config.ts 中的一行配置可以解決這個問題:

順便說一下,Parcel 和 Vite 導入 SASS 文件的方式有所不同:

* as 語法在 Vite 中仍然有效,但當你使用動態鍵訪問 styles 對象時會導致模塊化類名丟失。例如:

MDX 支持

由於 Vite 在底層使用 Rollup,我們可以使用官方的 @mdx-js/rollup 插件來支持 MDX 以及其插件。配置如下:

remarkGfm 插件用於支持 GitHub 風味的 Markdown,rehypeMdxCodeProps 插件用於將屬性傳遞到 MDX 文件中的代碼塊,如 Docusaurus 所做的一樣

MDX 中的 Mermaid 支持

我們希望在我們的 MDX 文件中支持 Mermaid 圖表,就像其他編程語言一樣。用法應該與其他代碼塊一樣簡單:

應呈現為:

由於我們的應用支持淺色和深色主題,我們進行了一些編碼以使 Mermaid 圖表在深色主題下工作。創建了一個 React 組件:

useTheme 是一個用於從上下文中獲取當前主題的自定義 hook。mermaid 庫是異步導入的,以減少初始頁面加載的大小。

對於 MDX 文件中的代碼塊,我們有一個統一的組件來執行此任務:

最後,我們定義 MDX 提供器如下:

延遲加載

這不是 Vite 特有的事,但它仍然值得一提,因為我們在遷移期間更新了我們的頁面以使用延遲加載,之後一切都正常運行。

React 內建了一個 React.lazy 函數來延遲加載組件。然而,在快速迭代時,這可能會引發一些問題。我們製作了一個小型庫,名為 react-safe-lazy,來解決這些問題。這是 React.lazy 的替代品,詳情可以在這篇博客文章中找到。

壓縮

有一個不錯的插件叫做 vite-plugin-compression 可以生成壓縮資產。它支持 gzip 和 brotli 壓縮。配置非常簡單:

手動塊

Vite(或底層的 Rollup)的一大特性是手動塊。雖然 React.lazy 用於延遲加載組件,我們可以通過指定手動塊更多地控制塊,來決定哪些組件或模塊應該一起打包。

例如,我們可以先使用 vite-bundle-visualizer 分析包大小和依賴,然後編寫一個適當的函數劃分塊:

開發伺服器

與生產以成為不同,Vite 在開發模式中不會打包源代碼(包括在相同 monorepo 中的鏈接依賴),並將每個模塊視為文件。對我們來說,瀏覽器首次加載時將加載數百個模塊,這看起來很瘋狂,但實際上在大多數情況下是可以接受的。你可以在 這裡看到討論。

如果這對你來說是個問題,一種不完美但可行的解決方案是將鏈接的依賴添加到 vite.config.tsoptimizeDeps 選項中:

這將「預打包」鏈接依賴,使開發伺服器更快。缺點是 HMR 可能不會對鏈接依賴預期地工作。

此外,我們使用了一個代理,在生產中提供靜態文件並在開發中將請求代理到 Vitest 伺服器。我們配置了一些特定的端口以避免衝突,在 vite.config.ts 中也很容易設置:

環境變量

與 Parcel 不同,Vite 使用現代方法來處理環境變量,使用 import.meta.env。它會自動加載 .env 文件並在代碼中替換變量。然而,它要求所有的環境變量都須以 VITE_(可配置)開頭。

當我們使用 Parcel 時,它簡單地替換 process.env 變量而不檢查前綴。所以我們想出了一個將遷移變得更簡單的變通方法,使用 define 字段:

這樣我們可以逐步為環境變量添加前綴,並移除 define 字段。

結論

就是這樣!我們順利地將三個前端專案從 Parcel 遷移到 Vite,希望這個短篇故事能幫助到你的遷移。最終的配置如下所示: