Vite 3.0 更新了什么?

以下文章來源于三元同學 ,作者三元同學



在 2021 年 2 月,尤大正式推出了 Vite 2.0 版本,可以說是 Vite 的一個重要轉折點,自此之后 Vite 的用戶量發(fā)生了非常迅速的增長,很快達到了每周 100 萬的 npm 下載量。同時,Vite 的社區(qū)也越來越活躍,目前已經形成非常龐大的社區(qū)生態(tài)(詳情可見Github 地址[1]),給整個前端領域帶來了諸多的改變,如:

Nuxt 3、SvelteKit、Astro、StoryBook 等在內的各大前端框架已經將 Vite 作為內置的構建方案。

基于 Vite 的測試工具 Vitest 誕生,成為替代 Jest 的新一代測試方案。

如今已經 2022 年 7 月,距離 v2 版本已經發(fā)布了 16 個月的時間,Vite 正式推出 3.0 版本,接下來就給大家介紹一下 Vite 3.0 帶來的一些改變以及未來的規(guī)劃。

一、全新的 VitePress 文檔
對于用戶側來說,談到框架的更新,文檔自然是最重要的部分?,F在你可以直接去 vitejs.dev[2] 站點體驗到 v3 版本的文檔,目前文檔同樣是使用 VitePress[3] 進行搭建。下面是暗黑模式下的一張截圖:



怎么樣,是不是比以前更加好看了呢?

不光是 Vite,也有 Vite 生態(tài)中其它的一些項目使用 VitePress 進行文檔站點的搭建,比如 Vitest[4], vite-plugin-pwa[5] 以及 VitePress[6] 自身的文檔,我也十分推薦大家使用 VitePress 作為自己的文檔建站方案之一。

如果你需要查看 Vite 2.0 的文章,也可以訪問 v2.vitejs.dev[7]。

二、開發(fā)階段的更新
1. CLI 的更新
在執(zhí)行 vite 命令啟動項目時,終端的界面和之前會有所不同,而更重要的是,為了避免 Vite 開發(fā)服務的端口和別的應用沖突,默認的端口號從之前的 3000 變成了 5173。



2. 開箱即用的 WebSocket 連接策略
Vite 2 中有存在一個痛點,即在存在代理的情況下(比如 Web IDE)需要我們手動配置 WebSocket 使 HMR 生效。目前 Vite 內置了一套更加完善的 WebSocket 連接策略,自動滿足更多場景的 HMR 需求。

3. 服務冷啟動性能提升
Vite 3.0 在服務冷啟動方面做了非常多的工作,來最大程度提升項目啟動的速度。

首先我們來盤點一下 Vite 2.x 階段服務冷啟動的一些問題。

從 Vite 2.0 到 2.9 版本之前,Vite 會在服務啟動之前進行依賴預構建,也就是使用 Esbuild 將項目中使用到的依賴掃描出來(Scan),然后分別進行一次打包(Optimize)。



這樣會造成兩個問題:

依賴預構建會阻塞 Dev Server 啟動,但其實不阻塞的情況下,Dev Server 也可以正常啟動。
當某些 Vite 插件手動注入了 import 語句,比如調用 babel-plugin-import 添加import Button from 'antd/lib/button',就會導致 Vite 的二次預構建,因為 antd/lib/button 的引入代碼由 Vite 插件注入,屬于 Dev Server 運行時發(fā)現的依賴,冷啟動階段無法掃描到。
所謂的二次預構建包含兩個步驟,一是需要將所有的依賴全量預構建,二是由于依賴更新,頁面需要進行 reload,加載最新的依賴代碼。這樣會導致 Dev Server 性能明顯下降,尤其是在新增依賴較多的場景下,很容易出現瀏覽器卡住的情況。因此二次預構建也是需要極力避免的。當時 vite-plugin-optimize-persist[8] 就是為了解決二次預構建帶來的問題,通過持久化的方式記錄 Dev Server 運行時掃描到的依賴,從而讓首次預構建便可以感知到,避免二次預構建的發(fā)生。

到了 2.9 版本,Vite 將預構建的邏輯做了一次整體的重構,最后的效果是下面這樣的:

Dev Server 啟動后預構建(Optimize 階段)在后臺執(zhí)行,也就是預構建不再阻塞 Dev Server 的啟動,只需要等待 Scan 階段完成,不過通常這個階段的開銷非常小。



如果某些依賴是 Dev Server 運行時才發(fā)現的,那么 Vite 會盡可能地復用已有預構建產物,盡量不進行 page reload。
具體實現大家可以去查看這個 PR[9]

