1:模板编译介绍
- 模版编译:模板编译的主要目的是将模板 (template) 转换为渲染函数 (render)。
<div>
<h1 @click="handler">title</h1>
<p>some content</p>
</div>
- 渲染函数 render
render (h) {
return h('div', [
h('h1', { on: { click: this.handler} }, 'title'),
h('p', 'some content')
])
}
- 模板编译的作用:
- Vue 2.x 使用 VNode 描述视图以及各种交互,用户自己编写 VNode 比较复杂
- 用户只需要编写类似 HTML 的代码 - Vue 模板,通过编译器将模板转换为返回 VNode 的 render 函数
- vue 文件会被 webpack 在构建的过程中转换成 render 函数
2. 体验模板编译的结果
带编译器版本的 Vue.js 中,使用 template 或 el 的方式设置模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>compile</title>
</head>
<body>
<div id="app">
<h1>Vue<span>模板编译过程</span></h1>
<p>{{ msg }}</p>
<comp @myclick="handler"></comp>
</div>
<script src="../../dist/vue.js"></script>
<script>
Vue.component('comp', {
template: '<div>I am a comp</div>'
})
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello compiler'
},
methods: {
handler () {
console.log('test')
}
}
})
console.log(vm.$options.render)
</script>
</body>
</html>
编译后 render 输出的结果
(ƒ anonymous() {
with(this){
return _c(
'div',{attrs:{"id":"app"}},
[
_m(0),
_v(" "),
_c('p',[_v(_s(msg))]),
_v(" "),
_c('comp',{on:{"myclick":handler}})
],
1
)
}
})
- _c 是 createElement() 方法,定义的位置 instance/render.js 中
- 相关的渲染函数(_开头的方法定义),在 instance/render-helps/index.js 中
// instance/render-helps/index.js
target._v = createTextVNode
target._m = renderStatic
// core/vdom/vnode.js
export function createTextVNode (val: string | number) {
return new VNode(undefined, undefined, undefined, String(val))
}
// 在 instance/render-helps/render-static.js
/**
* Runtime helper for rendering static trees.
*/
export function renderStatic (
index: number,
isInFor: boolean
): VNode | Array<VNode> {
const cached = this._staticTrees || (this._staticTrees = [])
let tree = cached[index]
// if has already-rendered static tree and not inside v-for,
// we can reuse the same tree.
if (tree && !isInFor) {
return tree
}
// otherwise, render a fresh tree.
tree = cached[index] = this.$options.staticRenderFns[index].call(
this._renderProxy,
null,
this // for render fns generated for functional component templates
)
markStatic(tree, `__static__${index}`, false)
return tree
}
- 把 template 转换成 render 的入口 src\platforms\web\entry-runtime-with-compiler.js
3. Vue Template Explorer
Vue 2.6 把模板编译成 render 函数的工具:vue-template-explorer: Vue 3.0 beta 把模板编译成 render 函数的工具: vue-next-template-explorer
4.模板编译过程
- 解析
- 优化
- 生成
总结
模版编译过程:
compileToFunctiosn首先从缓存中加载编译好的render函数,如果缓存中没有就调用compile函数,开始编译,在compile函数中首先合并options,调用baseCompile编译模版,compile的核心是合并选项,真正处理是在baseCompile中完成的。把模版和合并好的选项传递给baseCompile,在它里面完成了核心的3件事情,首先把template转换成ast tree,也就是抽象语法树,然后对抽象语法树进行优化,标记抽象语法树中的所有静态根节点,静态根节点不需要每次都被重绘,patch的过程中会跳过静态根节点,最后把优化过的ast tree生成js的创建代码。最后会回到compileToFunctiosn,继续把上一步中生成的字符串形式js代码转换为函数,通过调用createFunction,当render和staticRenderFns初始化完毕,挂载到vue实例的options对应的属性中。到此模版编译的过程就结束了。
5.编译的入口: compileToFunctions()
- src\platforms\web\entry-runtime-with-compiler.js
Vue.prototype.$mount = function ( ……
// 把 template 转换成 render 函数
const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
……
)
调试 compileToFunctions() 执行过程,生成渲染函数的过程
- compileToFunctions: src\compiler\to-function.js
- complie(template, options):src\compiler\create-compiler.js
- baseCompile(template.trim(), finalOptions):src\compiler\index.js
6. compile
核心作用:合并选项,调用baseCompile进行编译。
- complie(template, options):template: 模版 ; options: 选项
- src\compiler\create-compiler.js
return function createCompiler (baseOptions: CompilerOptions) {
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
const finalOptions = Object.create(baseOptions)
const errors = []
const tips = []
let warn = (msg, range, tip) => {
(tip ? tips : errors).push(msg)
}
if (options) {
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
// $flow-disable-line
const leadingSpaceLength = template.match(/^\s*/)[0].length
warn = (msg, range, tip) => {
const data: WarningMessage = { msg }
if (range) {
if (range.start != null) {
data.start = range.start + leadingSpaceLength
}
if (range.end != null) {
data.end = range.end + leadingSpaceLength
}
}
(tip ? tips : errors).push(data)
}
}
// merge custom modules
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules)
}
// merge custom directives
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives || null),
options.directives
)
}
// copy other options
for (const key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key]
}
}
}
finalOptions.warn = warn
const compiled = baseCompile(template.trim(), finalOptions)
if (process.env.NODE_ENV !== 'production') {
detectErrors(compiled.ast, warn)
}
compiled.errors = errors
compiled.tips = tips
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
7.baseCompile-AST
- src/compiler/index.js
- baseCompile:模版编译的核心函数。
做了3件事情:1.parse: 把模板转换成 ast 抽象语法树 2.optimize:优化抽象语法树 3.generate:把抽象语法树生成字符串形式的 js 代码
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 1.parse: 把模板转换成 ast 抽象语法树
// 抽象语法树,用来以树形的方式描述代码结构
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
// 2.optimize:优化抽象语法树
optimize(ast, options)
}
// 3.generate:把抽象语法树生成字符串形式的 js 代码
const code = generate(ast, options)
return {
ast,
// 渲染函数
render: code.render,
// 静态渲染函数,生成静态 VNode 树
staticRenderFns: code.staticRenderFns
}
})
- 什么是抽象语法树?
抽象语法树简称ast,使用抽象语法树的形式描述树形的代码结构。
此处的抽象语法树是用来描述树形结构的html字符串。 - 为什么使用抽象语法树?
- 把模版对象转换成ast后,可以通过ast对模版做优化处理
- 标记模板中的静态节点内容,在patch的时候直接跳过静态内容
- 在patch的过程中内容不需要再对比和重新渲染。优化性能。