本篇內(nèi)容主要講解“Vite依賴掃描怎么實(shí)現(xiàn)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Vite依賴掃描怎么實(shí)現(xiàn)”吧!
創(chuàng)新互聯(lián)專注于企業(yè)成都營銷網(wǎng)站建設(shè)、網(wǎng)站重做改版、精河網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、成都h5網(wǎng)站建設(shè)、商城系統(tǒng)網(wǎng)站開發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為精河等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
當(dāng)我們首次運(yùn)行 Vite 的時(shí)候,Vite 會(huì)執(zhí)行依賴預(yù)構(gòu)建,目的是為了兼容CommonJS 和 UMD,以及提升性能。
要對(duì)依賴進(jìn)行預(yù)構(gòu)建,首先要搞清楚這兩個(gè)問題:
預(yù)構(gòu)建的內(nèi)容是什么?/ 哪些模塊需要進(jìn)行預(yù)構(gòu)建?
如何找到需要預(yù)構(gòu)建的模塊?
這兩個(gè)問題,其實(shí)就是依賴掃描的內(nèi)容以及實(shí)現(xiàn)方式。
一個(gè)項(xiàng)目中,存在非常多的模塊,并不是所有模塊都會(huì)被預(yù)構(gòu)建。只有 bare import(裸依賴)會(huì)執(zhí)行依賴預(yù)構(gòu)建
什么是 bare import ?
直接看下面這個(gè)例子
// vue 是 bare import import xxx from "vue" import xxx from "vue/xxx" // 以下不是裸依賴 import xxx from "./foo.ts" import xxx from "/foo.ts"
可以簡(jiǎn)單的劃分一下:
用名稱去訪問的模塊是裸模塊
用路徑去訪問的模塊,不是 bare import
實(shí)際上 Vite 也是這么判斷的。
下面是一個(gè)常見的 Vue 項(xiàng)目的模塊依賴樹

依賴掃描的結(jié)果如下:
[ "vue", "axios" ]
為什么只對(duì) bare import 進(jìn)行預(yù)構(gòu)建?
Node.js 定義了 bare import 的尋址機(jī)制—— 在當(dāng)前目錄下的 node_modules 下尋找,找不到則往上一級(jí)目錄的 node_modules,直到目錄為根路徑,不能再往上。
bare import 一般是 npm 安裝的模塊,是第三方的模塊,不是我們自己寫的代碼,一般情況下是不會(huì)被修改的,因此對(duì)這部分的模塊提前執(zhí)行構(gòu)建,有利于提升性能。
相反,如果對(duì)開發(fā)者寫的代碼執(zhí)行預(yù)構(gòu)建,將項(xiàng)目打包成 chunk 文件,當(dāng)開發(fā)者修改代碼時(shí),就需要重新執(zhí)行構(gòu)建,再打包成 chunk 文件,這個(gè)過程反而會(huì)影響性能。
monorepo 下的模塊也會(huì)被預(yù)構(gòu)建嗎?
不會(huì)。因?yàn)?monorepo 的情況下,部分模塊雖然是 bare import,但這些模塊也是開發(fā)者自己寫的,不是第三方模塊,因此 Vite 沒有對(duì)該部分的模塊執(zhí)行預(yù)構(gòu)建。
實(shí)際上,Vite 會(huì)判斷模塊的實(shí)際路徑,是否在 node_modules 中:
實(shí)際路徑在 node_modules 的模塊會(huì)被預(yù)構(gòu)建,這是第三方模塊
實(shí)際路徑不在 node_modules 的模塊,證明該模塊是通過文件鏈接,鏈接到 node_modules 內(nèi)的(monorepo 的實(shí)現(xiàn)方式),是開發(fā)者自己寫的代碼,不執(zhí)行預(yù)構(gòu)建
實(shí)現(xiàn)思路
我們?cè)賮砜纯催@棵模塊依賴樹:

要掃描出所有的 bare import,就需要遍歷整個(gè)依賴樹,這就涉及到了樹的深度遍歷
當(dāng)我們?cè)谟懻摌涞谋闅v時(shí),一般會(huì)關(guān)注這兩點(diǎn):
什么時(shí)候停止深入?
如何處理葉子節(jié)點(diǎn)?

當(dāng)前葉子節(jié)點(diǎn)不需要繼續(xù)深入遍歷的情況:
當(dāng)遇到 bare import 節(jié)點(diǎn)時(shí),記錄下該依賴,就不需要繼續(xù)深入遍歷
遇到其他 JS 無關(guān)的模塊,如 CSS、SVG 等,因?yàn)椴皇?JS 代碼,因此也不需要繼續(xù)深入遍歷
當(dāng)所有的葉子節(jié)點(diǎn)遍歷完成后,記錄的 bare import 對(duì)象,就是依賴掃描的結(jié)果。
依賴掃描的實(shí)現(xiàn)思路其實(shí)非常容易理解,但實(shí)際的處理就不簡(jiǎn)單了。
我們來看看葉子節(jié)點(diǎn)的處理:
bare import
可以通過模塊 id 判斷,模塊 id 不為路徑的模塊,就是 bare import。遇到這些模塊則記錄依賴,不再深入遍歷。
其他 JS 無關(guān)的模塊
可以通過模塊的后綴名判斷,例如遇到 *.css 的模塊,無需任何處理,不再深入遍歷。
JS 模塊
要獲取 JS 代碼中依賴的子模塊,就需要將代碼轉(zhuǎn)成 AST,獲取其中 import 語句引入的模塊,或者正則匹配出所有 import 的模塊,然后繼續(xù)深入遍歷這些模塊
HTML 類型模塊
這類模塊比較復(fù)雜,例如 HTML 或 Vue,里面有一部分是 JS,需要把這部分 JS 代碼提取出來,然后按 JS 模塊進(jìn)行分析處理,繼續(xù)深入遍歷這些模塊。這里只需要關(guān)心 JS 部分,其他部分不會(huì)引入模塊。

具體實(shí)現(xiàn)
我們已經(jīng)知道了依賴掃描的實(shí)現(xiàn)思路,思路其實(shí)不復(fù)雜,復(fù)雜的是處理過程,尤其是 HTML、Vue 等模塊的處理。
Vite 這里用了一種比較巧妙的辦法 —— 用 esbuild 工具打包
為什么可以用 esbuild 打包代替深度遍歷的過程?
本質(zhì)上打包過程也是個(gè)深度遍歷模塊的過程,其替代的方式如下:
| 深度遍歷 | esbuild 打包 |
|---|---|
| 葉子節(jié)點(diǎn)的處理 | esbuild 可以對(duì)每個(gè)模塊(葉子節(jié)點(diǎn))進(jìn)行解析和加載 可以通過插件對(duì)這兩個(gè)過程進(jìn)行擴(kuò)展,加入一些特殊的邏輯 例如將 html 在加載過程中轉(zhuǎn)換為 js |
| 不深入處理模塊 | esbuild 可以在解析過程,指定當(dāng)前解析的模塊為 external 則 esbuild 不再深入解析和加載該模塊。 |
| 深入遍歷模塊 | 正常解析模塊(什么都不做,esbuild 默認(rèn)行為),返回模塊的文件真實(shí)路徑 |
這塊暫時(shí)看不懂沒有關(guān)系,后面會(huì)有例子

各類模塊的處理
| 例子 | 處理 | |
|---|---|---|
| bare import | vue | 在解析過程中,將裸依賴保存到 deps 對(duì)象中,設(shè)置為 external |
| 其他 JS 無關(guān)的模塊 | less文件 | 在解析過程中,設(shè)置為 external |
| JS 模塊 | ./mian.ts | 正常解析和加載即可,esbuild 本身能處理 JS |
| html 類型模塊 | index.html、app.vue | 在加載過程中,將這些模塊加載成 JS |
最后 dep 對(duì)象中收集到的依賴就是依賴掃描的結(jié)果,而這次 esbuild 的打包產(chǎn)物,其實(shí)是沒有任何作用的,在依賴掃描過程中,我們只關(guān)心每個(gè)模塊的處理過程,不關(guān)心構(gòu)建產(chǎn)物
用 Rollup 處理可以嗎?
其實(shí)也可以,打包工具基本上都會(huì)有解析和加載的流程,也能對(duì)模塊進(jìn)行 external
但是 esbuild 性能更好
這類文件有 html、vue 等。之前我們提到了要將它們轉(zhuǎn)換成 JS,那么到底要如何轉(zhuǎn)換呢?

由于依賴掃描過程,只關(guān)注引入的 JS 模塊,因此可以直接丟棄掉其他不需要的內(nèi)容,直接取其中 JS
html 類型文件(包括 vue)的轉(zhuǎn)換,有兩種情況:
每個(gè)外部 script,會(huì)直接轉(zhuǎn)換為 import 語句,引入外部 script
每個(gè)內(nèi)聯(lián) script,其內(nèi)容將會(huì)作為虛擬模塊被引入。
什么是虛擬模塊?
是模塊的內(nèi)容并非直接從磁盤中讀取,而是編譯時(shí)生成。
舉個(gè)例子,src/main.ts 是磁盤中實(shí)際存在的文件,而 virtual-module:D:/project/index.html?id=0 在磁盤中是不存在的,需要借助打包工具(如 esbuild),在編譯過程生成。
為什么需要虛擬模塊?
因?yàn)橐粋€(gè) html 類型文件中,允許有多個(gè) script 標(biāo)簽,多個(gè)內(nèi)聯(lián)的 script 標(biāo)簽,其內(nèi)容無法處理成一個(gè) JS 文件(因?yàn)榭赡軙?huì)有命名沖突等原因)
既然無法將多個(gè)內(nèi)聯(lián) script,就只能將它們分散成多個(gè)虛擬模塊,然后分別引入了。
依賴掃描的入口
下面是掃描依賴的入口函數(shù)(為了便于理解,有刪減和修改):
import { build } from 'esbuild'
export async function scanImports(config: ResolvedConfig): Promise<{
deps: Record<string, string>
missing: Record<string, string>
}> {
// 將項(xiàng)目中所有的 html 文件作為入口,會(huì)排除 node_modules
let entries: string[] = await globEntries('**/*.html', config)
// 掃描到的依賴,會(huì)放到該對(duì)象
const deps: Record<string, string> = {}
// 缺少的依賴,用于錯(cuò)誤提示
const missing: Record<string, string> = {}
// esbuild 掃描插件,這個(gè)是重點(diǎn)!!!
const plugin = esbuildScanPlugin(config, container, deps, missing, entries)
// 獲取用戶配置的 esbuild 自定義配置,沒有配置就是空的
const { plugins = [], ...esbuildOptions } =
config.optimizeDeps?.esbuildOptions ?? {}
await Promise.all(
// 入口可能不止一個(gè),分別用 esbuid 打包
entries.map((entry) =>
// esbuild 打包
build({
absWorkingDir: process.cwd(),
write: false,
entryPoints: [entry],
bundle: true,
format: 'esm',
// 使用插件
plugins: [...plugins, plugin],
...esbuildOptions
})
)
)
return {
deps,
missing
}
}主要流程如下:
將項(xiàng)目?jī)?nèi)所有的 html 作為入口文件(排除 node_modules)。
將每個(gè)入口文件,用 esbuild 進(jìn)行打包
這里的核心其實(shí)是 esbuildScanPlugin 插件的實(shí)現(xiàn),它定義了各類模塊(葉子節(jié)點(diǎn))的處理方式。
function esbuildScanPlugin(config, container, deps, missing, entries){}dep 、missing對(duì)象被當(dāng)做入?yún)魅耄诤瘮?shù)中,這兩個(gè)對(duì)象的內(nèi)容會(huì)在打包(插件運(yùn)行)過程中被修改
esbuild 插件
很多同學(xué)可能不知道 esbuild 插件是如何編寫的,這里簡(jiǎn)單介紹一下:
每個(gè)模塊都會(huì)經(jīng)過解析(resolve)和加載(load)的過程:
解析:將模塊路徑,解析成文件真實(shí)的路徑。例如 vue,會(huì)解析到實(shí)際 node_modules 中的 vue 的入口 js 文件
加載:根據(jù)解析的路徑,讀取文件的內(nèi)容

