什么是vue模板编译
在使用vue
开发过程中,我们把写在<template></template>
标签中的内容称之为模板。除去一些html
原生的内容还有solt
、v-if
、v-on
、{{}}
这些原生html
不存在的语法,但是仍然可别浏览器识别,其中最重要的一个原因就是vue
的模板编译了。
Vue
会把用户在<template> </template>
标签中写的内容进行编译,找出原生的html
和非原生的html
内容,经过处理生成渲染函数,也就是render
函数。render
函数会将模板内容生成对应的VNode
,而VNode
会经过patch
过程从而得到将渲染的视图中的VNode
,最后根据VNode
创建真实的DOM
节点并插入到视图中,最终完成视图的渲染更新。
而用户在<template></template>
标签中写的类似于原生html
的内容进行编译,生成渲染函数这一过程就是模版编译过程。
编译入口
模板编译过程是在vue实例初始化过程中到了mount
过程开始的。在initMixin
函数中对vue原型上添加了_init
函数,在_init
函数中进行完生命周期初始化、时间机制初始化、数据初始化等,会调用created
钩子函数,然后会判断是否有el
,如果有el
则执行vue
原型上的$mount
,进行挂载。模板编译的过程也就在$mount
中,重点在$mount
内部执行了那些代码。$mount
核心源码如下:
//src/platforms/web/entry-runtime-with-compiler.js
// 省略了部分代码,只保留了关键部分
import { compileToFunctions } from './compiler/index'
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el) {
const options = this.$options
// 如果没有 render 方法,则进行 template 编译
if (!options.render) {
let template = options.template
if (template) {
// 调用 compileToFunctions,编译 template,得到 render 方法
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
// 这里的 render 方法就是生成生成虚拟 DOM 的方法
options.render = render
}
}
return mount.call(this, el, hydrating)
}
我们发现主要的编译函数在compileToFunctions
,那么这个函数是从哪来的呢,点进去发现
//src/platforms/web/compiler/index.js
/* @flow */
import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
继续追溯,createCompiler
函数是从src/compiler/index.js
引入的,核心源码如下
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 模板解析阶段:用正则等方式解析template模板中的指令、class、style等数据,形成AST
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
// 优化阶段:遍历AST,找出其中的静态节点,并打上标记
optimize(ast, options)
}
// render函数生成阶段:将AST转换成渲染函数
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
这里就到了模板编译的入口了。
模板编译内部流程
那么把<template></template>
内容编译成render
函数过程中都发生了什么呢?
抽象语法树AST
什么是抽象语法树:抽象语法和抽象语法树其实就是源代码的抽象语法结构和属性表现形式。
我们常用的浏览器就是通过将js代码转化为抽象语法树来进行下一步的分析等其他操作。
如图
可以理解为根据一定规则,将固定的语法转化为指定的js对象,在根据对象做后续操作。
vue模板编译的第一步就是将模板字符串根据正则等判断生成AST语法树。
具体流程
将模板字符串解析成抽象语法树AST
后,我们就可以对其进行后续操作,也就是优化阶段,用优化后的AST
来生成render
函数,核心代码分为三个阶段
- 模板解析阶段——解析器(parse()) ——源码路径:
src/compiler/parser/index.js
- 优化阶段——优化器(optimize())——源码路径:
src/compiler/optimizer.js
- 代码生成阶段——代码生成器——源码路径:
src/compiler/codegen/index.js
整个流程是在src/complier/index.js
中的basCompile
函数中完成的。具体源码如下:
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 模板解析阶段:用正则等方式解析template模板中的指令、class、style等数据,形成AST
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
// 优化阶段:遍历AST,找出其中的静态节点,并打上标记
optimize(ast, options)
}
// render函数生成阶段:将AST转换成渲染函数
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
- const ast = parse(template.trim(), options):
parse
主要功能就是用正则等方式将模板字符串解析成AST - optimize(ast, options):
optimize
的主要作用是标记静态节点,这是Vue
在编译过程中的优化,在patch过程中,DOM-Diff
算法会直接跳过静态节点,从而减少来比较的过程,优化了patch
的性能 - const code = generate(ast, options):将AST转化成
render
函数字符串的过程,得到一个render
函数的字符串以及staticRenderFns
字符串
后面会整理详细的parse、optimize、generate逻辑。