模板编译的主要目的是将模板 (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 函数
模板编译的结果
带编译器版本的 Vue.js 中,使用 template 或 el 的方式设置模板。
<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>
编译后 render 输出的结果:
(function 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
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。
模板编译的过程
解析、优化、生成
编译的入口
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
解析 - parse
- 解析器将模板解析为抽象语树 AST,只有将模板解析成 AST 后,才能基于它做优化或者生成代码字符串。
// src\compiler\index.js
const ast = parse(template.trim(), options)
//src\compiler\parser\index.js
parse()
结构化指令的处理
v-if 和 v-for
最终生成单元表达式:
// src\compiler\parser\index.js
// structural directives
// 结构化的指令
// v-for
processFor(element)
processIf(element)
processOnce(element)
// src\compiler\codegen\index.js
export function genIf(
el: any,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
el.ifProcessed = true // avoid recursion
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
// 最终调用 genIfConditions 生成三元表达式
最终编译的结果:
ƒ anonymous(
) {
with (this) {
return _c('div', { attrs: { "id": "app" } }, [
_m(0),
_v(" "),
(msg) ? _c('p', [_v(_s(msg))]) : _e(), _v(" "),
_c('comp', { on: { "myclick": onMyClick } })
], 1)
}
}
v-if/v-for 结构化指令只能在编译阶段处理,如果我们要在 render 函数处理条件或循环只能使用 js 中的 if 和 for。
Vue.component('comp', {
data: () {
return {
msg: 'my comp'
}
},
render(h) {
if (this.msg) {
return h('div', this.msg)
}
return h('div', 'bar')
}
})
优化 - optimize
- 优化抽象语法树,检测子节点中是否是纯静态节点 永远不会更改的节点
- 一旦检测到纯静态节点,值不会改变的节点
- 提升为常量,重新渲染的时候不在重新创建节点
- 在 patch 的时候直接跳过静态子树 ```javascript // src\compiler\index.js if (options.optimize !== false) { optimize(ast, options) } // src\compiler\optimizer.js /**
- Goal of the optimizer: walk the generated template AST tree
- and detect sub-trees that are purely static, i.e. parts of
- the DOM that never needs to change. *
- Once we detect these sub-trees, we can: *
- Hoist them into constants, so that we no longer need to
- create fresh nodes for them on each re-render;
- Completely skip them in the patching process. */ export function optimize(root: ?ASTElement, options: CompilerOptions) { if (!root) return isStaticKey = genStaticKeysCached(options.staticKeys || ‘’) isPlatformReservedTag = options.isReservedTag || no // first pass: mark all non-static nodes. // 标记非静态节点 markStatic(root) // second pass: mark static roots. // 标记静态根节点 markStaticRoots(root, false) } ```
生成 - generate
// src\compiler\index.js
const code = generate(ast, options)
// src\compiler\codegen\index.js
export function generate(
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
// 把字符串转换成函数
// src\compiler\to-function.js
function createFunction(code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err, code })
return noop
}
}