Vue3 編譯流程-源碼解析
前言:
Vue3 發(fā)布已經(jīng)很長(zhǎng)一段時(shí)間了,最近也有機(jī)會(huì)在公司項(xiàng)目中用上了 Vue3 + TypeScript + Vite 的技術(shù)棧,所以閑暇之余抽空也在抽空閱讀 Vue3 的源碼。本著好記性不如爛筆頭的想法,在閱讀源碼時(shí)順便記錄了一些筆記,也希望能爭(zhēng)取寫一些源碼閱讀筆記,幫助每個(gè)想看源碼但可能存在困難的同學(xué)減少理解成本。
Vue2.x 的源碼我也有過(guò)一些簡(jiǎn)單的閱讀,自 Vue3 重構(gòu)后,Vue 項(xiàng)目的目錄結(jié)構(gòu)也發(fā)生了很大的變化,各個(gè)功能模塊被分別放入了 packages 目錄下,職責(zé)更加清晰,通過(guò)目錄名就可以一目了然。今天將從 Vue 的入口文件開始,看看聲明了一個(gè) Vue 的單文件之后是如何被 compile-core 編譯核心模塊編譯成渲染函數(shù)的。
為了大家的閱讀方便,以及控制文章篇幅,我會(huì)把閱讀源碼時(shí)不太需要在意的邏輯進(jìn)行折疊,或者通過(guò)注釋 /* 忽略邏輯 */ 這樣的標(biāo)識(shí)進(jìn)行忽略處理。
我個(gè)人是不太喜歡在看源碼分析文章時(shí)一上來(lái)就懟出一大段代碼,這容易讓沒(méi)閱讀的同學(xué)有點(diǎn)懵逼。所以這個(gè)系列的文章我會(huì)盡量對(duì)關(guān)鍵的代碼畫出一張流程圖。目的還是一個(gè),幫助大家降低理解成本,同時(shí)也讓各位同學(xué)在下次自主閱讀時(shí)有張流程圖能參考。
1、解讀Vue 入口文件
我們會(huì)先從一個(gè) Vue 對(duì)象的入口來(lái)開始我們的源碼閱讀, packages/vue/index.ts 。這個(gè)入口文件的代碼比較簡(jiǎn)單,只有一個(gè) compileToFunction 函數(shù),但函數(shù)體內(nèi)的內(nèi)容卻又比較關(guān)鍵,所以先看一張圖,來(lái)理解這個(gè)函數(shù)體究竟完成了哪些事情。

在看完流程圖之后,我們來(lái)對(duì)照代碼一起看,我相信大部分同學(xué)在此時(shí)可能對(duì)下發(fā)圖片中的代碼一目了然了。