插件可以定制化解析和加載的過程,下面是一些插件示例代碼:
const plugin = {
name: 'xxx',
setup(build) {
// 定制解析過程,所有的 http/https 的模塊,都會(huì)被 external
build.onResolve({ filter: /^(https?:)?\/\// }, ({ path }) => ({
path,
external: true
}))
// 定制解析過程,給所有 less 文件 namespace: less 標(biāo)記
build.onResolve({ filter: /.*\.less/ }, args => ({
path: args.path,
namespace: 'less',
}))
// 定義加載過程:只處理 namespace 為 less 的模塊
build.onLoad({ filter: /.*/, namespace: 'less' }, () => {
const raw = fs.readFileSync(path, 'utf-8')
const content = // 省略 less 處理,將 less 處理成 css
return {
contents,
loader: 'css'
}
})
}
}通過 onResolve、onLoad 定義解析和加載過程
onResolve 的第一個(gè)參數(shù)為過濾條件,第二個(gè)參數(shù)為回調(diào)函數(shù),解析時(shí)調(diào)用,返回值可以給模塊做標(biāo)記,如 external、namespace(用于過濾),還需要返回模塊的路徑
每個(gè)模塊, onResolve 會(huì)被依次調(diào)用,直到回調(diào)函數(shù)返回有效的值,后面的不再調(diào)用。如果都沒有有效返回,則使用默認(rèn)的解析方式
onLoad 的第一個(gè)參數(shù)為過濾條件,第二個(gè)參數(shù)為回調(diào)函數(shù),加載時(shí)調(diào)用,可以讀取文件的內(nèi)容,然后進(jìn)行處理,最后返回加載的內(nèi)容。
每個(gè)模塊,onLoad 會(huì)被依次調(diào)用,直到回調(diào)函數(shù)返回有效的值,后面的不再調(diào)用。如果都沒有有效返回,則使用默認(rèn)的加載方式。
掃描插件的實(shí)現(xiàn)
function esbuildScanPlugin( config: ResolvedConfig, container: PluginContainer, depImports: Record<string, string>, missing: Record<string, string>, entries: string[] ): Plugin
部分參數(shù)解析:
config:Vite 的解析好的用戶配置
container:這里只會(huì)用到 container.resolveId 的方法,這個(gè)方法能將模塊路徑轉(zhuǎn)成真實(shí)路徑。
例如 vue 轉(zhuǎn)成 xxx/node_modules/dist/vue.esm-bundler.js。
depImports:用于存儲(chǔ)掃描到的依賴對(duì)象,插件執(zhí)行過程中會(huì)被修改
missing:用于存儲(chǔ)缺少的依賴的對(duì)象,插件執(zhí)行過程中會(huì)被修改
entries:存儲(chǔ)所有入口文件的數(shù)組
esbuild 默認(rèn)能將模塊路徑轉(zhuǎn)成真實(shí)路徑,為什么還要用
container.resolveId?
因?yàn)?Vite/Rollup 的插件,也能擴(kuò)展解析的流程,例如 alias 的能力,我們常常會(huì)在項(xiàng)目中用 @ 的別名代表項(xiàng)目的 src 路徑。
因此不能用 esbuild 原生的解析流程進(jìn)行解析。
container(插件容器)用于兼容 Rollup 插件生態(tài),用于保證 dev 和 production 模式下,Vite 能有一致的表現(xiàn)。感興趣的可查看《Vite 是如何兼容 Rollup 插件生態(tài)的》
這里 container.resolveId 會(huì)被再次包裝一成 resolve 函數(shù)(多了緩存能力)
const seen = new Map<string, string | undefined>()
const resolve = async (
id: string,
importer?: string,
options?: ResolveIdOptions
) => {
const key = id + (importer && path.dirname(importer))
// 如果有緩存,就直接使用緩存
if (seen.has(key)) {
return seen.get(key)
}
// 將模塊路徑轉(zhuǎn)成真實(shí)路徑
const resolved = await container.resolveId(
id,
importer && normalizePath(importer),
{
...options,
scan: true
}
)
// 緩存解析過的路徑,之后可以直接獲取
const res = resolved?.id
seen.set(key, res)
return res
}那么接下來就是插件的實(shí)現(xiàn)了,先回顧一下之前寫的各類模塊的處理:
| 例子 | 處理 | |
|---|---|---|
| bare import | vue | 在解析過程中,將裸依賴保存到 deps 對(duì)象中,設(shè)置為 external |
| 其他 JS 無關(guān)的模塊 | less文件 | 在解析過程中,設(shè)置為 external |
| JS 模塊 | ./mian.ts | 正常解析和加載即可,esbuild 本身能處理 JS |
| html 類型模塊 | index.html、app.vue | 在加載過程中,將這些模塊加載成 JS |
esbuild 本身就能處理 JS 語法,因此 JS 是不需要任何處理的,esbuild 能夠分析出 JS 文件中的依賴,并進(jìn)一步深入處理這些依賴。
// external urls
build.onResolve({ filter: /^(https?:)?\/\// }, ({ path }) => ({
path,
external: true
}))
// external css 等文件
build.onResolve(
{
filter: /\.(css|less|sass|scss|styl|stylus|pcss|postcss|json|wasm)$/
},
({ path }) => ({
path,
external: true
}
)
// 省略其他 JS 無關(guān)的模塊這部分處理非常簡(jiǎn)單,直接匹配,然后 external 就行了
build.onResolve(
{
// 第一個(gè)字符串為字母或 @,且第二個(gè)字符串不是 : 冒號(hào)。如 vite、@vite/plugin-vue
// 目的是:避免匹配 window 路徑,如 D:/xxx
filter: /^[\w@][^:]/
},
async ({ path: id, importer, pluginData }) => {
// depImports 為
if (depImports[id]) {
return externalUnlessEntry({ path: id })
}
// 將模塊路徑轉(zhuǎn)換成真實(shí)路徑,實(shí)際上調(diào)用 container.resolveId
const resolved = await resolve(id, importer, {
custom: {
depScan: { loader: pluginData?.htmlType?.loader }
}
})
// 如果解析到路徑,證明找得到依賴
// 如果解析不到路徑,則證明找不到依賴,要記錄下來后面報(bào)錯(cuò)
if (resolved) {
if (shouldExternalizeDep(resolved, id)) {
return externalUnlessEntry({ path: id })
}
// 如果模塊在 node_modules 中,則記錄 bare import
if (resolved.includes('node_modules')) {
// 記錄 bare import
depImports[id] = resolved
return {
path,
external: true
}
}
// isScannable 判斷該文件是否可以掃描,可掃描的文件有 JS、html、vue 等
// 因?yàn)橛锌赡苈阋蕾嚨娜肟谑?nbsp;css 等非 JS 模塊的文件
else if (isScannable(resolved)) {
// 真實(shí)路徑不在 node_modules 中,則證明是 monorepo,實(shí)際上代碼還是在用戶的目錄中
// 是用戶自己寫的代碼,不應(yīng)該 external
return {
path: path.resolve(resolved)
}
} else {
// 其他模塊不可掃描,直接忽略,external
return {
path,
external: true
}
}
} else {
// 解析不到依賴,則記錄缺少的依賴
missing[id] = normalizePath(importer)
}
}
)如果文件在 node_modules 中,才認(rèn)為是 bare import,記錄當(dāng)前模塊
文件不在 node_modules 中,則是 monorepo,是用戶自己寫的代碼
如果這些代碼 isScanable 可掃描(即含有 JS 代碼),則繼續(xù)深入處理
其他非 JS 模塊,external
如: index.html、app.vue
const htmlTypesRE = /\.(html|vue|svelte|astro)$/
// html types: 提取 script 標(biāo)簽
build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
// 將模塊路徑,轉(zhuǎn)成文件的真實(shí)路徑
const resolved = await resolve(path, importer)
if (!resolved) return
// 不處理 node_modules 內(nèi)的
if (resolved.includes('node_modules'){
return
}
return {
path: resolved,
// 標(biāo)記 namespace 為 html
namespace: 'html'
}
})解析過程很簡(jiǎn)單,只是用于過濾掉一些不需要的模塊,并且標(biāo)記 namespace 為 html
真正的處理在加載階段:

