模板编译的主要目的是将模板 (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.jstarget._v = createTextVNodetarget._m = renderStatic// core/vdom/vnode.jsexport function createTextVNode(val: string | number) {return new VNode(undefined, undefined, undefined, String(val))}// 在 instance/render-helps/render-static.jsexport 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 = renderoptions.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.jsconst ast = parse(template.trim(), options)//src\compiler\parser\index.jsparse()
结构化指令的处理
v-if 和 v-for
最终生成单元表达式:
// src\compiler\parser\index.js// structural directives// 结构化的指令// v-forprocessFor(element)processIf(element)processOnce(element)// src\compiler\codegen\index.jsexport function genIf(el: any,state: CodegenState,altGen?: Function,altEmpty?: string): string {el.ifProcessed = true // avoid recursionreturn 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.jsconst code = generate(ast, options)// src\compiler\codegen\index.jsexport 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.jsfunction createFunction(code, errors) {try {return new Function(code)} catch (err) {errors.push({ err, code })return noop}}
总结

