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') 
]) 
}
  • 模板编译的作用:
  1. Vue 2.x 使用 VNode 描述视图以及各种交互,用户自己编写 VNode 比较复杂
  2. 用户只需要编写类似 HTML 的代码 - Vue 模板,通过编译器将模板转换为返回 VNode 的 render 函数
  3. 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.模板编译过程
  1. 解析
  2. 优化
  3. 生成
总结

vue项目 有时候报错为什么要安装python_语法树

模版编译过程:

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() 执行过程,生成渲染函数的过程

  1. compileToFunctions: src\compiler\to-function.js
  2. complie(template, options):src\compiler\create-compiler.js
  3. 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字符串。
  • 为什么使用抽象语法树?
  1. 把模版对象转换成ast后,可以通过ast对模版做优化处理
  2. 标记模板中的静态节点内容,在patch的时候直接跳过静态内容
  3. 在patch的过程中内容不需要再对比和重新渲染。优化性能。