[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
createCompilerCreator
返回一個 createCompiler
的函式,該函式返回的是一個物件,包括 compile
和compileToFunctions
, compileToFunctions
對應的就是$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物件上。