依賴預(yù)構(gòu)建?
當(dāng)你首次啟動(dòng) vite 時(shí),Vite 在本地加載你的站點(diǎn)之前預(yù)構(gòu)建了項(xiàng)目依賴。默認(rèn)情況下,它是自動(dòng)且透明地完成的。
原因?
這就是 Vite 執(zhí)行時(shí)所做的“依賴預(yù)構(gòu)建”。這個(gè)過程有兩個(gè)目的:
-
CommonJS 和 UMD 兼容性: 在開發(fā)階段中,Vite 的開發(fā)服務(wù)器將所有代碼視為原生 ES 模塊。因此,Vite 必須先將以 CommonJS 或 UMD 形式提供的依賴項(xiàng)轉(zhuǎn)換為 ES 模塊。
在轉(zhuǎn)換 CommonJS 依賴項(xiàng)時(shí),Vite 會(huì)進(jìn)行智能導(dǎo)入分析,這樣即使模塊的導(dǎo)出是動(dòng)態(tài)分配的(例如 React),具名導(dǎo)入(named imports)也能正常工作:
js
// 符合預(yù)期 import React, { useState } from 'react' -
性能: 為了提高后續(xù)頁(yè)面的加載性能,Vite將那些具有許多內(nèi)部模塊的 ESM 依賴項(xiàng)轉(zhuǎn)換為單個(gè)模塊。
有些包將它們的 ES 模塊構(gòu)建為許多單獨(dú)的文件,彼此導(dǎo)入。例如,
lodash-es有超過 600 個(gè)內(nèi)置模塊!當(dāng)我們執(zhí)行import { debounce } from 'lodash-es'時(shí),瀏覽器同時(shí)發(fā)出 600 多個(gè) HTTP 請(qǐng)求!即使服務(wù)器能夠輕松處理它們,但大量請(qǐng)求會(huì)導(dǎo)致瀏覽器端的網(wǎng)絡(luò)擁塞,使頁(yè)面加載變得明顯緩慢。通過將
lodash-es預(yù)構(gòu)建成單個(gè)模塊,現(xiàn)在我們只需要一個(gè)HTTP請(qǐng)求!
注意
依賴預(yù)構(gòu)建僅適用于開發(fā)模式,并使用 esbuild 將依賴項(xiàng)轉(zhuǎn)換為 ES 模塊。在生產(chǎn)構(gòu)建中,將使用 @rollup/plugin-commonjs。
自動(dòng)依賴搜尋?
如果沒有找到現(xiàn)有的緩存,Vite 會(huì)掃描您的源代碼,并自動(dòng)尋找引入的依賴項(xiàng)(即 "bare import",表示期望從 node_modules 中解析),并將這些依賴項(xiàng)作為預(yù)構(gòu)建的入口點(diǎn)。預(yù)打包使用 esbuild 執(zhí)行,因此通常速度非??臁?/p>
在服務(wù)器已經(jīng)啟動(dòng)后,如果遇到尚未在緩存中的新依賴項(xiàng)導(dǎo)入,則 Vite 將重新運(yùn)行依賴項(xiàng)構(gòu)建過程,并在需要時(shí)重新加載頁(yè)面。
Monorepo 和鏈接依賴?
在一個(gè) monorepo 啟動(dòng)中,該倉(cāng)庫(kù)中的某個(gè)包可能會(huì)成為另一個(gè)包的依賴。Vite 會(huì)自動(dòng)偵測(cè)沒有從 node_modules 解析的依賴項(xiàng),并將鏈接的依賴視為源碼。它不會(huì)嘗試打包被鏈接的依賴,而是會(huì)分析被鏈接依賴的依賴列表。
然而,這需要被鏈接的依賴被導(dǎo)出為 ESM 格式。如果不是,那么你可以在配置里將此依賴添加到 optimizeDeps.include 和 build.commonjsOptions.include 這兩項(xiàng)中。
js
export default defineConfig({
optimizeDeps: {
include: ['linked-dep'],
},
build: {
commonjsOptions: {
include: [/linked-dep/, /node_modules/],
},
},
})
當(dāng)對(duì)鏈接的依賴進(jìn)行更改時(shí),請(qǐng)使用 --force 命令行選項(xiàng)重新啟動(dòng)開發(fā)服務(wù)器,以使更改生效。
重復(fù)刪除
由于鏈接的依賴項(xiàng)解析方式不同,傳遞依賴項(xiàng)(transitive dependencies)可能會(huì)被錯(cuò)誤地去重,從而在運(yùn)行時(shí)出現(xiàn)問題。如果遇到此問題,請(qǐng)使用 npm pack 命令來(lái)修復(fù)它。
自定義行為?
有時(shí)候默認(rèn)的依賴啟發(fā)式算法(discovery heuristics)可能并不總是理想的。如果您想要明確地包含或排除依賴項(xiàng),可以使用 optimizeDeps 配置項(xiàng) 來(lái)進(jìn)行設(shè)置。
optimizeDeps.include 或 optimizeDeps.exclude 的一個(gè)典型使用場(chǎng)景,是當(dāng) Vite 在源碼中無(wú)法直接發(fā)現(xiàn) import 的時(shí)候。例如,import 可能是插件轉(zhuǎn)換的結(jié)果。這意味著 Vite 無(wú)法在初始掃描時(shí)發(fā)現(xiàn) import —— 只能在文件被瀏覽器請(qǐng)求并轉(zhuǎn)換后才能發(fā)現(xiàn)。這將導(dǎo)致服務(wù)器在啟動(dòng)后立即重新打包。
include 和 exclude 都可以用來(lái)處理這個(gè)問題。如果依賴項(xiàng)很大(包含很多內(nèi)部模塊)或者是 CommonJS,那么你應(yīng)該包含它;如果依賴項(xiàng)很小,并且已經(jīng)是有效的 ESM,則可以排除它,讓瀏覽器直接加載它。
你也可以使用 optimizeDeps.esbuildOptions 選項(xiàng) 來(lái)進(jìn)一步自定義 esbuild。例如,添加一個(gè) esbuild 插件來(lái)處理依賴項(xiàng)中的特殊文件。
緩存?
文件系統(tǒng)緩存?
Vite 將預(yù)構(gòu)建的依賴項(xiàng)緩存到 node_modules/.vite 中。它會(huì)基于以下幾個(gè)來(lái)源來(lái)決定是否需要重新運(yùn)行預(yù)構(gòu)建步驟:
- 包管理器的鎖文件內(nèi)容,例如
package-lock.json,yarn.lock,pnpm-lock.yaml,或者bun.lockb; - 補(bǔ)丁文件夾的修改時(shí)間;
-
vite.config.js中的相關(guān)字段; -
NODE_ENV的值。
只有在上述其中一項(xiàng)發(fā)生更改時(shí),才需要重新運(yùn)行預(yù)構(gòu)建。
如果出于某些原因你想要強(qiáng)制 Vite 重新構(gòu)建依賴項(xiàng),你可以在啟動(dòng)開發(fā)服務(wù)器時(shí)指定 --force 選項(xiàng),或手動(dòng)刪除 node_modules/.vite 緩存目錄。
瀏覽器緩存?
已預(yù)構(gòu)建的依賴請(qǐng)求使用 HTTP 頭 max-age=31536000, immutable 進(jìn)行強(qiáng)緩存,以提高開發(fā)期間頁(yè)面重新加載的性能。一旦被緩存,這些請(qǐng)求將永遠(yuǎn)不會(huì)再次訪問開發(fā)服務(wù)器。如果安裝了不同版本的依賴項(xiàng)(這反映在包管理器的 lockfile 中),則會(huì)通過附加版本查詢自動(dòng)失效。如果你想通過本地編輯來(lái)調(diào)試依賴項(xiàng),您可以:
- 通過瀏覽器開發(fā)工具的 Network 選項(xiàng)卡暫時(shí)禁用緩存;
- 重啟 Vite 開發(fā)服務(wù)器指定
--force選項(xiàng),來(lái)重新構(gòu)建依賴項(xiàng); - 重新載入頁(yè)面。