1. 程式人生 > >[Vue原始碼分析] 模板的編譯

[Vue原始碼分析] 模板的編譯

最近小組有個關於vue原始碼分析的分享會,提前準備一下…
前言:
Vue有兩個版本:Runtime + Compiler 、 Runtime only ,前者是包含編譯程式碼的版本,後者不包含編譯程式碼,編譯過程需要藉助webpack的vue-loader,接下來分析的是Runtime + Compiler版本,編譯過程感覺挺複雜的,所以下邊只是大概分析一下整個流程,原始碼理解直接寫在原始碼中。

模板的編譯

之前分析Virtual DOM的時候我們分析過模板到真實 DOM 渲染的過程,中間有一個環節把模板編譯成render函式,這個過程稱作編譯。

(1)編譯入口追蹤

編譯入口在分析virtual dom的時候已經提及過,位於src/platforms/web/entry-runtime-with-compiler.js

下的$mount方法(紅框部分):
在這裡插入圖片描述
可以看到compileToFunctions方法將模板編譯成了render以及staticRenderFns函式,通過物件的解構賦值獲取結果並賦值給options

compileToFunctions在 src/platforms/web/compiler/index.js中定義,如下:
在這裡插入圖片描述
發現這是一個賦值的過程,值由createCompiler產生,createCompiler方法在 src/compiler/index.js 中定義,如下:
在這裡插入圖片描述
這個方法是createCompilerCreator的返回值,createCompilerCreator中傳入的引數是一個方法,在此暫時不看引數方法裡邊的內容,因為這裡只是一個呼叫,並沒有執行什麼。先看看createCompilerCreator

的定義,它的定義在src/compiler/create-compiler.js中,如下:
在這裡插入圖片描述
createCompilerCreator返回一個 createCompiler 的函式,該函式返回的是一個物件,包括 compilecompileToFunctionscompileToFunctions對應的就是$mount方法中呼叫的compileToFunctions 方法,它是 createCompileToFunctionFn 方法的返回值,我們接下來看一下 createCompileToFunctionFn 方法,它的定義在 src/compiler/to-function/js 中,這個函式便是compileToFunctions
的最終定義,如下:
在這裡插入圖片描述
compileToFunctions中編譯的核心是compile的呼叫,compile是通過引數的方式傳入的,也就是createCompilerCreator中定義的compile,現在我們返回去看compile是什麼,之前的圖摺疊了,現在展開如下:
在這裡插入圖片描述
compile前部分程式碼都是在處理配置引數,實際上的編譯過程只有程式碼中紅框部分,也就是呼叫baseCompile方法,這個方法時呼叫createCompilerCreator時通過引數的方式傳入的,也就是之前介紹到的當時說暫時不用看的程式碼,重新上一次圖:
在這裡插入圖片描述
編譯入口追蹤到這裡告一段落(vue專案支援多個平臺,不同平臺配置不一樣,所以入口繞了很多個圈)。可以看到最終主要步驟有三步,一步是通過parse生成ast樹,一步是optimize,看英文意思及傳入引數應該是對ast樹進行優化的一個過程,一步是呼叫generate生成code,接下來看看這幾個步驟都幹了什麼。

(2) parse

編譯過程首先就是對模板做解析,生成 AST語法樹,我們可以在parse後debugger一下,看看AST語法樹的模樣。

新建一個vue demo,在main.js做如下配置:
在這裡插入圖片描述
然後再原始碼中parse後打個斷點,或者列印一下:
在這裡插入圖片描述
可以看到,控制檯輸入了以下內容,這便是ast的結構:
在這裡插入圖片描述
至此ast的結構我們瞭解了,但ast是怎麼生成的呢?接下來我們看看parse是什麼。parse的定義位於src/compiler/parser/index.js中。
這個過程很複雜,概括地說就是把template模板字串轉換成AST樹,它是一種用JavaScript物件的形式來描述整個模板。整個parse的過程是利用正則表示式順序解析模板,當解析到開始標籤、閉合標籤、文字的時候都會分別執行對應的回撥函式,最終達到構造AST樹的目的。
這塊內容很多,挑幾個點講一下:

①:解析標籤

解析HTML是通過呼叫parseHML方法完成的,它的定義位於src/compiler/parser/html-parser
呼叫:
在這裡插入圖片描述
定義:
在這裡插入圖片描述
這個方法也是比較複雜,整體來說就是迴圈解析template ,用頂部預先定義好的一堆正則表示式做正則匹配,處理開始標籤和結束標籤,對於不同情況分別進行不同的處理,直到解析完畢。

比較關鍵的一個點事在匹配的過程中會利用 advance 函式不斷前進整個模板字串,直到字串末尾。
在這裡插入圖片描述

舉個例子:

假如模板本來是這個樣子的,可以理解為一個佇列,目前佇列的索引為0:
在這裡插入圖片描述
通過呼叫advance(4)後,通過html.substring(4),佇列的索引就變成了4,當前待解析模板就變成了如下:
在這裡插入圖片描述

②:解析文字、表示式

除了處理開始標籤和結束標籤,還需要處理文字,通過parseText實現,原始碼位於src/compiler/parser/text-parsre.js
在這裡插入圖片描述
回頭看看之前打印出來的ast,可以看到打上標記的表示式:
在這裡插入圖片描述

③:解析指令,以v-for為例

v-for指令解析的入口是processFor方法,該方法定義位於src/compiler/parser/index.js,此方法依賴parseFor以及extend方法,共同完成v-for的解析。
在這裡插入圖片描述
大概思路:通過正則匹配v-for,匹配到了就呼叫parseFor方法,parseFor方法位於同文件中:
在這裡插入圖片描述
這個方法也是通過正則匹配,分別匹配出不同的內容,比如’v-for="(item, index) in data"’,匹配出來的res.for是data,res.alias是item,res.iterator是index,隨後返回解析出來的 結果,傳給processFor中的res常量res,接著呼叫extend方法完成解析,extend方法的定義位於/Users/xiaoqiang/Desktop/GitHub/vue/src/shared/util.js中,如下:
在這裡插入圖片描述
其實extend只是一個迴圈,把之前解析出來的屬性迴圈出來並掛載到傳入的ast物件上。