直接跳過(guò)所有代碼,看文件的末尾 35 行,調(diào)用了 registerRuntiomCompiler 函數(shù),將 compileToFunction 函數(shù)作為參數(shù)傳入,這行代碼即對(duì)應(yīng)流程圖的起始,通過(guò)依賴注入的方式,將 compile 函數(shù)注入至 runtime 運(yùn)行時(shí)中,依賴注入是一種比較巧妙的解耦方式,此時(shí)運(yùn)行時(shí)再調(diào)用 compile 編譯函數(shù),就是在調(diào)用當(dāng)前的 compileToFunction 函數(shù)了。
再看代碼中的第 17 行,調(diào)用了 compile-dom 庫(kù)提供的 compile 函數(shù),從返回值中解構(gòu)出了 code 變量。這個(gè)就是編譯器執(zhí)行之后生成的編譯結(jié)果,code 是編譯結(jié)果的其中一個(gè)參數(shù),是一個(gè)代碼字符串。比如
<template>
<div>
Hello World
</div>
</template>
這個(gè)簡(jiǎn)單的模板,在經(jīng)過(guò)編譯后,code 返回的字符串為
const _Vue = Vue return function render(_ctx, _cache) { with (_ctx) { const { openBlock: _openBlock, createBlock: _createBlock } = _Vue return (_openBlock(), _createBlock("div", null, "Hello World")) } }
這個(gè)神奇的 compile 函數(shù)內(nèi)部的奧妙在之后我會(huì)詳細(xì)講解。
在拿到這個(gè)這個(gè)代碼字符串的結(jié)果后,我們?cè)夙樦a往下看,第 25 行聲明了一個(gè) render 變量,并且將生成的代碼字符串 code 作為參數(shù)傳入了 new Function 構(gòu)造函數(shù)。這就是流程圖中的倒數(shù)第二步,生成了 render 函數(shù)??梢詫⑽曳旁谏厦娴?code 字符串格式化,能夠發(fā)現(xiàn) render 函數(shù)是一個(gè)柯里化的函數(shù),返回了一個(gè)函數(shù),函數(shù)內(nèi)部通過(guò) with 來(lái)擴(kuò)展作用域鏈。
而最后入口文件返回了 render 變量,并且順手緩存了 render 函數(shù)。
上方源碼的第 1 行,我們看到入口文件創(chuàng)建了一個(gè) compileCache 對(duì)象,用以緩存 compileToFunction 函數(shù)生成的 render 函數(shù),將 template 參數(shù)作為緩存的 key, 并在 11 行的位置有一個(gè) if 分支做緩存的判斷,如果該模板之前被緩存過(guò),則不再進(jìn)行編譯,直接返回緩存中的 render 函數(shù),以此提高性能。
至此 package/vue/index.ts 的入口文件就解讀完了。相信大家也都看出來(lái)了,最有意思的部分就是調(diào)用 compile 函數(shù)編譯出了代碼字符串,所以接下來(lái)我將圍繞 compile 函數(shù)來(lái)接著嘮。compile 函數(shù)牽扯到 compile-dom 和 compile-core 兩個(gè)模塊,本篇文章我只會(huì)解讀關(guān)鍵流程。細(xì)節(jié)分析的話會(huì)放在后續(xù)文章中。一起來(lái)看一下 compile 的運(yùn)行流程:
2、compile 的運(yùn)行流程

compile 函數(shù)內(nèi)部直接返回 baseCompile 函數(shù)的結(jié)果,而 baseCompile 函數(shù)在執(zhí)行過(guò)程中會(huì)生成 AST 抽象語(yǔ)法樹,并調(diào)用 transform 對(duì) 每個(gè) AST 節(jié)點(diǎn)進(jìn)行處理,例如轉(zhuǎn)換vOn、v-if、v-for 等指令,最后將處理后的 AST 抽象語(yǔ)法樹通過(guò) generate 函數(shù)生成之前提及的代碼字符串,并返回編譯結(jié)果,至此 compile 函數(shù)執(zhí)行完畢。明白了大體的流程后,接著來(lái)看源碼。

compile 函數(shù)的源碼路徑是 packages/compiler-dom/src/index.ts, 我們看到在 compile 的函數(shù)體內(nèi),直接 return 了 baseCompile 的處理結(jié)果。而 baseCompile 的源碼路徑是 packages/compiler-core/src/compile.ts 。為什么會(huì)有 baseCompile 這樣的命名呢?因?yàn)?code> compile-core 是編譯的核心模塊,接受外部的參數(shù)來(lái)按照規(guī)則完成編譯,而 compile-dom 是專門處理瀏覽器場(chǎng)景下的編譯,在這個(gè)模塊下導(dǎo)出的 compile 函數(shù)是入口文件真正接收的編譯函數(shù)。而 compile-dom 中的 compile 函數(shù)相對(duì) baseCompile 也是更高階的一個(gè)編譯器。例如當(dāng) Vue 在 weex 在 iOS 或者 Android 這些 Native App 中工作時(shí),compile-dom 可能會(huì)被相關(guān)的移動(dòng)端編譯庫(kù)來(lái)取代。
順著往下一起看一下 baseCompile 函數(shù):