那問題就完全解決了嗎?其實并不是,在某些場景下,Vite 仍然不可避免地需要二次預構建。如下面的這個例子:



A 和 B 都是項目的第三方依賴,它們也同時依賴 C。那么當 Vite 預構建 A 的時候,將會 A 和 C 一起進行打包。但 Vite 在運行時發(fā)現了依賴 B,而 A 和 B 需要共享 C 的代碼,這樣 C 的代碼可能就會被抽離成一個公共的 chunk,因此之前 A 的預構建產物可能就發(fā)生變化了,那么此時 Vite 必須要強制刷新頁面,讓瀏覽器使用最新的預構建產物。這仍然是一個二次預構建(所有依賴再次打包 + page reload)的過程。

總體而言,2.9 版本解決了預構建阻塞服務啟動的問題,但并沒有完全解決二次預構建的問題。

但在 Vite 3.0,二次預構建的問題也得到了根本的解決。那 Vite 3.0 是如何做到的呢?

核心的解決思路在于延遲處理,即把預構建的行為延遲到頁面加載的最后階段進行,此時 Vite 已經編譯完了所有的源文件,可以準確地記錄下所有需要預構建的依賴(包括 Vite 插件添加的一些依賴),然后統(tǒng)一進行預構建,將預構建的產物響應給給瀏覽器即可。

依賴預構建的代碼在 Vite 中先后重構了多次,目前的版本實現比較復雜,后續(xù)會單獨寫一篇文章討論實現細節(jié)。

因此,與 Vite 2.0 相比,Vite 3.0 在冷啟動階段所做的優(yōu)化主要有兩個方面:

預構建不再阻塞 Dev Server 的啟動,真正做到服務秒啟動的效果;
從根本上防止二次預構建的發(fā)生。





4. import.meta.glob 語法更新
Vite 3.0 中重寫對 import.meta.glob 的實現進行了重寫,支持了更加靈活的 glob 語法,增加了如下的一些特性:

多種模式匹配:
import.meta.glob(["./dir/*.js", "./another/*.js"]);
否定模式(!):
import.meta.glob(["./dir/*.js", "!**/bar.js"]);
命名導入,可以更好地做到 Tree Shaking:
import.meta.glob("./dir/*.js", { import: "setup" });
自定義 query 參數:
import.meta.glob("./dir/*.js", { query: { custom: "data" } });
指定 eager 模式,替換掉原來import.meta.globEager:
import.meta.glob("./dir/*.js", { eager: true });
三、生產階段的更新
1. SSR 產物默認使用 ESM 格式
在當下的社區(qū)生態(tài)中,眾多 SSR 框架已經在使用 ESM 格式作為默認的產物格式。Vite 3.0 也積極擁抱社區(qū),支持 SSR 構建默認打包出 ESM 格式的產物。

2. Relative Base 支持
Vite 3.0 正式支持 Relative Base(即配置base: ''),主要用于構建時無法確定 base 地址的場景。

四、實驗性功能
1. 更細粒度的 base 配置
在某些場景下,我們需要將不同的資源部署到不同的 CDN 上,比如將圖片部署到單獨的 CDN,和 JS/CSS 的部署服務區(qū)分開來。但 2.x 的版本僅支持統(tǒng)一的部署域名,即base 配置。在 3.0 中,你可以通過 renderBuiltUrl 進行更細粒度的配置:

{
  experimental: {
    renderBuiltUrl: (filename: string, { hostType: 'js' | 'css' | 'html' }) => {
      if (hostType === 'js') {
        return { runtime: `window.__toCdnUrl(${JSON.stringify(filename)})` }
      } else {
        return 'https://cdn.domain.com/assets/' + filename
      }
    }
  }
}
目前該配置項還不穩(wěn)定 ,可能會在之后的 minor 版本修改。具體文檔見 https://vitejs.dev/guide/build.html#advanced-base-options

2. Esbuild 預構建用于生產環(huán)境
這應該是 Vite 架構上非常大的一個改動: 將原來僅僅用于開發(fā)階段的依賴預構建功能應用在生產環(huán)境。在 Vite 2.x 中,開發(fā)階段使用 Esbuild 來打包依賴,而在生產環(huán)境使用 Rollup 進行打包,用 @rollupjs/plugin-commonjs 來處理 cjs 的依賴,這樣做會導致依賴處理的不一致問題,造成一些生產構建中的 bug。

但 Vite 3.0 中支持通過配置將 Esbulid 預構建同時用于開發(fā)環(huán)境和生產環(huán)境,僅添加optimizeDeps.disabled: false 的配置即可。不過這個改動確實比較大,Vite 團隊不打算將此作為 v3 的正式更新內容,而是一個實驗性質的功能,不會默認開啟。

