您现在的位置是:网站首页> 编程资料编程资料
Vue编程三部曲之将template编译成AST示例详解_vue.js_
2023-05-24
243人已围观
简介 Vue编程三部曲之将template编译成AST示例详解_vue.js_
前言
Vue.js 提供了 2 个版本,一个是 Runtime + Compiler 版本,一个是 Runtime only 版本。Runtime + Compiler 版本是包含编译代码的,可以把编译过程放在运行时做,Runtime only 版本不包含编译代码的,需要借助 webpack 的 vue-loader 事先把模板编译成 render 函数。
如果你需要在客户端编译模板 (比如传入一个字符串给 template 选项,或挂载到一个元素上并以其 DOM 内部的 HTML 作为模板),就将需要加上编译器,即完整版:
// 需要编译器 new Vue({ template: '{{ hi }}' }) // 不需要编译器 new Vue({ render (h) { return h('div', this.hi) } }) 当使用 vue-loader 或 vueify 的时候,*.vue 文件内部的模板会在构建时预编译成 JavaScript。你在最终打好的包里实际上是不需要编译器的,所以只用运行时版本即可。因为运行时版本相比完整版体积要小大约 30%,所以应该尽可能使用这个版本。
在 Vue 的整个编译过程中,会做三件事:
- 解析模板
parse,生成 AST - 优化 AST
optimize - 生成代码
generate
对编译过程的了解会让我们对 Vue 的指令、内置组件等有更好的理解。不过由于编译的过程是一个相对复杂的过程,我们只要求理解整体的流程、输入和输出即可,对于细节我们不必抠太细。由于篇幅较长,这里会用三篇文章来讲这三件事。这是第一篇, 模板解析,template -> AST
注:全文源码来源,Vue(2.6.11),Runtime + Compiler 的 Vue.js
编译准备
这里先做一个准备工作,编译之前有一个嵌套的函数调用,看似非常的复杂,但是却有玄机。有什么玄机?接着往下看。
源码编译链式调用

compileToFunctions
在源码走了一遭,发现经过一系列的调用,最后 createCompiler 函数返回的 compileToFunctions函数 对应的就是 $mount 函数调用的 compileToFunctions 方法,它是调用 createCompileToFunctionFn 方法的返回值。
// 伪代码 function createCompilerCreator (baseCompile) { return function createCompiler (baseOptions) { function compile ( template, options ) { ... return compiled } return { compile: compile, compileToFunctions: createCompileToFunctionFn(compile) } } } function createCompileToFunctionFn (compile) { var cache = Object.create(null); return function compileToFunctions ( template, options, vm ) { ... } } 方法接受三个参数。
- 编译模板 template
- 编译配置 options
- Vue 的实例
这个方法编译的核心代码就一行。
// compile var compiled = compile(template, options);
而 compile 方法的核心代码也就一行。
const compiled = baseCompile(template, finalOptions)
并且 baseCompile方法是在执行 createCompilerCreator 方法执行的时候传入的。
var createCompiler = createCompilerCreator(function baseCompile ( template, options ) { var ast = parse(template.trim(), options); if (options.optimize !== false) { optimize(ast, options); } var code = generate(ast, options); return { ast: ast, render: code.render, staticRenderFns: code.staticRenderFns } }); baseCompile会做三件事情。

其实看到这里你就会发现,这编译的准备工作,做了很多函数的调用,但是兜兜转转之后,最后回头来还是调用了最开始createCompilerCreator传入的函数。
我理解这样做的原因是 Vue 本身是支持多平台的编译,在不同平台下的编译会有所有不同,但是在同一平台编译是相同的,所以在使用createCompiler(baseOptions)时,baseOptions 会有所有不同。
在 Vue 中利用函数柯里化的思想,将 baseOptions 的配置参数进行了保存。并且在调用链中,不断的进行函数调用并返回函数。
这其实也是利用了函数柯里化的思想把很多基础的函数抽离出来, 通过 createCompilerCreator(baseCompile) 的方式把真正编译的过程和其它逻辑如对编译配置处理、缓存处理等剥离开,这样的设计还是非常巧妙的。
编译准备已经做完,我们接下来看看 Vue 是如何做 parse 的。
parse
parse 要做的事情就是对 template 做解析,生成 AST 抽象语法树。
抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
例如现在有这样一段代码:
经过parse,就变成了一个嵌套的树状结构的对象。

在 AST 中,每一个树节点都是一个 element,并且维护了上下文关系(父子关系)。
解析 template
parse的过程核心就是 parseHTML 函数,这个函数的作用就是解析 template 模板。下面将解析过程中一些重要的点进行一个抽象解读。
function parseHTML (html, options) { var stack = []; ... // 遍历模板字符串 while (html) { ... } // 清除所有剩余的标签 parseEndTag(); // 将 html 字符串的指针前移 function advance (n) { ... } // 解析开始标签 function parseStartTag () { ... } // 处理解析的开始标签的结果 function handleStartTag (match) { ... } // 解析结束标签 function parseEndTag (tagName, start, end) { ... } } 标签匹配相关的正则
下面也会讲到关于一些指令匹配相关的正则。其实这些正则大家在平时的项目中有涉及也可以用起来,毕竟这些正则是经过千万人测试的。
// 识别合法的xml标签 var ncname = '[a-zA-Z_][\w\-\.]*'; // 复用拼接,这在我们项目中完成可以学起来 var qnameCapture = "((?:" + ncname + "\:)?" + ncname + ")"; // 匹配注释 var comment =/^