Vue3 公布曾经有一段时间了,最近也有机会在公司我的项目中用上了 Vue3 + TypeScript + Vite 的技术栈,所以空闲之余抽空也在抽空浏览 Vue3 的源码。本着好忘性不如烂笔头的想法,在浏览源码时顺便记录了一些笔记,也心愿能争取写一些源码浏览笔记,帮忙每个想看源码但可能存在艰难的同学缩小了解老本。
Vue2.x 的源码我也有过一些简略的浏览,自 Vue3 重构后,Vue 我的项目的目录构造也产生了很大的变动,各个功能模块被别离放入了 packages
目录下,职责更加清晰,通过目录名就能够高深莫测。明天将从 Vue 的入口文件开始,看看申明了一个 Vue 的单文件之后是如何被 compile-core
编译外围模块编译成渲染函数的。
为了大家的浏览不便,以及管制文章篇幅,我会把浏览源码时不太须要在意的逻辑进行折叠,或者通过正文 /* 疏忽逻辑 */
这样的标识进行疏忽解决。
我集体是不太喜爱在看源码剖析文章时一上来就怼出一大段代码,这容易让没浏览的同学有点懵逼。所以这个系列的文章我会尽量对要害的代码画出一张流程图。目标还是一个,帮忙大家升高了解老本,同时也让各位同学在下次自主浏览时有张流程图能参考。
咱们会先从一个 vue 文件创建的入口来开始咱们的源码浏览,packages/vue/index.ts
。这个入口文件的代码比较简单,只有一个 compileToFunction
函数,但函数体内的内容却又比拟要害,所以先看一张图,来了解这个函数体到底实现了哪些事件。
在看完流程图之后,咱们来对照代码一起看,我置信大部分同学在此时可能对下发图片中的代码高深莫测了。
间接跳过所有代码,看文件的开端 35 行,调用了 registerRuntiomCompiler 函数,将 compileToFunction 函数作为参数传入,这行代码即对应流程图的起始,通过依赖注入的形式,将 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 的运行流程:
compile 函数外部间接返回 baseCompile 函数的后果,而 baseCompile 函数在执行过程中会生成 AST 形象语法树,并调用 transform 对 每个 AST 节点进行解决,例如转换 vOn、v-if、v-for 等指令,最初将解决后的 AST 形象语法树通过 generate 函数生成之前提及的代码字符串,并返回编译后果,至此 compile 函数执行结束。明确了大体的流程后,接着来看源码。
compile 函数的源码门路是 packages/compiler-dom/src/index.ts,咱们看到在 compile 的函数体内,间接 return 了 baseCompile 的处理结果。而 baseCompile 的源码门路是 packages/compiler-core/src/compile.ts。为什么会有 baseCompile 这样的命名呢?因为 compile-core 是编译的外围模块,承受内部的参数来依照规定实现编译,而 compile-dom 是专门解决浏览器场景下的编译,在这个模块下导出的 compile 函数是入口文件真正接管的编译函数。而 compile-dom 中的 compile 函数绝对 baseCompile 也是更高阶的一个编译器。例如当 Vue 在 weex 在 iOS 或者 Android 这些 Native App 中工作时,compile-dom 可能会被相干的挪动端编译库来取代。
顺着往下一起看一下 baseCompile 函数:
先从函数申明中来看,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 解析、transform、codegen、compile、parse 这些函数都是一个独自的小模块,外部的实现都十分精妙,在编译器的后续文章中,会一一进行介绍。
本文通过从入口文件开始,对编译的大体流程进行解释,心愿能够帮忙大家在浏览编译器这个模块的代码时能有一个清晰的流程概念,配合流程图食用更香哟。