Vite 4.3 正式發布,速度全面提升!
4 月 20 日,Vite 4.3 正式發布。在這個版本中,Vite 團隊專注于改進 devServer 的性能。簡化了解析邏輯,優化了熱路徑,并對查找 package.json、TS 配置文件和一般解析 URL 實現了更智能的緩存。與 Vite 4.2 相比,這個版本的速度得到了全面提升!
性能提升
以下是性能改進的具體數據,由 sapphi-red/performance-compare 測試得出,該測試會以 1000 個 React 組件測試應用的冷啟動和熱啟動時間以及根節點和葉節點組件的 HMR 時間:
Vite (babel) | Vite 4.2 | Vite 4.3 | 改進 |
開發冷啟動 | 17249.0ms | 5132.4ms | -70.2% |
開發熱啟動 | 6027.8ms | 4536.1ms | -24.7% |
根 HMR | 46.8ms | 26.7ms | -42.9% |
葉 HMR | 27.0ms | 12.9ms | -52.2% |
Vite (swc) | Vite 4.2 | Vite 4.3 | 改進 |
開發冷啟動 | 13552.5ms | 3201.0ms | -76.4% |
開發熱啟動 | 4625.5ms | 2834.4ms | -38.7% |
根 HMR | 30.5ms | 24.0ms | -21.3% |
葉 HMR | 16.9ms | 10.0ms | -40.8% |
此性能運行的規格和版本:
- CPU:Ryzen 9 5900X,內存:DDR4-3600 32GB,SSD:WD Blue SN550 NVME SSD
- Windows 10 專業版 21H2 19044.2846
- Node.js 18.16.0
- Vite 和 React 插件版本
- Vite 4.2 (babel): Vite 4.2.1 + plugin-react 3.1.0
- Vite 4.3 (babel): Vite 4.3.0 + plugin-react 4.0.0-beta.1
- Vite 4.2 (swc): Vite 4.2.1 + plugin-react-swc 3.2.0
- Vite 4.3 (swc): Vite 4.3.0 + plugin-react-swc 3.3.0
Vite 團隊將繼續致力于提升 Vite 的性能,正在為 Vite 開發一個官方基準測試工具,以獲得每個 Pull Request 的性能指標。vite-plugin-inspect 現在有更多與性能相關的功能,可以幫助開發者確定哪些插件或中間件是應用性能的瓶頸。頁面加載后使用 vite --profile(然后按 p)將保存 devServer 啟動的 CPU 配置文件。可以在應用中將它們作為 speedscope 打開以識別性能問題。
接下來,Vite 團隊決定今年做一個 Vite 主版本,以配合 9 月 Node.js 16 的 EOL,放棄對 Node.js 14 和 16 的支持。
為什么 Vite 4.3 這么快?
更智能的解析策略
Vite會將所有接收到的URL和路徑解析為目標模塊。在 Vite 4.2 中,存在很多冗余的解析邏輯和不必要的模塊搜索。為了減少計算和文件系統調用,Vite 4.3 使解析邏輯更簡單、更嚴格和更準確。
更簡單的解析
Vite 4.2嚴重依賴 resolve 包來解析依賴的 package.json,查看 resolve 的源碼發現解析 package.json 時有很多無用的邏輯。Vite 4.3 摒棄了 resolve,遵循更簡單的 resolve 邏輯:直接檢查嵌套父目錄中是否存在 package.json。
更嚴格的解析
Vite 必須調用 Nodejs fs API 來查找模塊。但是 IO 很昂貴。Vite 4.3 縮小了文件搜索范圍,并跳過搜索一些特殊路徑,以盡可能減少 fs 調用。例如:
- 由于 # 符號不會出現在 URL 中,用戶可以控制源文件路徑中沒有 # 符號,因此 Vite 4.3 不再檢查用戶源文件中帶有 # 符號的路徑,而是僅在 node_modules 中搜索它們。
- 在Unix系統中,Vite 4.2 會先檢查根目錄下的每一個絕對路徑,對大多數路徑都可以,但是如果絕對路徑以根開頭就很容易失敗。為了在 /root/root 不存在的情況下跳過搜索 /root/root/path-to-file,Vite 4.3 會在開頭判斷 /root/root 作為目錄是否存在,并預先緩存結果。
- 當 Vite 服務器收到 @fs/xxx? 和 @vite/xxx 時,就不需要再解析這些 URL。Vite 4.3 直接返回之前緩存的結果,不再重新解析。
更準確的解析
Vite 4.2 在文件路徑為目錄時遞歸解析模塊,會導致不必要的重復計算。Vite 4.3 將遞歸解析扁平化,并對不同類型的路徑應用適當的解析,展平后緩存一些 fs 調用也更容易。
包解析
Vite 4.3 打破了解析 node_modules 包數據的性能瓶頸。Vite 4.2 使用絕對文件路徑作為包數據緩存鍵。這還不夠,因為 Vite 必須遍歷 pkg/foo/bar? 和 pkg/foo/baz 中的同一個目錄。
Vite 4.3 不僅使用了絕對路徑(/root/node_modules/pkg/foo/bar.js? & /root/node_modules/pkg/foo/baz.js?),還使用了遍歷目錄(/root/node_modules/pkg/foo? & /root/node_modules/pkg?) 作為 pkg 緩存的鍵。
另一種情況是,Vite 4.2 在單個函數中查找深層導入路徑的 package.json?,例如 Vite 4.2 解析 a/b/c/d? 等文件路徑時,首先檢查根 a/package.json? 是否存在, 如果沒有,則按照a/b/c/package.json? -> a/b/package.json?的順序查找最近的package.json?,但事實是查找根package.json?和最近的package.json?應該分開處理 ,因為在不同的解析上下文中需要它們。Vite 4.3 將根 package.json? 和最近的 package.json 解析分成兩部分,這樣它們就不會混在一起。
fs.realpathSync 問題
Nodejs 中有一個有趣的 realpathSync 問題,它指出 fs.realpathSync? 比 fs.realpathSync.native? 慢 70 倍。但 Vite 4.2 僅在非 Windows 系統上使用 fs.realpathSync.native?,因為它在 Windows 上的行為不同。為了解決這個問題,Vite 4.3 在 Windows 上調用 fs.realpathSync.native 時添加了網絡驅動器驗證。
非阻塞任務
作為一個按需服務,Vite dev server 可以在沒有準備好所有東西的情況下啟動。
非阻塞 tsconfig 解析
Vite 服務器在預綁定 ts 或 tsx 時需要 tsconfig 數據。
Vite 4.2 在服務端啟動之前,在插件鉤子 configResolved? 中等待 tsconfig? 數據解析完成。一旦服務器啟動而沒有準備好 tsconfig? 數據,頁面請求就可以訪問服務器,即使請求可能需要稍后等待 tsconfig 解析。
Vite 4.3 會在服務器啟動前初始化 tsconfig? 解析,但服務器不會等待。解析過程在后臺運行。一旦有ts相關的請求進來,就得等tsconfig解析完了。
非阻塞文件處理
Vite 中有大量的 fs 調用,其中一些是同步的。這些同步 fs 調用可能會阻塞主線程。Vite 4.3 將它們改為異步。此外,并行化異步函數也更容易。關于異步函數,可能有許多 Promise 對象在解析后要釋放。由于更智能的解析策略,釋放 fs-Promise 對象的成本要低得多。
HMR 防抖
考慮兩個簡單的依賴鏈 C <- B <- A & D <- B <- A,當 A 被編輯時,HMR 將從 A 傳播到 C 和 A 傳播到 D。這導致 A 和 B 在 Vite 4.2 中被更新兩次。
Vite 4.3 緩存了這些遍歷的模塊,以避免多次搜索它們,它適用于由 git checkout 觸發的 HMR。
并行化
并行化始終是獲得更好性能的好選擇。在 Vite 4.3 中,我們并行化了一些核心功能,包括導入分析、提取 deps 的導出、解析模塊 url 和運行批量優化器。
Javascript 優化
用回調替換 *yield
Vite 使用 tsconfck? 來查找和解析 tsconfig? 文件。tsconfck? 曾經通過 *yield? 遍歷目標目錄,生成器的一個缺點是它需要更多的內存空間來存儲它的生成器對象,并且在運行時會有大量的生成器上下文切換。所以從 v2.1.1 開始在核心中用回調替換 ??*yield?
?。
使用 === 代替 startsWith 和 endsWith
Vite 4.2 使用 startsWith? 和 endsWith? 檢查熱更新 URL 中的頭部和尾部 '/'。比較 str.startsWith('x')? 和 str[0] === 'x'? 的執行基準發現,===? 比 startsWith? 快約 20%,endsWith 比 === 慢約 60%。
避免重復創建正則表達式
Vite 需要很多正則表達式來匹配字符串,其中大部分都是靜態的,因此只使用它們的單例會更好。Vite 4.3 將正則表達式提升,以便可以重復使用它們。
放棄生成自定義錯誤
在 Vite 4.2 中,有一些自定義錯誤以改進開發體驗。這些錯誤可能會導致額外的計算和垃圾回收,從而降低 Vite 的速度。在 Vite 4.3 中,放棄了生成某些熱更新自定義錯誤(例如 package.json NOT_FOUND 錯誤),直接拋出原始錯誤以獲得更好的性能。
參考資料:
- https://vitejs.dev/blog/announcing-vite4-3.html。
- https://sun0day.github.io/blog/vite/why-vite4_3-is-faster.html。