深入了解Vite:依賴預(yù)構(gòu)建原理
前言
前面我們有提到Vite在開(kāi)發(fā)階段,提倡的是一個(gè)no-bundle的理念,不必與webpack那樣需要先將整個(gè)項(xiàng)目進(jìn)行打包構(gòu)建。但是no-bundle的理念只適合源代碼部分(我們自己寫的代碼),vite會(huì)將項(xiàng)目中的所有模塊分為依賴與源碼兩部分。
依賴:指的是一些不會(huì)變動(dòng)的一些模塊,如:node_modules中的第三方依賴,這部分代碼vite會(huì)在啟動(dòng)本地服務(wù)之前使用esbuild進(jìn)行預(yù)構(gòu)建。esbuild 使用 Go 編寫,比使用 JavaScript 編寫的打包器預(yù)構(gòu)建依賴快 10-100 倍。
源碼:指的是我們自己開(kāi)發(fā)時(shí)寫的那部分代碼,這部分代碼可能會(huì)經(jīng)常變動(dòng),并且一般不會(huì)同時(shí)加載所有源代碼。
所以總結(jié)來(lái)說(shuō):no-bundle是針對(duì)源碼的,而預(yù)構(gòu)建是針對(duì)第三方依賴的
使用預(yù)構(gòu)建的原因
主要有以下兩點(diǎn):
- commonJS 與 UMD兼容:因?yàn)閂ite在開(kāi)發(fā)階段主要是依賴瀏覽器原生ES模塊化規(guī)范,所以無(wú)論是我們的源代碼還是第三方依賴都得符合ESM的規(guī)范,但是目前并不是所有第三方依賴都有ESM的版本,所以需要對(duì)第三方依賴進(jìn)行預(yù)編譯,將它們轉(zhuǎn)換成EMS規(guī)范的產(chǎn)物。
比如React,它就沒(méi)有ESM的版本,所以在使用Vite時(shí)需要預(yù)構(gòu)建
圖片
- 性能:為了提高后續(xù)頁(yè)面的加載性能,Vite將那些具有許多內(nèi)部模塊的 ESM 依賴項(xiàng)轉(zhuǎn)換為單個(gè)模塊。
比如常用的loads-es
我們引入lodash-es工具包中的debounce方法,此時(shí)它理想狀態(tài)應(yīng)該是只發(fā)出一個(gè)請(qǐng)求
import { debounce } from 'lodash-es'
事實(shí)也是這樣:
圖片
但這是預(yù)構(gòu)建的功勞,如果我們對(duì)lodash-es關(guān)閉預(yù)構(gòu)建呢?
vite配置文件加上如下代碼,再來(lái)試試:
// vite.config.js
optimizeDeps: {
exclude: ['lodash-es']
}
圖片
可以看到,此時(shí)發(fā)起了600多個(gè)請(qǐng)求,這是因?yàn)閘odash-es 有超過(guò) 600 個(gè)內(nèi)置模塊!
vite通過(guò)將 lodash-es 預(yù)構(gòu)建成單個(gè)模塊,只需要發(fā)起一個(gè)HTTP請(qǐng)求!可以很大程度地提高加載性能
由于Vite的預(yù)構(gòu)建是基于性能優(yōu)異的Esbuild來(lái)完成的,所以并不會(huì)造成明顯的打包性能問(wèn)題
開(kāi)啟預(yù)構(gòu)建
默認(rèn)配置
一般來(lái)說(shuō),Vite幫我們默認(rèn)開(kāi)啟了預(yù)構(gòu)建
圖片
預(yù)構(gòu)建產(chǎn)物會(huì)存放在:node_modules/.vite/deps
圖片
里面會(huì)有一個(gè)_metadata.json的文件,這里保存著已經(jīng)預(yù)構(gòu)建過(guò)的依賴信息
對(duì)于預(yù)構(gòu)建產(chǎn)物的請(qǐng)求,Vite會(huì)設(shè)置為強(qiáng)緩存,有效時(shí)間為1年,對(duì)于有效期內(nèi)的請(qǐng)求,會(huì)直接使用緩存內(nèi)容
圖片
如果只有HTTP強(qiáng)緩存肯定也不行,如果用戶更新了依賴版本,在緩存過(guò)期之前,瀏覽器拿到的一直是舊版本的內(nèi)容。
所以Vite對(duì)本地文件也設(shè)置了緩存判斷,如果下面幾個(gè)地方任意一個(gè)地方有變動(dòng),Vite將會(huì)對(duì)依賴進(jìn)行重新預(yù)構(gòu)建:
- 項(xiàng)目依賴dependencies變更
圖片
- 各種包管理器的lock文件變更
圖片
- optimizeDeps配置內(nèi)容變更
圖片
自定義配置
entries
默認(rèn)情況下,Vite會(huì)抓取項(xiàng)目中的index.html來(lái)檢測(cè)需要預(yù)構(gòu)建的依賴
optimizeDeps: {
entries: ['index.html']
}
如果指定了 build.rollupOptions.input,Vite 將轉(zhuǎn)而去抓取這些入口點(diǎn)。
exclude
排除需要預(yù)構(gòu)建的依賴項(xiàng)
optimizeDeps: {
exclude: ['lodash-es']
}
include
默認(rèn)情況下,不在 node_modules 中的依賴不會(huì)被預(yù)構(gòu)建。使用此選項(xiàng)可強(qiáng)制選擇預(yù)構(gòu)建的依賴項(xiàng)。
optimizeDeps: {
include: ['lodash-es']
}
預(yù)構(gòu)建流程
還是從源碼入手,在啟動(dòng)服務(wù)的過(guò)程中會(huì)執(zhí)行一個(gè)initDepsOptimizer表示初始化依賴優(yōu)化
圖片
接著找到定義initDepsOptimizer方法的地方
圖片
在這里會(huì)執(zhí)行createDepsOptimizer方法,再接著找到定義createDepsOptimizer的地方
圖片
這里首先會(huì)去執(zhí)行l(wèi)oadCachedDepOptimizationMetadata用于獲取本地緩存中的metadata數(shù)據(jù)
圖片
該函數(shù)會(huì)在獲取到_metadata.json文件內(nèi)容之后去對(duì)比lock文件hash以及配置文件optimizeDeps內(nèi)容,如果一樣說(shuō)明預(yù)構(gòu)建緩存沒(méi)有任何改變,無(wú)需重新預(yù)構(gòu)建,直接使用上次預(yù)構(gòu)建緩存即可。
如果沒(méi)有緩存時(shí)則需要進(jìn)行依賴掃描:
圖片
這里主要是會(huì)調(diào)用scanImport方法,從名字也能看出該方法應(yīng)該是通過(guò)掃描項(xiàng)目中的import語(yǔ)句來(lái)得到需要預(yù)編譯的依賴
圖片
最終會(huì)返回一個(gè)prepareEsbuildScanner方法:
圖片
最后該方法中會(huì)使用esbuild對(duì)掃描出來(lái)的依賴項(xiàng)進(jìn)行預(yù)編譯。