Vite 是怎樣利用 Esbuild 來提升性能的 ?
前言
在上一篇 爲什麼有人說 vite 快,有人卻說 vite 慢?[1] 中,我們提到過開發模式下使用 Vite 會有首屏性能下降的負面效果。之所以會造成首屏性能下降,一方面是 dev server 需要完成預構建纔可以響應首屏請求;另一方面是需要對請求文件做實時轉換。
也許有的同學會問,是不是針對這兩個方面做優化,就可以提升首屏性能呢?原則上這樣是沒有問題的,而且 Vite 也是這麼做的。爲了能提升性能,Vite 另闢蹊徑的藉助了 Esbuild 能快速完成項目打包、文件轉換的能力來進行預構建、內容轉換,效果非常好。
今天小編就通過本文和大家一起聊一聊 Vite 是怎樣利用 Esbuild 來提升性能的。
初探 Esbuild
首先,小編先帶大家簡單瞭解一下 Esbuild,其官方地址是: Esbuild[9]。
什麼是 Esbuild
Esbuild 是一款基於 Go 語言開發的 javascript 打包工具,最大的一個特徵就是快。
通過官網提供的一張圖,我們可以清晰的看到 Esbuild 的表現是多麼優秀:
同樣規模的項目,使用 Esbuild 可以將打包速度提升 10 - 100 倍,這對廣大一直飽受 Webpack 緩慢打包速度折磨的開發人員來說,簡直就是福音。
而 Esbuild 之所以能這麼快,主要原因有兩個:
-
Go語言開發,可以多線程打包,代碼直接編譯成機器碼;Webpack一直被人詬病構建速度慢,主要原因是在打包構建過程中,存在大量的resolve、load、transform、parse操作 (詳見 爲什麼有人說 vite 快,有人卻說 vite 慢?- 快速的冷啓動 [10] ),而這些操作通常是通過javascript代碼來執行的。要知道,javascript並不是什麼高效的語言,在執行過程中要先編譯後執行,還是單線程並且不能利用多核cpu優勢,和Go語言相比,效率很低。 -
可充分利用多核
cpu優勢;
關鍵 API - transfrom & build
Esbuild 並不複雜。它對外提供了兩個 API - transform 和 build,使用起來非常簡單。
transfrom,轉換的意思。通過這個 api,我們可以將 ts、jsx、tsx 等格式的內容轉化爲 js。transfrom 只負責文件內容轉換,並不會生成一個新的文件。
build,構建的意思,根據指定的單個或者多個入口,分析依賴,並使用 loader 將不同格式的內容轉化爲 js 內容,生成一個或多個 bundle 文件。
這兩個 API 的使用方式:
const res = await esbuild.transform(code, options) // 將 code 轉換爲指定格式的內容
esbuild.build(options) // 打包構建
複製代碼
關於使用 transform、build 需要傳入的具體配置項,本文就不詳細說明了,官網對這一塊兒有很詳細的說明,感興趣的同學可以去官網 - simple-options[11]、Advanced options[12] 看看,也可以自己動手試試。
plugin
和 Webpack、Rollup 等構建工具一樣,Esbuild 也提供了供外部使用的 plugin,使得我們可以介入構建打包過程。
在這裏要說明一點,只有
build這個API的入參中可以配置plugin,transform不可以。
一個標準的 plugin 的標準格式如下:
let customerPlugin = {
name: 'xxx',
setup: (build) => {
build.onResolve({ filter: '', namespace: '' }, args => { ...});
build.onLoad({ filter: '', namespace: ''}, args => { ... });
build.onStart(() => { ... });
build.onEnd((result) => { ... });
}
}
複製代碼
其中,setup 可以幫助我們在 build 的各個過程中註冊 hook。
Esbuild 對外提供的 hook 比較簡單,總共 4 個:
-
onResolve, 解析url時觸發,可自定義url如何解析。如果callback有返回path,後面的同類型callback將不會執行。所有的onResolvecallback將按照對應的plugin註冊的順序執行。 -
onLoad, 加載模塊時觸發,可自定義模塊如何加載。如果callback有返回contents,後面的同類型callback將不會執行。所有的onLoadcallback將按照對應的plugin註冊的順序執行。 -
onStart, 每次build開始時都會觸發,沒有入參,因此不具有改變build的能力。多個plugin的onStart並行執行。 -
onEnd, 每次build結束時會觸發,入參爲build的結果,可對result做修改。所有的的onEnd將按照對應的plugin註冊的順序執行。
正是有了 onResolve、onLoad、onStart、onEnd,我們可以在 build 過程中的解析 url、加載模塊內容、構建開始、構建結束階段介入,做自定義操作。
Esbuild 在 Vite 中的巧妙使用
瞭解了 Esbuild 的基本用法以後,小編就帶大家一起來看看 Vite 是怎麼利用 Esbuild 來做預構建和內容轉換的。
預構建
先來回顧一下爲什麼要做預構建。
原因有兩點:
-
將非
ESM規範的代碼轉換爲符合ESM規範的代碼; -
將第三方依賴內部的多個文件合併爲一個,減少
http請求數量;
要完成預構建,最關鍵的兩點是找到項目中所有的第三份依賴和對第三方依賴做合併、轉換。藉助 Esbuild,Vite 很輕鬆的實現了這兩個訴求。
-
尋找第三方依賴
尋找第三方依賴的過程非常簡單,分爲兩步:
和
Webpack、Rollup、Parcel等構建工具一樣,Esbuild在做打包構建時也要構建模塊依賴圖 -module graph(具體過程可參考 爲什麼有人說 vite 快,有人卻說 vite 慢?- 快速的冷啓動 [13] 中Webpack構建module graph)。在構建
module graph時,第一步就是解析模塊的絕對路徑,這個時候就會觸發onResolve hook。在onResolve hook觸發時,會傳入模塊的路徑。根據模塊的路徑,我們就可以判斷出這個模塊是第三方依賴還是業務代碼。舉個 🌰,
// main.tsx import react from 'react'; import CustomeComponent from './components/CustomeComponent'; ... 複製代碼在對
main.tsx的內容做parser操作時,能知道main.tsx依賴react和CustomeComponent,然後開始解析react和CustomeComponent。解析
react、CustomeComponent時,會觸發onResolve hook,入參分別爲'react'和'./components/CustomeComponent'。根據入參,我們可以很清楚的區分'react'是第三方依賴,'./components/CustomeComponet'是業務代碼。這樣,
esbuild完成構建,項目中的第三方依賴也就收集完畢了。所有的第三方依賴會收集到一個deps列表中。
-
定義一個帶
onResolve hook和onLoad hook的esbuild plugin; -
執行
esbuild的build方法做打包構建;
-
合併、轉換第三方依賴
知道了項目中的第三方依賴以後,再做合併、轉換操作就非常簡單了。
這一步,
Vite直接通過esbuild提供的build方法,指定entryPoints爲收集到的第三方依賴,format爲esm,再做一次打包構建。這一次,會對第三方依賴做合併、轉換操作。打包構建完成以後,再把構建內容輸出到
/node_modules/.vite/deps下。
這樣,通過兩次 esbuild.build,預構建就完成了。
middlewares 中內容轉換
Vite 中源文件的轉換是在 dev server 啓動以後通過 middlewares 實現的。
當瀏覽器發起請求以後,dev sever 會通過相應的 middlewares 對請求做處理,然後將處理以後的內容返回給瀏覽器。
middlewares 對源文件的處理,分爲 resolve、load、transform、parser 四個過程:
-
resolve- 解析url,找到源文件的絕對路徑; -
load- 加載源文件。如果是第三方依賴,直接將預構建內容返回給瀏覽器;如果是業務代碼,繼續transform、parser。 -
transfrom- 對源文件內容做轉換,即ts->js,less->css等。轉換完成的內容可以直接返回給瀏覽器了。 -
parser- 對轉換以後的內容做分析,找到依賴模塊,對依賴模塊做預轉換 -pre transform操作,即重複1-4。pre transform是Vite做的一個優化點。預轉換的內容會先做緩存,等瀏覽器發起請求以後,如果已經完成轉換,直接將緩存的內容返回給瀏覽器。
Vite 在處理步驟 3 時,是通過 esbuild.transform 實現的,對比 Webpack 使用各個 loader 處理源文件,那是非常簡單、快捷的。
結束語
有一說一,Vite 通過 Esbuild 來優化預構建和內容轉換的思路非常棒,這給我們以後處理同類問題提供瞭解決方案,真心給尤大點 👍🏻。
另外除了使用 Esbuild, Vite 內部還有很多可以拿出來單獨講的優化技巧,這個以後有機會小編可以再給大家詳細講講。
最後說一句,如果本文對大家有幫助,那就給小編點個 👍🏻 吧。大家的支持,是小編前進的動力,😄。
關於本文
作者:0o 華仔 o0
https://juejin.cn/post/7129802255120728100
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/s0jtejAf0lLVsWMYmDaPuw