1. 程式人生 > 程式設計 >Vue3 編譯流程-原始碼解析

Vue3 編譯流程-原始碼解析

前言:

3 釋出已經很長一段時間了,最近也有機會在公司專案中用上了 Vue3 + TypeScript + Vite 的技術棧,所以閒暇之餘抽空也在抽空閱讀 Vue3 的原始碼。本著好記性不如爛筆頭的想法,在閱讀原始碼時順便記錄了一些筆記,也希望能爭取寫一些原始碼閱讀筆記,幫助每個想看原始碼但可能存在困難的同學減少理解成本。

Vue2.x 的原始碼我也有過一些簡單的閱讀,自 Vue3 重構後,Vue 專案的目錄結構也發生了很大的變化,各個功能模組被分別放入了 packages 目錄下,職責更加清晰,通過目錄名就可以一目瞭然。今天將從 Vue 的入口檔案開始,看看聲明瞭一個 Vue 的單檔案之後是如何被 compile-core

編譯核心模組編譯成渲染函式的。

為了大家的閱讀方便,以及控制文章篇幅,我會把閱讀原始碼時不太需要在意的邏輯進行摺疊,或者通過註釋 /* 忽略邏輯 */ 這樣的標識進行忽略處理。

我個人是不太喜歡在看原始碼分析文章時一上來就懟出一大段程式碼,這容易讓沒閱讀的同學有點懵逼。所以這個系列的文章我會盡量對關鍵的程式碼畫出一張流程圖。目的還是一個,幫助大家降低理解成本,同時也讓各位同學在下次自主閱讀時有張流程圖能參考。

1、解讀Vue 入口檔案

我們會先從一個 Vue 物件的入口來開始我們的原始碼閱讀, packages/vue/index.ts 。這個入口檔案的程式碼比較簡單,只有一個 compileToFunction

函式,但函式體內的內容卻又比較關鍵,所以先看一張圖,來理解這個函式體究竟完成了哪些事情。

Vue3 編譯流程-原始碼解析

在看完流程圖之後,我們來對照程式碼一起看,我相信大部分同學在此時可能對下發圖片中的程式碼一目瞭然了。

Vue3 編譯流程-原始碼解析

直接跳過所有程式碼,看檔案的末尾 35 行,呼叫了 registerRuntiomCompiler 函式,將 compileToFunction 函式作為TFOGHdRbSL引數傳入,這行程式碼即對應流程圖的起始,通過依賴注入的方式,將 compile 函式注入至 runtime 執行時中,依賴注入是一種比較巧妙的解耦方式,此時執行時再呼叫 compile 編譯函式,就是在呼叫當前的 compileToFunction

函數了。

再看程式碼中的第 17 行,呼叫了 compile-dom 庫提供的 compile 函式,從返回值中解構出了 code 變數。這個就是編譯器執行之後生成的編譯結果,code 是編譯結果的其中一個引數,是一個程式碼字串。比如

<template>
  <div>
    Hello World
  </div>
</template>

這個簡單的模板,在經過編譯後,code 返回的字串為

const _Vue = Vue return function render(_ctx,_cache) {  with (_ctx) {    const { openBlock: _openBlock,createBlock: _createBlock } = _Vue     return (_openBlock(),_createBlock("div",null,"Hello World"))  } }

這個神奇的 compile 函式內部的奧妙在之後我會詳細講解。

在拿到這個這個程式碼字串的結果後,我們再順著程式碼往下看,第 25 行聲明瞭一個 render 變數,並且將生成的程式碼字串 code 作為引數傳入了 new Function 建構函式。這就是流程圖中的倒數第二步,生成了 render 函式。可以將我放在上面的 code 字串格式化,能夠發現 render 函式是一個柯里化的函式,返回了一個函式,函式內部通過 with 來擴充套件作用域鏈。

而最後入口檔案返回了 render 變數,並且順手快取了 render 函式。

上方原始碼的第 1 行,我們看到入口檔案建立了一個 compileCache 物件,用以快取 compileToFunction 函式生成的 render 函式,將 template 引數作為快取的 key, 並在 11 行的位置有一個 if 分支做快取的判斷,如果該模板之前被快取過,則不再進行編譯,直接返回快取中的 render 函式,以此提高效能。

至此 package/vue/index.ts 的入口檔案就解讀完了。相信大家也都看出來了,最有意思的部分就是呼叫 compile 函式編譯出了程式碼字串,所以接下來我將圍繞 compile 函式來接著嘮。compile 函式牽扯到 compile-dom compile-core 兩個模組,本篇文章我只會解讀關鍵流程。細節分析的話會放在後續文章中。一起來看一下 compile 的執行流程:

2、compile 的執行流程

Vue3 編譯流程-原始碼解析

compile 函式內部直接返回 baseCompile 函式的結果,而 baseCompile 函式在執行過程中會生成 AST 抽象語法樹,並呼叫 transform 對 每個 AST 節點進行處理,例如轉換vOn、v-if、v-for 等指令,最後將處理後的 AST 抽象語法樹通過 generate 函式生成之前提及的程式碼字串,並返回編譯結果,至此 compile 函式執行完畢。明白了大體的流程後,接著來看原始碼。

Vue3 編譯流程-原始碼解析

compile 函式的原始碼路徑是 packages/compiler-dom/src/index.ts, 我們看到在 compile 的函式體內,直接 return 了 baseCompile 的處理結果。而 baseCompile 的原始碼路徑是 packages/compiler-core/src/compilehttp://www.cppcns.com.ts 。為什麼會有 baseCompile 這樣的命名呢?因為 compile-core 是編譯的核心模組,接受外部的引數來按照規則完成編譯,而 compile-dom 是專門處理瀏覽器場景下的編譯,在這個模組下匯出的 compile 函式是入口檔案真正接收的編譯函式。而 compile-dom 中的 compile 函式相對 baseCompile 也是更高階的一個編譯器。例如當 Vue 在 weex 在 iOS 或者 這些 Native App 中工作時,compile-dom 可能會被相關的移動端編譯庫來取代。

順著往下一起看一下 baseCompile 函式:

Vue3 編譯流程-原始碼解析

先從函式宣告中來看,baseCompile 接收 template 模板以及上層高階編譯器中處理過 options 編譯選項,最終返回一個 CodegenResult 型別的編譯結果。

export interface CodegenResult {
  code: string
  preamble: string
  ast: RootNode
  map?: RawSourceMap
}

通過 CodegenResult 的介面聲明能清晰的看到返回結果中存在 code 程式碼字串、處理後的 AST 抽象語法樹,以及 sourceMap。

看上方原始碼的第 12 行,判斷 template 模板是否為字串,如果是的話則會對字串進行解析,否則直接將 template 作為 AST 。其實我們平時在寫的單檔案 vue 程式碼,都是以字串的形式傳遞進去的。

接下來原始碼是 16 行呼叫了 transform 函式,以及傳入了指令轉換、節點轉換等工具函式,對由模板生成的 AST 進行轉換。

最終的 32 行位置,我們將轉換好的 AST 傳入 generate,生成 CodegenResult 型別的返回結果。

在 compile-core 模組中,AST 解析、transformcodegencompileparse 這些函式都是一個單獨的小模組,內部的實現都非常精妙,在編譯器的後續文章中,會逐個進行介紹。

本文通過從入口檔案開始,對編譯的大體流程進行解釋,希望可以幫助大家在閱讀編譯器這個模組的程式碼時能有一個清晰的流程概念,配合流程圖食用更香喲。

到此這篇關於Vue3 編譯流程-原始碼解析的文章就介紹到這了,更多相關Vue3 編譯流程內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!