Vue3 編譯流程-源碼解析
前言:
Vue3 發(fā)布已經(jīng)很長一段時間了,最近也有機(jī)會在公司項目中用上了 Vue3 + TypeScript + Vite
的技術(shù)棧,所以閑暇之余抽空也在抽空閱讀 Vue3 的源碼。本著好記性不如爛筆頭的想法,在閱讀源碼時順便記錄了一些筆記,也希望能爭取寫一些源碼閱讀筆記,幫助每個想看源碼但可能存在困難的同學(xué)減少理解成本。
Vue2.x 的源碼我也有過一些簡單的閱讀,自 Vue3 重構(gòu)后,Vue 項目的目錄結(jié)構(gòu)也發(fā)生了很大的變化,各個功能模塊被分別放入了 packages
目錄下,職責(zé)更加清晰,通過目錄名就可以一目了然。今天將從 Vue 的入口文件開始,看看聲明了一個 Vue 的單文件之后是如何被 compile-core
編譯核心模塊編譯成渲染函數(shù)的。
為了大家的閱讀方便,以及控制文章篇幅,我會把閱讀源碼時不太需要在意的邏輯進(jìn)行折疊,或者通過注釋 /* 忽略邏輯 */ 這樣的標(biāo)識進(jìn)行忽略處理。
我個人是不太喜歡在看源碼分析文章時一上來就懟出一大段代碼,這容易讓沒閱讀的同學(xué)有點懵逼。所以這個系列的文章我會盡量對關(guān)鍵的代碼畫出一張流程圖。目的還是一個,幫助大家降低理解成本,同時也讓各位同學(xué)在下次自主閱讀時有張流程圖能參考。
1、解讀Vue 入口文件
我們會先從一個 Vue 對象的入口來開始我們的源碼閱讀, packages/vue/index.ts
。這個入口文件的代碼比較簡單,只有一個 compileToFunction
函數(shù),但函數(shù)體內(nèi)的內(nèi)容卻又比較關(guān)鍵,所以先看一張圖,來理解這個函數(shù)體究竟完成了哪些事情。
在看完流程圖之后,我們來對照代碼一起看,我相信大部分同學(xué)在此時可能對下發(fā)圖片中的代碼一目了然了。
直接跳過所有代碼,看文件的末尾 35 行,調(diào)用了 registerRuntiomCompiler
函數(shù),將 compileToFunction
函數(shù)作為參數(shù)傳入,這行代碼即對應(yīng)流程圖的起始,通過依賴注入的方式,將 compile
函數(shù)注入至 runtime
運行時中,依賴注入是一種比較巧妙的解耦方式,此時運行時再調(diào)用 compile
編譯函數(shù),就是在調(diào)用當(dāng)前的 compileToFunction
函數(shù)了。
再看代碼中的第 17 行,調(diào)用了 compile-dom
庫提供的 compile
函數(shù),從返回值中解構(gòu)出了 code
變量。這個就是編譯器執(zhí)行之后生成的編譯結(jié)果,code 是編譯結(jié)果的其中一個參數(shù),是一個代碼字符串。比如
<template> <div> Hello World </div> </template>
這個簡單的模板,在經(jīng)過編譯后,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
函數(shù)內(nèi)部的奧妙在之后我會詳細(xì)講解。
在拿到這個這個代碼字符串的結(jié)果后,我們再順著代碼往下看,第 25 行聲明了一個 render
變量,并且將生成的代碼字符串 code 作為參數(shù)傳入了 new Function
構(gòu)造函數(shù)。這就是流程圖中的倒數(shù)第二步,生成了 render
函數(shù)。可以將我放在上面的 code
字符串格式化,能夠發(fā)現(xiàn) render
函數(shù)是一個柯里化的函數(shù),返回了一個函數(shù),函數(shù)內(nèi)部通過 with 來擴(kuò)展作用域鏈。
而最后入口文件返回了 render
變量,并且順手緩存了 render 函數(shù)。
上方源碼的第 1 行,我們看到入口文件創(chuàng)建了一個 compileCache
對象,用以緩存 compileToFunction
函數(shù)生成的 render
函數(shù),將 template
參數(shù)作為緩存的 key, 并在 11 行的位置有一個 if 分支做緩存的判斷,如果該模板之前被緩存過,則不再進(jìn)行編譯,直接返回緩存中的 render
函數(shù),以此提高性能。
至此 package/vue/index.ts
的入口文件就解讀完了。相信大家也都看出來了,最有意思的部分就是調(diào)用 compile 函數(shù)編譯出了代碼字符串,所以接下來我將圍繞 compile
函數(shù)來接著嘮。compile 函數(shù)牽扯到 compile-dom
和 compile-core
兩個模塊,本篇文章我只會解讀關(guān)鍵流程。細(xì)節(jié)分析的話會放在后續(xù)文章中。一起來看一下 compile 的運行流程:
2、compile 的運行流程
compile
函數(shù)內(nèi)部直接返回 baseCompile
函數(shù)的結(jié)果,而 baseCompile
函數(shù)在執(zhí)行過程中會生成 AST 抽象語法樹,并調(diào)用 transform
對 每個 AST 節(jié)點進(jìn)行處理,例如轉(zhuǎn)換vOn、v-if、v-for 等指令,最后將處理后的 AST 抽象語法樹通過 generate 函數(shù)生成之前提及的代碼字符串,并返回編譯結(jié)果,至此 compile
函數(shù)執(zhí)行完畢。明白了大體的流程后,接著來看源碼。
compile
函數(shù)的源碼路徑是 packages/compiler-dom/src/index.ts
, 我們看到在 compile
的函數(shù)體內(nèi),直接 return 了 baseCompile
的處理結(jié)果。而 baseCompile
的源碼路徑是 packages/compiler-core/src/compile.ts
。為什么會有 baseCompile
這樣的命名呢?因為 compile-core
是編譯的核心模塊,接受外部的參數(shù)來按照規(guī)則完成編譯,而 compile-dom 是專門處理瀏覽器場景下的編譯,在這個模塊下導(dǎo)出的 compile 函數(shù)是入口文件真正接收的編譯函數(shù)。而 compile-dom
中的 compile 函數(shù)相對 baseCompile
也是更高階的一個編譯器。例如當(dāng) Vue 在 weex 在 iOS 或者 Android 這些 Native App 中工作時,compile-dom
可能會被相關(guān)的移動端編譯庫來取代。
順著往下一起看一下 baseCompile 函數(shù):
先從函數(shù)聲明中來看,baseCompile
接收 template
模板以及上層高階編譯器中處理過 options
編譯選項,最終返回一個 CodegenResult
類型的編譯結(jié)果。
export interface CodegenResult { code: string preamble: string ast: RootNode map?: RawSourceMap }
通過 CodegenResult
的接口聲明能清晰的看到返回結(jié)果中存在 code
代碼字符串、處理后的 AST 抽象語法樹,以及 sourceMap。
看上方源碼的第 12 行,判斷 template
模板是否為字符串,如果是的話則會對字符串進(jìn)行解析,否則直接將 template
作為 AST 。其實我們平時在寫的單文件 vue 代碼,都是以字符串的形式傳遞進(jìn)去的。
接下來源碼是 16 行調(diào)用了 transform
函數(shù),以及傳入了指令轉(zhuǎn)換、節(jié)點轉(zhuǎn)換等工具函數(shù),對由模板生成的 AST 進(jìn)行轉(zhuǎn)換。
最終的 32 行位置,我們將轉(zhuǎn)換好的 AST 傳入 generate,生成 CodegenResult
類型的返回結(jié)果。
在 compile-core 模塊中,AST 解析、transform
、codegen
、compile
、parse
這些函數(shù)都是一個單獨的小模塊,內(nèi)部的實現(xiàn)都非常精妙,在編譯器的后續(xù)文章中,會逐個進(jìn)行介紹。
本文通過從入口文件開始,對編譯的大體流程進(jìn)行解釋,希望可以幫助大家在閱讀編譯器這個模塊的代碼時能有一個清晰的流程概念,配合流程圖食用更香喲。
到此這篇關(guān)于Vue3 編譯流程-源碼解析的文章就介紹到這了,更多相關(guān)Vue3 編譯流程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue3新特性Suspense和Teleport應(yīng)用場景分析
本文介紹了Vue2和Vue3中的Suspense用于處理異步請求的加載提示,以及如何在組件間實現(xiàn)動態(tài)加載,同時,Teleport技術(shù)展示了如何在DOM中靈活地控制組件的渲染位置,解決布局問題,感興趣的朋友跟隨小編一起看看吧2024-07-07啟動myvue報錯npm?ERR!?code?ENOENT?npm?ERR!?syscall?open的解
這篇文章主要介紹了啟動myvue報錯npm?ERR!?code?ENOENT?npm?ERR!?syscall?open的解決辦法,文中給出了詳細(xì)的解決方法,并通過圖文結(jié)合的方式介紹的非常詳細(xì),需要的朋友可以參考下2024-03-03關(guān)于elementUi表格合并行數(shù)據(jù)并展示序號
這篇文章主要介紹了關(guān)于elementUi表格合并行數(shù)據(jù)并展示序號,通過給table傳入span-method方法可以實現(xiàn)合并行或列,方法的參數(shù)是一個對象,感興趣的朋友可以學(xué)習(xí)一下2023-04-04