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

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

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

Gao
Gao
Founder

Stop wasting weeks on user auth
Launch secure apps faster with Logto. Integrate user auth in minutes, and focus on your core product.
Get started
Product screenshot

背後的故事

我們在 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,希望這個短篇故事能幫助到你的遷移。最終的配置如下所示: