Vite Learnings Tiefenanalyse „Dependency Scanning'
In diesem Artikel werden die Implementierungsdetails des Abhängigkeitsscans in Vite ausführlich erläutert. Das endgültige Scanergebnis ist ein Objekt, das die Namen mehrerer Module enthält Produkte verwendet werden.
Wenn wir Vite zum ersten Mal ausführen, führt Vite eine Abhängigkeitsvorerstellung durch, um kompatibel mit CommonJS und UMD zu sein und die Leistung zu verbessern. [Verwandte Empfehlungen: vuejs-Video-Tutorial]
Um Abhängigkeiten vorab zu erstellen, müssen Sie zunächst diese beiden Fragen verstehen:
Was ist der vorab erstellte Inhalt? / Welche Module müssen vorgefertigt werden?
Wie finde ich die Module, die vorgefertigt werden müssen?
Diese beiden Probleme hängen tatsächlich vom Inhalt und der Implementierungsmethode des Scannens ab.
In diesem Artikel werden die Implementierungsdetails des Abhängigkeitsscans ausführlich erläutert. Das endgültige Scanergebnis ist ein „Objekt, das die Namen mehrerer Module enthält“ und bezieht sich nicht auf den Vorerstellungsprozess oder die Verwendung der vorgefertigten Produkte. Wenn Sie an diesem Teil des Inhalts interessiert sind, können Sie mir folgen und auf weitere Artikel warten.
Verlassen Sie sich auf vorgefertigte InhalteIn einem Projekt gibt es viele Module, und nicht alle Module sind vorgefertigt.
Nur Bare-Import (Bare-Abhängigkeit) führt die Vorerstellung von Abhängigkeiten durchWas ist Bare-Import? Schauen Sie sich das folgende Beispiel direkt an auf diese Weise.
-ProjektsDas Folgende ist der Modulabhängigkeitsbaum eines gemeinsamen
Vue
- Die Ergebnisse des Abhängigkeitsscans sind wie folgt:
// vue 是 bare import import xxx from "vue" import xxx from "vue/xxx" // 以下不是裸依赖 import xxx from "./foo.ts" import xxx from "/foo.ts"
Nach dem Login kopieren- Warum ist nur Bare-Import vorgefertigt?
– Suchen Sie unter „node_modules“ im aktuellen Verzeichnis. Wenn nicht, gehen Sie zu „node_modules“ im oberen Verzeichnis, bis das Verzeichnis der Stammpfad ist und nicht weiter.
Bare Import ist im Allgemeinen ein von npm installiertes Modul, nicht der Code, den wir selbst geschrieben haben. Unter normalen Umständen wird er nicht geändert. Daher ist es von Vorteil, diesen Teil zu erstellen Modul im Voraus.Im Gegenteil, wenn der vom Entwickler geschriebene Code vorgefertigt ist und das Projekt in Chunk-Dateien gepackt ist,
und ihn dann in Chunk-Dateien packen . Dieser Vorgang wirkt sich auf die Leistung aus.
Werden Module unter Monorepo auch vorgefertigt sein?
Nein. Denn im Fall von Monorepo sind einige Module zwar reine Importmodule, diese Module werden jedoch auch von den Entwicklern selbst geschrieben und sind keine Module von Drittanbietern, sodass Vite keine Vorerstellung für diese Module durchführt. Tatsächlich ermittelt Vite
, ob sich der tatsächliche Pfad des Moduls in node_modules befindet: Das Modul, dessen tatsächlicher Pfad in node_modules liegt, wird vorgefertigt. Dies ist ein Modul eines Drittanbieters
Das Modul, dessen Der tatsächliche Pfad ist nicht in node_modules enthalten. Dies beweist, dass Module über Dateiverknüpfungen mit node_modules verknüpft sind (die Implementierungsmethode von Monorepo). Es handelt sich um Codes, die von den Entwicklern selbst geschrieben wurden und kein Vorab-Scannen von Abhängigkeiten durchführen ImplementierungsideenWerfen wir einen Blick auf diesen Modulabhängigkeitsbaum:
Um alle bloßen Importe zu scannen, müssen Sie den gesamten Abhängigkeitsbaum durchlaufen, was eine
Tiefendurchquerung des Baumes beinhaltetWann Wenn wir über das Durchqueren von Bäumen sprechen, achten wir normalerweise auf diese beiden Punkte:
- Wann hört man auf, in die Tiefe zu gehen?
- Wie gehe ich mit Blattknoten um?
Situation, in der der aktuelle Blattknoten die eingehende Durchquerung nicht fortsetzen muss:
Wenn Sie auf einen leeren Importknoten stoßen, zeichnen Sie die Abhängigkeit auf, und es besteht keine Notwendigkeit, die eingehende Durchquerung fortzusetzenWann Wenn Sie auf andere JS-unabhängige Module wie CSS, SVG usw. stoßen, ist es nicht erforderlich, sie weiter eingehend zu durchlaufen, da es sich nicht um JS-Codes handelt das Ergebnis des Abhängigkeitsscans
. Die Implementierungsidee, sich auf das Scannen zu verlassen, ist eigentlich sehr einfach zu verstehen, aber die eigentliche Verarbeitung ist nicht einfach.Werfen wir einen Blick auf die Verarbeitung von Blattknoten:
- Bare-Import
kann anhand der Modul-ID beurteilt werden. Ein Modul, dessen Modul-ID kein Pfad ist, ist ein Bare-Import. Wenn Sie auf diese Module stoßen, zeichnen Sie die Abhängigkeiten auf und durchlaufen Sie sie nicht mehr in der Tiefe.
- Andere Module, die nichts mit JS zu tun haben
können anhand des Suffixnamens des Moduls beurteilt werden Wenn Sie beispielsweise auf ein Modul von *.css
stoßen, erfolgt keine Verarbeitung erforderlich und es ist keine eingehende Durchquerung*.css
的模块,无需任何处理,不再深入遍历。
- JS 模块
要获取 JS 代码中依赖的子模块,就需要将代码转成 AST,获取其中 import 语句引入的模块,或者正则匹配出所有 import 的模块,然后继续深入遍历这些模块
- HTML 类型模块
这类模块比较复杂,例如 HTML 或 Vue,里面有一部分是 JS,需要把这部分 JS 代码提取出来,然后按 JS 模块进行分析处理,继续深入遍历这些模块。这里只需要关心 JS 部分,其他部分不会引入模块。
具体实现
我们已经知道了依赖扫描的实现思路,思路其实不复杂,复杂的是处理过程,尤其是 HTML、Vue 等模块的处理。
Vite 这里用了一种比较巧妙的办法 —— 用 esbuild 工具打包
为什么可以用 esbuild 打包代替深度遍历的过程?
本质上打包过程也是个深度遍历模块的过程,其替代的方式如下:
深度遍历 | esbuild 打包 |
---|---|
叶子节点的处理 | esbuild 可以对每个模块(叶子节点)进行解析和加载 可以通过插件对这两个过程进行扩展,加入一些特殊的逻辑 例如将 html 在加载过程中转换为 js |
不深入处理模块 | esbuild 可以在解析过程,指定当前解析的模块为 external 则 esbuild 不再深入解析和加载该模块。 |
深入遍历模块 | 正常解析模块(什么都不做,esbuild 默认行为),返回模块的文件真实路径 |
这块暂时看不懂没有关系,后面会有例子
各类模块的处理
例子 | 处理 | |
---|---|---|
bare import | vue |
在解析过程中,将裸依赖保存到 deps 对象中,设置为 external |
其他 JS 无关的模块 | less文件 |
在解析过程中,设置为 external |
JS 模块 | ./mian.ts |
正常解析和加载即可,esbuild 本身能处理 JS |
html 类型模块 |
index.html 、app.vue erforderlich. |
JS-Modul |