先從函數(shù)聲明中來(lái)看,baseCompile 接收 template 模板以及上層高階編譯器中處理過(guò) options 編譯選項(xiàng),最終返回一個(gè) CodegenResult 類型的編譯結(jié)果。
export interface CodegenResult {
code: string
preamble: string
ast: RootNode
map?: RawSourceMap
}
通過(guò) CodegenResult 的接口聲明能清晰的看到返回結(jié)果中存在 code 代碼字符串、處理后的 AST 抽象語(yǔ)法樹,以及 sourceMap。
看上方源碼的第 12 行,判斷 template 模板是否為字符串,如果是的話則會(huì)對(duì)字符串進(jìn)行解析,否則直接將 template 作為 AST 。其實(shí)我們平時(shí)在寫的單文件 vue 代碼,都是以字符串的形式傳遞進(jìn)去的。
接下來(lái)源碼是 16 行調(diào)用了 transform 函數(shù),以及傳入了指令轉(zhuǎn)換、節(jié)點(diǎn)轉(zhuǎn)換等工具函數(shù),對(duì)由模板生成的 AST 進(jìn)行轉(zhuǎn)換。
最終的 32 行位置,我們將轉(zhuǎn)換好的 AST 傳入 generate,生成 CodegenResult 類型的返回結(jié)果。
在 compile-core 模塊中,AST 解析、transform、codegen、compile、parse 這些函數(shù)都是一個(gè)單獨(dú)的小模塊,內(nèi)部的實(shí)現(xiàn)都非常精妙,在編譯器的后續(xù)文章中,會(huì)逐個(gè)進(jìn)行介紹。
本文通過(guò)從入口文件開始,對(duì)編譯的大體流程進(jìn)行解釋,希望可以幫助大家在閱讀編譯器這個(gè)模塊的代碼時(shí)能有一個(gè)清晰的流程概念,配合流程圖食用更香喲。
到此這篇關(guān)于Vue3 編譯流程-源碼解析的文章就介紹到這了,更多相關(guān)Vue3 編譯流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue中uni-app 實(shí)現(xiàn)小程序登錄注冊(cè)功能
這篇文章主要介紹了uni-app 實(shí)現(xiàn)小程序登錄注冊(cè)功能,文中給大家介紹了實(shí)現(xiàn)思路,以及vuex和本地緩存的區(qū)別,需要的朋友可以參考下2019-10-10
解決Vue+Element ui開發(fā)中碰到的IE問(wèn)題
今天小編就為大家分享一篇解決Vue+Element ui開發(fā)中碰到的IE問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
配置vite.confgi.ts無(wú)法使用require問(wèn)題以及解決
這篇文章主要介紹了配置vite.confgi.ts無(wú)法使用require問(wèn)題以及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
vue如何基于el-table實(shí)現(xiàn)多頁(yè)多選及翻頁(yè)回顯過(guò)程
在最近的一個(gè)項(xiàng)目中我需要實(shí)現(xiàn)表格的翻頁(yè),并且還要實(shí)現(xiàn)全選、多選功能,下面這篇文章主要給大家介紹了關(guān)于vue如何基于el-table實(shí)現(xiàn)多頁(yè)多選及翻頁(yè)回顯過(guò)程的相關(guān)資料,需要的朋友可以參考下2022-12-12
uniapp Vue3中如何解決web/H5網(wǎng)頁(yè)瀏覽器跨域的問(wèn)題
存在跨域問(wèn)題的原因是因?yàn)闉g覽器的同源策略,也就是說(shuō)前端無(wú)法直接發(fā)起跨域請(qǐng)求,同源策略是一個(gè)基礎(chǔ)的安全策略,但是這也會(huì)給uniapp/Vue開發(fā)者在部署時(shí)帶來(lái)一定的麻煩,這篇文章主要介紹了在uniapp Vue3版本中如何解決web/H5網(wǎng)頁(yè)瀏覽器跨域的問(wèn)題,需要的朋友可以參考下2024-06-06
vscode配置@路徑提示方式,并解決vue文件內(nèi)失效的問(wèn)題
這篇文章主要介紹了vscode配置@路徑提示方式,并解決vue文件內(nèi)失效的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
elementui實(shí)現(xiàn)標(biāo)簽頁(yè)與菜單欄聯(lián)動(dòng)的示例代碼
多級(jí)聯(lián)動(dòng)是一種常見的交互方式,本文主要介紹了elementui實(shí)現(xiàn)標(biāo)簽頁(yè)與菜單欄聯(lián)動(dòng)的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06

