# 总结

至此,模板编译的三大阶段都已经完成了,我们一起从宏观的角度来分析模板编译的整体流程是怎么样的。

首先,我们需要明白,模板编译就是将用户编写的模板通过一系列处理最终生成供Vue实例在挂载时可调用的render函数的过程。

简单来说,我写了一个模板字符串,经过模板编译后,输出了render函数。

# 整体流程

刚刚说过,模板编译就是将用户编写的模板通过一系列处理最终生成供Vue实例在挂载时可调用的render函数的过程。所以我们从Vue实例挂载时开始分析。

我们都知道,Vue实例挂载时会调用$mount方法(生命周期部分会详细分析),那么首先看$mount方法,简化如下:

// 缓存runtime 版本的$mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  const options = this.$options
  // 如果没有渲染函数时,将template/el编译成渲染函数
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        // 如果是#开头,通过选择符获取innerHTML使用
        // 如果是字符串不是#号开头,则是用户手动设置的模板,直接使用
        if (template.charAt(0) === '#') {
          // 使用选择符获取模板
          template = idToTemplate(template)
        }
      // 使用nodeType判断是否为一个真实的dom元素,如果是则使用DOM元素的innerHTML作为模板
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        return this
      }
    } else if (el) {
      // 返回el提供的Dom元素的HTML字符串
      template = getOuterHTML(el)
    }
    if (template) {
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  return mount.call(this, el, hydrating)
}
成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

上述代码中可以看出,$mount会先判断用户有没有自己手写render函数,如果没有,则需要Vue自己根据模板通过compileToFunctions函数生成render函数。

很明显compileToFunctions函数就是我们需要着重分析的部分,先看调用部分

const { render, staticRenderFns } = compileToFunctions(template, {
  shouldDecodeNewlines,
  shouldDecodeNewlinesForHref,
  delimiters: options.delimiters,
  comments: options.comments
}, this)
成功
1
2
3
4
5
6

template作为参数调用compileToFunctions,就得到了render函数,这不就是我们要找的么,我们继续看compileToFunctions的来源

// src/platforms/web/compiler/index.js
const { compile, compileToFunctions } = createCompiler(baseOptions)
成功
1
2

可以看出来compileToFunctionscreateCompiler函数调用完毕后的返回值中解构来的,继续查找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)
  }
  // 代码生成阶段:将AST转换成渲染函数
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

到这个函数后,就遇到熟悉的部分了,createCompiler是调用createCompilerCreator函数返回得到的,createCompilerCreator 函数接收一个baseCompile函数作为参数,baseCompile函数内部逻辑就是我们熟悉的模板编译的三个阶段。

那我们看createCompilerCreator做了什么事情

// src/compiler/create-compiler.js
export function createCompilerCreator (baseCompile) {
  return function createCompiler (baseOptions) {

  }
}
成功
1
2
3
4
5
6

createCompilerCreator函数直接返回了createCompiler函数,我们继续看createCompiler的实现

// src/compiler/create-compiler.js
function createCompiler (baseOptions) {
  function compile (){

  }
  return {
    compile,
    compileToFunctions: createCompileToFunctionFn(compile)
  }
}
成功
1
2
3
4
5
6
7
8
9
10

createCompiler函数返回了compilecompileToFunctions两个值,compileToFunctions就是我们最初要找的函数,它是由createCompileToFunctionFn(compile)调用实现的,继续查找createCompileToFunctionFn的实现

// src/compiler/to-function.js

function createFunction (code, errors) {
  try {
    return new Function(code)
  } catch (err) {
    errors.push({ err, code })
    return noop
  }
}

export function createCompileToFunctionFn (compile) {
  return function compileToFunctions (){
    // compile
    const res = {}
    const compiled = compile(template, options)
    res.render = createFunction(compiled.render, fnGenErrors)
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
      return createFunction(code, fnGenErrors)
    })
    return res
  }
}
成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

终于看到了compileToFunctions的定义,这一层层嵌套还挺麻烦的。

compileToFunctions调用的是传入的参数compile进行编译,编译完成后,使用createFunction将函数字符串生成可执行的render函数,我们再返回看compile的实现,compile的实现在createCompiler函数内部

function compile (
  template: string,
  options?: CompilerOptions
): CompiledResult {
  const finalOptions = Object.create(baseOptions)
  const errors = []
  const tips = []

  const compiled = baseCompile(template, finalOptions)

  compiled.errors = errors
  compiled.tips = tips
  return compiled
}
成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14

compile主要调用了baseCompile,而baseCompile则是我们上面说过,调用模板编译三个阶段的函数。

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)
  }
  // 代码生成阶段:将AST转换成渲染函数
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
}
成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

那么主线逻辑就很清晰了

  • compileToFunctions函数内部调用了compile函数
  • compile函数内部又调用了baseCompile函数
  • baseCompile函数返回的是代码生成阶段生成好的render函数字符串
  • compileToFunctions函数内部将render函数字符串传给createFunction函数从而变成真正的render函数
  • 最后将其赋值给options.render

整体流程图如下

模板编译整体流程图