🎜Warum kann Esbuild-Verpackung verwendet werden, um den Deep-Traversal-Prozess zu ersetzen? 🎜🎜Im Wesentlichen🎜der Verpackungsprozess ist auch ein Prozess der Tiefendurchquerung von Modulen🎜 Die alternative Methode lautet wie folgt:🎜
Tiefendurchquerung | esbuild-Verpackung | 🎜||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Beispiel | Verarbeitung | 🎜|||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
例子 | 处理 | |
---|---|---|
bare import | vue |
在解析过程中,将裸依赖保存到 deps 对象中,设置为 external |
其他 JS 无关的模块 | less文件 |
在解析过程中,设置为 external |
JS 模块 | ./mian.ts |
正常解析和加载即可,esbuild 本身能处理 JS |
html 类型模块 |
index.html 、app.vue
|
在加载过程中,将这些模块加载成 JS |
JS 模块
esbuild 本身就能处理 JS 语法,因此 JS 是不需要任何处理的,esbuild 能够分析出 JS 文件中的依赖,并进一步深入处理这些依赖。
其他 JS 无关的模块
// 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 无关的模块
这部分处理非常简单,直接匹配,然后 external 就行了
bare import
build.onResolve( { // 第一个字符串为字母或 @,且第二个字符串不是 : 冒号。如 vite、@vite/plugin-vue // 目的是:避免匹配 window 路径,如 D:/xxx filter: /^[\w@][^:]/ }, async ({ path: id, importer, pluginData }) => { // depImports 为 if (depImports[id]) { return externalUnlessEntry({ path: id }) } // 将模块路径转换成真实路径,实际上调用 container.resolveId const resolved = await resolve(id, importer, { custom: { depScan: { loader: pluginData?.htmlType?.loader } } }) // 如果解析到路径,证明找得到依赖 // 如果解析不到路径,则证明找不到依赖,要记录下来后面报错 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 等 // 因为有可能裸依赖的入口是 css 等非 JS 模块的文件 else if (isScannable(resolved)) { // 真实路径不在 node_modules 中,则证明是 monorepo,实际上代码还是在用户的目录中 // 是用户自己写的代码,不应该 external return { path: path.resolve(resolved) } } else { // 其他模块不可扫描,直接忽略,external return { path, external: true } } } else { // 解析不到依赖,则记录缺少的依赖 missing[id] = normalizePath(importer) } } )
- 如果文件在 node_modules 中,才认为是 bare import,记录当前模块
- 文件不在 node_modules 中,则是 monorepo,是用户自己写的代码
- 如果这些代码 isScanable 可扫描(即含有 JS 代码),则继续深入处理
- 其他非 JS 模块,external
html 类型模块
如: index.html
、app.vue
const htmlTypesRE = /\.(html|vue|svelte|astro)$/ // html types: 提取 script 标签 build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { // 将模块路径,转成文件的真实路径 const resolved = await resolve(path, importer) if (!resolved) return // 不处理 node_modules 内的 if (resolved.includes('node_modules'){ return } return { path: resolved, // 标记 namespace 为 html namespace: 'html' } })
解析过程很简单,只是用于过滤掉一些不需要的模块,并且标记 namespace 为 html
真正的处理在加载阶段:
// 正则,匹配例子: <script></script> const scriptModuleRE = /(<script>]*type\s*=\s*(?:"module"|'module')[^>]*>)(.*?)<\/script>/gims // 正则,匹配例子: <script></script> export const scriptRE = /(<script>]*>|>))(.*?)<\/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,因为只有 module 类型的 script 才能使用 import const regex = isHtml ? scriptModuleRE : scriptRE // 重置正则表达式的索引位置,因为同一个正则表达式对象,每次匹配后,lastIndex 都会改变 // regex 会被重复使用,每次都需要重置为 0,代表从第 0 个字符开始正则匹配 regex.lastIndex = 0 // load 钩子返回值,表示加载后的 js 代码 let js = '' let scriptId = 0 let match: RegExpExecArray | null // 匹配源码的 script 标签,用 while 循环,因为 html 可能有多个 script 标签 while ((match = regex.exec(raw))) { // openTag: 它的值的例子: <script> // content: script 标签的内容 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 钩子可以设置 应的 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()) { // 内联的 script,它的内容要做成虚拟模块 // 缓存虚拟模块的内容 // 一个 html 可能有多个 script,用 scriptId 区分 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>
加载阶段的主要做有以下流程:
- 读取文件源码
- 正则匹配出所有的 script 标签,并对每个 script 标签的内容进行处理
- 外部 script,改为用 import 引入
- 内联 script,改为引入虚拟模块,并将对应的虚拟模块的内容缓存到 script 对象。
- 最后返回转换后的 js
srcMatch[1] || srcMatch[2] || srcMatch[3] 是干嘛?
我们来看看匹配的表达式:
const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im
因为 src 可以有以下三种写法:
- src="xxx"
- src='xxx'
- src=xxx
三种情况会出现其中一种,因此是三个捕获组
虚拟模块是如何加载成对应的 script 代码的?
export const virtualModuleRE = /^virtual-module:.*/ // 匹配所有的虚拟模块,namespace 标记为 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' } }) // 之前的内联 script 内容,保存到 script 对象,加载虚拟模块的时候取出来 build.onLoad({ filter: /.*/, namespace: 'script' }, ({ path }) => { return scripts[path] })
虚拟模块的加载很简单,直接从 script 对象中,读取之前缓存起来的内容即可。
这样之后,我们就可以把 html 类型的模块,转换成 JS 了
扫描结果
下面是一个 depImport 对象的例子:
{ "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:模块的真实路径
总结
依赖扫描是预构建前的一个非常重要的步骤,这决定了 Vite 需要对哪些依赖进行预构建。
本文介绍了 Vite 会对哪些内容进行依赖预构建,然后分析了实现依赖扫描的基本思路 —— 深度遍历依赖树,并对各种类型的模块进行处理。然后介绍了 Vite 如何巧妙的使用 esbuild 实现这一过程。最后对这部分的源码进行了解析:
- 最复杂的就是 html 类型模块的处理,需要使用虚拟模块;
- 当遇到 bare import 时,需要判断是否在 node_modules 中,在的才记录依赖,然后 external。
- 其他 JS 无关的模块就直接 external
- JS 模块由于 esbuild 本身能处理,不需要做任何的特殊操作
最后获取到的 depImport 是一个记录依赖以及其真实路径的对象
Das obige ist der detaillierte Inhalt vonVite Learnings Tiefenanalyse „Dependency Scanning'. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

AI Hentai Generator
Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

Heiße Werkzeuge

Notepad++7.3.1
Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1
Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6
Visuelle Webentwicklungstools

SublimeText3 Mac-Version
Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Heiße Themen

Durch die Verwendung von ECharts in Vue können Sie Ihrer Anwendung ganz einfach Datenvisualisierungsfunktionen hinzufügen. Zu den spezifischen Schritten gehören: Installieren von ECharts- und Vue ECharts-Paketen, Einführung von ECharts, Erstellen von Diagrammkomponenten, Konfigurieren von Optionen, Verwenden von Diagrammkomponenten, Anpassen von Diagrammen an Vue-Daten, Hinzufügen interaktiver Funktionen und Verwenden erweiterter Verwendungsmöglichkeiten.

Frage: Welche Rolle spielt der Exportstandard in Vue? Detaillierte Beschreibung: Exportstandard definiert den Standardexport der Komponente. Beim Importieren werden Komponenten automatisch importiert. Vereinfachen Sie den Importvorgang, verbessern Sie die Übersichtlichkeit und vermeiden Sie Konflikte. Wird häufig zum Exportieren einzelner Komponenten verwendet, wobei sowohl benannte als auch Standardexporte verwendet werden und globale Komponenten registriert werden.

Die Kartenfunktion von Vue.js ist eine integrierte Funktion höherer Ordnung, die ein neues Array erstellt, wobei jedes Element das transformierte Ergebnis jedes Elements im ursprünglichen Array ist. Die Syntax lautet map(callbackFn), wobei callbackFn jedes Element im Array als erstes Argument empfängt, optional den Index als zweites Argument, und einen Wert zurückgibt. Die Kartenfunktion ändert das ursprüngliche Array nicht.

onMounted ist ein Lebenszyklus-Hook für die Komponentenmontage in Vue. Seine Funktion besteht darin, Initialisierungsvorgänge durchzuführen, nachdem die Komponente im DOM bereitgestellt wurde, z. B. das Abrufen von Referenzen auf DOM-Elemente, das Festlegen von Daten, das Senden von HTTP-Anforderungen, das Registrieren von Ereignis-Listenern usw. Es wird nur einmal aufgerufen, wenn die Komponente gemountet wird. Wenn Sie Vorgänge ausführen müssen, nachdem die Komponente aktualisiert wurde oder bevor sie zerstört wurde, können Sie andere Lebenszyklus-Hooks verwenden.

Es gibt zwei Möglichkeiten, Module in Vue.js zu exportieren: Export und Export Default. export wird zum Exportieren benannter Entitäten verwendet und erfordert die Verwendung von geschweiften Klammern; export default wird zum Exportieren von Standardentitäten verwendet und erfordert keine geschweiften Klammern. Beim Importieren müssen per Export exportierte Entitäten ihre Namen verwenden, während per Exportstandard exportierte Entitäten implizit verwendet werden können. Es wird empfohlen, die Export-Standardeinstellung für Module zu verwenden, die mehrmals importiert werden müssen, und die Export-Standardeinstellung für Module, die nur einmal exportiert werden müssen.

Vue-Hooks sind Rückruffunktionen, die Aktionen bei bestimmten Ereignissen oder Lebenszyklusphasen ausführen. Dazu gehören Lebenszyklus-Hooks (wie beforeCreate, mount, beforeDestroy), Event-Handling-Hooks (wie click, input, keydown) und benutzerdefinierte Hooks. Hooks verbessern die Komponentenkontrolle, reagieren auf Komponentenlebenszyklen, verarbeiten Benutzerinteraktionen und verbessern die Wiederverwendbarkeit von Komponenten. Um Hooks zu verwenden, definieren Sie einfach die Hook-Funktion, führen Sie die Logik aus und geben Sie einen optionalen Wert zurück.

Vue.js-Ereignismodifikatoren werden verwendet, um bestimmte Verhaltensweisen hinzuzufügen, darunter: Verhindern von Standardverhalten (.prevent), Stoppen von Ereignisblasen (.stop), Einmaliges Ereignis (.once), Erfassen von Ereignissen (.capture), Passives Abhören von Ereignissen (.passive), Adaptiv Modifikator (.self)Schlüsselmodifikator (.key)

onMounted in Vue entspricht der useEffect-Lebenszyklusmethode in React mit einem leeren Abhängigkeitsarray [], das unmittelbar nach dem Mounten der Komponente im DOM ausgeführt wird.