// 正則,匹配例子: <script type=module></script>
const scriptModuleRE = /(<script\b[^>]*type\s*=\s*(?:"module"|'module')[^>]*>)(.*?)<\/script>/gims
// 正則,匹配例子: <script></script>
export const scriptRE = /(<script\b(?:\s[^>]*>|>))(.*?)<\/script>/gims
build.onLoad(
{ filter: htmlTypesRE, namespace: 'html' },
async ({ path }) => {
// 讀取源碼
let raw = fs.readFileSync(path, 'utf-8')
// 去掉注釋,避免后面匹配到注釋
raw = raw.replace(commentRE, '<!---->')
const isHtml = path.endsWith('.html')
// scriptModuleRE: <script type=module></script>
// scriptRE: <script></script>
// html 模塊,需要匹配 module 類型的 script,因?yàn)橹挥?nbsp;module 類型的 script 才能使用 import
const regex = isHtml ? scriptModuleRE : scriptRE
// 重置正則表達(dá)式的索引位置,因?yàn)橥粋€(gè)正則表達(dá)式對(duì)象,每次匹配后,lastIndex 都會(huì)改變
// regex 會(huì)被重復(fù)使用,每次都需要重置為 0,代表從第 0 個(gè)字符開始正則匹配
regex.lastIndex = 0
// load 鉤子返回值,表示加載后的 js 代碼
let js = ''
let scriptId = 0
let match: RegExpExecArray | null
// 匹配源碼的 script 標(biāo)簽,用 while 循環(huán),因?yàn)?nbsp;html 可能有多個(gè) script 標(biāo)簽
while ((match = regex.exec(raw))) {
// openTag: 它的值的例子: <script type="module" src="xxx">
// content: script 標(biāo)簽的內(nèi)容
const [, openTag, content] = match
// 正則匹配出 openTag 中的 type 和 lang 屬性
const typeMatch = openTag.match(typeRE)
const type =
typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3])
const langMatch = openTag.match(langRE)
const lang =
langMatch && (langMatch[1] || langMatch[2] || langMatch[3])
// 跳過 type="application/ld+json" 和其他非 non-JS 類型
if (
type &&
!(
type.includes('javascript') ||
type.includes('ecmascript') ||
type === 'module'
)
) {
continue
}
// esbuild load 鉤子可以設(shè)置 應(yīng)的 loader
let loader: Loader = 'js'
if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') {
loader = lang
} else if (path.endsWith('.astro')) {
loader = 'ts'
}
// 正則匹配出 script src 屬性
const srcMatch = openTag.match(srcRE)
// 有 src 屬性,證明是外部 script
if (srcMatch) {
const src = srcMatch[1] || srcMatch[2] || srcMatch[3]
// 外部 script,改為用 import 用引入外部 script
js += `import ${JSON.stringify(src)}\n`
} else if (content.trim()) {
// 內(nèi)聯(lián)的 script,它的內(nèi)容要做成虛擬模塊
// 緩存虛擬模塊的內(nèi)容
// 一個(gè) html 可能有多個(gè) script,用 scriptId 區(qū)分
const key = `${path}?id=${scriptId++}`
scripts[key] = {
loader,
content,
pluginData: {
htmlType: { loader }
}
}
// 虛擬模塊的路徑,如 virtual-module:D:/project/index.html?id=0
const virtualModulePath = virtualModulePrefix + key
js += `export * from ${virtualModulePath}\n`
}
}
return {
loader: 'js',
contents: js
}
}
)加載階段的主要做有以下流程:
讀取文件源碼
正則匹配出所有的 script 標(biāo)簽,并對(duì)每個(gè) script 標(biāo)簽的內(nèi)容進(jìn)行處理
外部 script,改為用 import 引入
內(nèi)聯(lián) script,改為引入虛擬模塊,并將對(duì)應(yīng)的虛擬模塊的內(nèi)容緩存到 script 對(duì)象。
最后返回轉(zhuǎn)換后的 js
srcMatch[1] || srcMatch[2] || srcMatch[3] 是干嘛?
我們來看看匹配的表達(dá)式:
const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im
因?yàn)?src 可以有以下三種寫法:
src="xxx"
src='xxx'
src=xxx
三種情況會(huì)出現(xiàn)其中一種,因此是三個(gè)捕獲組
虛擬模塊是如何加載成對(duì)應(yīng)的 script 代碼的?
export const virtualModuleRE = /^virtual-module:.*/
// 匹配所有的虛擬模塊,namespace 標(biāo)記為 script
build.onResolve({ filter: virtualModuleRE }, ({ path }) => {
return {
// 去掉 prefix
// virtual-module:D:/project/index.html?id=0 => D:/project/index.html?id=0
path: path.replace(virtualModulePrefix, ''),
namespace: 'script'
}
})
// 之前的內(nèi)聯(lián) script 內(nèi)容,保存到 script 對(duì)象,加載虛擬模塊的時(shí)候取出來
build.onLoad({ filter: /.*/, namespace: 'script' }, ({ path }) => {
return scripts[path]
})虛擬模塊的加載很簡(jiǎn)單,直接從 script 對(duì)象中,讀取之前緩存起來的內(nèi)容即可。
這樣之后,我們就可以把 html 類型的模塊,轉(zhuǎn)換成 JS 了
掃描結(jié)果
下面是一個(gè) depImport 對(duì)象的例子:
{
"vue": "D:/app/vite/node_modules/.pnpm/vue@3.2.37/node_modules/vue/dist/vue.runtime.esm-bundler.js",
"vue/dist/vue.d.ts": "D:/app/vite/node_modules/.pnpm/vue@3.2.37/node_modules/vue/dist/vue.d.ts",
"lodash-es": "D:/app/vite/node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/lodash.js"
}key:模塊名稱
value:模塊的真實(shí)路徑
依賴掃描是預(yù)構(gòu)建前的一個(gè)非常重要的步驟,這決定了 Vite 需要對(duì)哪些依賴進(jìn)行預(yù)構(gòu)建。
本文介紹了 Vite 會(huì)對(duì)哪些內(nèi)容進(jìn)行依賴預(yù)構(gòu)建,然后分析了實(shí)現(xiàn)依賴掃描的基本思路 —— 深度遍歷依賴樹,并對(duì)各種類型的模塊進(jìn)行處理。然后介紹了 Vite 如何巧妙的使用 esbuild實(shí)現(xiàn)這一過程。最后對(duì)這部分的源碼進(jìn)行了解析:
最復(fù)雜的就是 html 類型模塊的處理,需要使用虛擬模塊;
當(dāng)遇到 bare import 時(shí),需要判斷是否在 node_modules 中,在的才記錄依賴,然后 external。
其他 JS 無關(guān)的模塊就直接 external
JS 模塊由于 esbuild 本身能處理,不需要做任何的特殊操作
最后獲取到的 depImport 是一個(gè)記錄依賴以及其真實(shí)路徑的對(duì)象
到此,相信大家對(duì)“Vite依賴掃描怎么實(shí)現(xiàn)”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
網(wǎng)站標(biāo)題:Vite依賴掃描怎么實(shí)現(xiàn)
新聞來源:http://chinadenli.net/article30/gsjipo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站制作、網(wǎng)站內(nèi)鏈、App開發(fā)、App設(shè)計(jì)、外貿(mào)網(wǎng)站建設(shè)、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)