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
將不會執行。所有的onResolve
callback
將按照對應的plugin
註冊的順序執行。 -
onLoad
, 加載模塊時觸發,可自定義模塊如何加載。如果callback
有返回contents
,後面的同類型callback
將不會執行。所有的onLoad
callback
將按照對應的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