回憶之編譯入口
編譯,其實(shí)就是vue對(duì)模版指令和內(nèi)置組件的處理。
????????編譯我們最終執(zhí)行的是compileToFunctions(template,options)。
????????這個(gè)方法哪里來的?是從一個(gè)creatCompiler(baseOptions)方法返回對(duì)象中取到的(把該方法內(nèi)的輔助方法作為參數(shù)結(jié)合一個(gè)外部引入的createCompileToFunctionFn方法得到的,該輔助方法會(huì)用到下面的編譯核心方法baseCompile)。
? ??????creatCompiler方法哪里來的?調(diào)用creatCompilerCreator(baseCompile)返會(huì)得到的。
????????vue.js在不同平臺(tái)下都會(huì)有編譯的過程,因此編譯過程中依賴的配置baseOptions會(huì)有所不同,像最近美團(tuán)開源的mpvue是對(duì)小程序內(nèi)的編譯過程,也是基于vue編譯實(shí)現(xiàn)的。
? ? ? ? vue.js利用函數(shù)柯里化的技巧很好的實(shí)現(xiàn)了baseOptions的參數(shù)保留。(我們第一次去拿到compileTofunctions時(shí)會(huì)把baseOptions做為參數(shù)傳入createCompiler,之后在執(zhí)行真正編譯過程compileToFunctions中不會(huì)再傳入baseOptions,實(shí)際上他利用函數(shù)柯里化的技巧把baseOptions通過閉包形成長久持有。函數(shù)createCompiler內(nèi)引用了函數(shù)外的變量,并把持有該變量的函數(shù)compiled給return返回了,這樣就形成了閉包,所以函數(shù)compile內(nèi)對(duì)于baseOptions引用就會(huì)長久持有,之后你真正編譯執(zhí)行createCompileToFunctionFn方法中會(huì)調(diào)用compile方法,這時(shí)就不用再傳入baseOptions配置)
????????同樣,vue.js也是利用函數(shù)柯里化技巧把baseCompile函數(shù)抽出來,把真正編譯的過程和其他邏輯如對(duì)編譯配置處理、緩存處理等剝離開。(編譯核心方法baseCompile也是同理,把它做為參數(shù)傳入,compile引用該函數(shù)并return 返回,形成閉包,使compile對(duì)baseCompile方法長久持有,之后你真正編譯執(zhí)行createCompileToFunctionFn方法中會(huì)調(diào)用compile方法,這時(shí)就不用再傳入baseCompile方法)
具體代碼描述:




編譯概述:
????????????之前我們分析過模板到真實(shí) DOM 渲染的過程,中間有一個(gè)環(huán)節(jié)是把模板編譯成?render?函數(shù),這個(gè)過程我們把它稱作編譯。
????????雖然我們可以直接為組件編寫?render?函數(shù),但是編寫?template?模板更加直觀,也更符合我們的開發(fā)習(xí)慣。
????????Vue.js 提供了 2 個(gè)版本,一個(gè)是 Runtime + Compiler 的,一個(gè)是 Runtime only 的,前者是包含編譯代碼的,可以把編譯過程放在運(yùn)行時(shí)做,后者是不包含編譯代碼的,需要借助 webpack 的?vue-loader事先把模板編譯成?render函數(shù)。
????????這一章我們就來分析編譯的過程,對(duì)編譯過程的了解會(huì)讓我們對(duì) Vue 的指令、內(nèi)置組件等有更好的理解。不過由于編譯的過程是一個(gè)相對(duì)復(fù)雜的過程,我們只要求理解整體的流程、輸入和輸出即可,對(duì)于細(xì)節(jié)我們不必?fù)柑?xì)。有些細(xì)節(jié)比如對(duì)于?slot?的處理我們可以在之后去分析插槽實(shí)現(xiàn)的時(shí)候再詳細(xì)分析。
????????當(dāng)我們使用 Runtime + Compiler 的 Vue.js,它的入口是?src/platforms/web/entry-runtime-with-compiler.js,關(guān)于編譯的入口就是在這里。

????????compileToFunctions?方法就是把模板?template?編譯生成?render?以及?staticRenderFns,它的定義在?src/platforms/web/compiler/index.js?中:

????????可以看到?compileToFunctions?方法實(shí)際上是?createCompiler?方法的返回值,該方法接收一個(gè)編譯配置參數(shù),接下來我們來看一下?createCompiler?方法的定義,在?src/compiler/index.js?中:

????????createCompiler?方法實(shí)際上是通過調(diào)用?createCompilerCreator?方法返回的,該方法傳入的參數(shù)是一個(gè)函數(shù),真正的編譯過程都在這個(gè)?baseCompile?函數(shù)里執(zhí)行,那么?createCompilerCreator?又是什么呢,它的定義在?src/compiler/create-compiler.js?中:

????????可以看到該方法返回了一個(gè)?createCompiler?的函數(shù),它接收一個(gè)?baseOptions?的參數(shù),返回的是一個(gè)對(duì)象,包括?compile?方法屬性和?compileToFunctions?屬性,這個(gè)?compileToFunctions?對(duì)應(yīng)的就是?$mount?函數(shù)調(diào)用的?compileToFunctions?方法,它是調(diào)用?createCompileToFunctionFn?方法的返回值,我們接下來看一下?createCompileToFunctionFn?方法,它的定義在?src/compiler/to-function/js?中:

????????compile?函數(shù)執(zhí)行的邏輯是先處理配置參數(shù),真正執(zhí)行編譯過程就一行代碼:constcompiled=baseCompile(template,finalOptions)
????????baseCompile?在執(zhí)行?createCompilerCreator?方法時(shí)作為參數(shù)傳入,如下:

所以編譯的入口我們終于找到了,它主要就是執(zhí)行了如下幾個(gè)邏輯:
1、解析模板字符串生成 AST:cons tast=parse(template.trim(),options)
2、優(yōu)化語法樹:optimize(ast,options)
3、生成代碼:const code=generate(ast,options)
總結(jié)
????????編譯入口邏輯之所以這么繞,是因?yàn)?b> Vue.js 在不同的平臺(tái)下都會(huì)有編譯的過程,因此編譯過程中的依賴的配置?baseOptions?會(huì)有所不同。而編譯過程會(huì)多次執(zhí)行,但這同一個(gè)平臺(tái)下每一次的編譯過程配置又是相同的,為了不讓這些配置在每次編譯過程都通過參數(shù)傳入,Vue.js 利用了函數(shù)柯里化的技巧很好的實(shí)現(xiàn)了?baseOptions?的參數(shù)保留。同樣,Vue.js 也是利用函數(shù)柯里化技巧把基礎(chǔ)的編譯過程函數(shù)抽出來,通過?createCompilerCreator(baseCompile)?的方式把真正編譯的過程和其它邏輯如對(duì)編譯配置處理、緩存處理等剝離開,這樣的設(shè)計(jì)還是非常巧妙的。