順便提一句,Rollup 將在接下來的幾個月發(fā)布 v3 的大版本,要知道,Rollup 2.0 發(fā)布至今已經過去 2 年多的時間了,無論是 Rollup 還是 Vite 來講,這都是一次非常重大的變更。由于 Vite 的架構非常依賴 Rollup,在 Rollup 發(fā)布 v3 之后,Vite 也將跟隨著發(fā)布 Vite 的第 4 個 major 版本。所以,Vite 4.0 的到來也不遠啦:)

五、倉庫內部的變化
除了本身功能上的演進,Vite 的倉庫本身也產生了不少的變化,從中我們也能了解到社區(qū)的一些動向:

不再支持 Nodejs 12,需要 Node.js 14.18+ 的版本。
單元測試和 E2E 測試從 Jest 完全遷移到 Vitest,一方面 Vitest 更快、體驗更好,另一方面也能在 Vite 這樣大型的倉庫完善 Vitest 的生態(tài),進一步提升 Vitest 穩(wěn)定性。
VitePress 文檔部分也參與 CI 流程。
包管理器 pnpm 遷移至 v7。
不管是 Vite 本身的包還是 E2E 中測試的項目,都在 package.json 中聲明 type: "module",即 Pure ESM 包,對外提供 ESM 格式的產物,將社區(qū) Pure ESM 的趨勢又推動了一步。
官方所有的 Vite 插件都采用 unbuild(新一代庫構建工具) 進行構建,pluin-vue-jsx 和 plugin-legacy 均遷移到了 TS 上。
包體積優(yōu)化。3.0 進一步優(yōu)化 Vite 本身的產物和 node_modules 體積,將 terser 和 node-forge 的依賴移除,讓用戶進行按需安裝(node-forge 的功能是實現 https 證書生成,可用 @vitejs/plugin-basic-ssl 插件替代),效果如下:

Publish Size    Install Size
Vite 2.9.14    4.38MB    19.1MB
Vite 3.0.0    3.05MB    17.8MB
Reduction    -30%    -7%
不得不說在自身包體積的優(yōu)化方面, Vite 對于還是做的很細致的,這也是很多庫開發(fā)者忽視的一點,有時候加個插件就得安裝動輒上百 MB 的依賴,導致項目的 node_modules 最后變得非常臃腫,此時不妨學習一下 Vite 是怎么優(yōu)化自身體積的。

六、未來規(guī)劃
首先在 Vite 3.0 發(fā)布之后會重點保證 3.0 的穩(wěn)定性,解決目前的一系列 issue。

其次,Rollup 團隊將在接下來的幾個月發(fā)布新的 major 版本,Vite 將持續(xù)跟進,緊接著發(fā)布 v4 版本,并在 v4 版本中將目前的一些實踐性功能穩(wěn)定下來。

小結
Vite 3.0 帶來了一些比較大的架構變動,比如依賴預構建的重構、支持生產環(huán)境 Esbuild 預打包依賴以及全面支持 Pure ESM,當然也有一些比較小的 break change 在這個版本集中發(fā)布,比如 import.meta.glob 語法的變更等等。

總之,在這一年多的時間里,Vite 團隊做了非常多的功能改進和架構升級,目前的 Github Star 已經達到了 44 k+,并且還在持續(xù)維護中。與此同時,Vite 的社區(qū)生態(tài)也逐步完善,比如 Vitest、VitePress、豐富的社區(qū)插件[10]以及眾多內置 Vite 的社區(qū)框架等等,可以預見的是,Vite 將在未來的很長一段時間內繼續(xù)發(fā)展,持續(xù)迭代,提供更好的用戶體驗,成為下一代前端工具鏈。

參考資料
[1]
Github 地址: https://github.com/vitejs/awesome-vite

[2]
vitejs.dev: vitejs.dev

[3]
VitePress: https://vitepress.vuejs.org/

[4]
Vitest: https://vitest.dev/

[5]
vite-plugin-pwa: https://vite-plugin-pwa.netlify.app/

[6]
VitePress: https://vitepress.vuejs.org/

[7]
v2.vitejs.dev: https://v2.vitejs.dev/

[8]
vite-plugin-optimize-persist: https://github.com/antfu/vite-plugin-optimize-persist

[9]
PR: https://github.com/vitejs/vite/pull/6758

[10]
社區(qū)插件: https://github.com/vitejs/awesome-vite

作者:三元同學


歡迎關注微信公眾號 :前端印象