Vite 是什麼? 為什麼要用 Vite? 它解決了哪些問題? 又是如何解決?
2024年4月30日
什麼是 Vite?
Vite 是近幾年前端業界熱門的 JavaScript 建構工具 (build tool),它大幅地簡化了前端建構的流程與時間。Vite 的核心功能有幾點:
作為開發伺服器 (dev server)
讓開發者可以在本地 (localhost) 進行開發。Vite 的熱模組更新 (hot module replacement, HMR) 提供開發者非常好的開發體驗。以開發 React 來說,當有任何的改動後,Vite 的熱模塊更新會以非常快的速度重新渲染本地的頁面,同時會保留當下的任何狀態 (state)。
打包程式碼
Vite 背後是透過 Rollup 進行打包,高度優化部署到生產環境 (production) 的打包結果。
目前前端社群中的絕大部分框架,都採用了 Vite,包含 Astro, Nuxt, SvelteKit, Solid Start, Qwik City 都是;基本上除了 Next 與 Angular 之外,熱門的框架都是選 Vite。
為什麼要用 Vite? 它解決了哪些問題?
Vite 的核心功能跟業界很常使用的 Webpack 很相似,那為什麼要選 Vite 呢? 它解決了哪些過去遇到的問題?
解決開發伺服器過慢
假如有用過 Webpack 在大型專案的開發者,可能會有類似的經驗,就是在改動某部分程式碼後,要等數十秒才能看到畫面渲染新的版本,特別是專案中有用 TypeScript 時,每次的等待時間都讓開發體驗非常的差。類似的問題可能不只在使用 Webpack 的專案出現,其他工具也有 (例如 Vue CLI)。
會有這個問題,是因為當專案越大時,不僅專案本身的程式碼越多,依賴的套件也會變多,導致整個專案的程式碼程式碼量大幅度上升,而過去的工具並沒辦法很有效的處理這麼多程式碼,才會導致,改一個東西,要等很久才會在畫面上看到改變。
而 Vite 的出現就是要解決這類問題。Vite 不管在本地專案的冷啟動,或是專案改動時的熱模組更新,速度都非常的快。每次修改後不用等個數十秒才能看到新的畫面。這讓開發者的體驗更好,試想假如做某個專案,每次改動完都要等半分鐘,很可能讓開發者失去耐心。除此之外,這也讓回饋循環 (feedback loop speed) 更加快速,對於專案開發也是助益良多。
冷啟動是什麼?
冷啟動指的是系統在完全沒有預先運行的狀態下啟動、或首次啟動構建工具所經歷的階段,因此意味著系統需要從頭開始初始化,通常都會需要比較長時間。
生產環境的程式碼打包
除了說能解決開發時的開發伺服器過慢問題,Vite 也透過 Rollup 協助進行生產環境的打包。雖然現在原生的 ES Module 已經獲得廣泛的支持,但如果直接在生產環境使用沒打包的 ES Module 仍是沒效率的。在打包的過程中,透過 tree-shaking、lazy-loading、common chunk splitting 等方式,可以讓打包出的結果能夠獲得優化。
你可能會問,為什麼我們要為生產環境打包程式碼? 畢竟現在原生的 ESM 已經得到廣泛支持。原因是在生產環境中直接使用未打包的 ESM 仍然不是太有效率,這是因為由於嵌套引入 (nested import) 會需要額外的網路傳輸往返,即使開啟 HTTP/2 也是,而額外的網路傳輸就需要額外的時間。為了在生產環境中,能有最佳的加載性能,透過 tree-shaking、懶加載(lazy-loading)和常見塊分割(common chunk splitting)來打包程式碼,可以更好地緩存,也能提升效率。
Vite 如何解決伺服器過慢的問題?
上面提到,Vite 解決開發伺服器過慢的問題,其實我們可以更進一步拆解成「開發伺服器啟動過慢」與「開發伺服器更新過慢」這兩個問題來看。
Vite 如何解決開發伺服器「啟動過慢」的問題
當啟動開發伺服器時,如果用基於打包工具 (bundler-based) 的設置,會需要先把你整個應用程式都走過一遍,然後才能啟動伺服器。
Vite 通過把應用程式中的模組分為兩類來加快開發服務器的啟動時間,一個是依賴 (dependencies),另一個是源碼 (source code)。
依賴 (dependencies) 大多是普通的 JavaScript,開發過程中不會經常改變。一些大型的依賴(例如擁有數百個模組的組件庫)處理起來也相當耗時。依賴還可能以不同的模組格式(例如 ESM 或 CommonJS)提供。Vite 使用 esbuild 預先打包這些依賴。esbuild 是用 Go 語言寫的,比基於 JavaScript 的打包工具快 10 到 100 倍,這讓 Vite 加快速度不少。
而源碼則通常需要轉換 JSX、CSS 或 Vue/Svelte 組件等非純 JavaScript 的東西,並且會經常被更動。然而,並不是所有的源碼都需要同時加載(例如,基於路由的分割的代碼,只有在到某個路由時才需要加載)。Vite 通過原生 ESM 來提供源碼。實際上這是讓瀏覽器接管了打包工具的部分工作,Vite 只需要根據瀏覽器的請求,按需轉換和提供源碼。只有在當前某個元件被實際使用時,才會動態載入背後的程式碼,這也樣讓最開始要啟動伺服器的程式碼減少,自然讓啟動變快。
Vite 如何解決開發伺服器「更新過慢」的問題
在基於打包工具 (bundler-based)的設置中,當一個文件被更新後,重新構建整個包效率會很低。原因顯而易見,因為更新速度會隨著應用程式大小的增加而下降 (白話來講就是,越大包,打包起來越慢)。
在某些打包工具中,開發服務器會在記憶體中運行打包,但是當文件改變時,它仍然需要重新構建打包,並重新加載網頁。重新構建打包可能很耗時,並且重新加載頁面會消除應用程式當前的狀態 (state)。這就是為什麼一些打包工具支持熱模組替換(HMR):允許模組在不影響頁面其餘部分的情況下「熱替換」自己。這大大提高了開發體驗(DX),但實際上,即使是 HMR,隨著應用程式大小的增長,更新速度也會顯著下降。
在 Vite 中,HMR 是通過原生 ESM 進行的。當一段程式碼被更新時,Vite 只需要準確地使更新的模組與其最近的 HMR 邊界之間的鏈路失效 (大多數時候只是模組本身),這使得 HMR 更新不管應用程式的大小如何都能維持高速。
Vite 還利用 HTTP 標頭來加速整頁重載 (這也是利用瀏覽器來幫忙):原碼的模組請求是通過 304 Not Modified
請求,而依賴模組則通過 Cache-Control: max-age=31536000,immutable
來快取,這樣快取確保這些依賴不會再次觸及開發伺服器,更新時速度也就大幅提升。
Vite 的未來發展藍圖
在社群中,經常有人會問說,為什麼 Vite 不用 esbuild 作為打包工具,而是選擇 Rollup? 畢竟 esbuild 速度更快。主要原因是 Rollup 的插件 API 設計比較靈活,Vite 團隊認為 Rollup 在性能和靈活性之間提供了更好的平衡。
雖然,Rollup 目前效能不如 esbuild,但這件事正在被改善中。目前最新版的 Rollup 已經把解析器換成了 SWC。此外官方團隊也正在進行用 Rust 語言重寫 Rollup (很有趣地,這個改寫的版本被叫做 Rolldown)。
先前 Vite 創作者尤雨溪,在 2023 年 ViteConf 有宣布改寫的 Rolldown 計畫,等 Rolldown 完成後,將會在 Vite 中取代 Rollup,來大幅提升構建性能,並消除開發和構建之間的不一致性。