Vue源码~模板编译

241 阅读1分钟

这是我参与8月更文挑战的第6天,活动详情查看: 8月更文挑战

模板编译简介

  • 模板编译的主要目的是将模板(template)转换为渲染函数(render)
<div>
    <h1 @click="handler">title</h1>
    <p>some content</p>
</div>
  • 渲染函数 - 回顾
render (h) {
    return h('div', [
        h('h1', { on: { click: this.handler } }, 'title'),
        h('p', 'some content')
    ])
}

模板编译的作用

  • Vue2.x 使用 VNode 描述视图以及各种交互 ,用户自己编写 VNode 比较复杂
  • 用户只需要编写类似 HTML 的代码 - Vue.js 模板,通过编译器将模板转换为返回 VNoderender 函数
  • .vue 文件会被 webpack 在构建的过程中转换成 render 函数

模板编译 - 示例

<body>
    <div id="app">
        <h1>Vue <span>模板编译过程</span></h1>
        <p>{{ msg }}</p>
        <comp  @myclick="handler"></comp>
    </div>
</body>
<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>

模板编译的结果

(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
    );
  }
})

Vue Template Explorer

编译

gzaWLV.png

  • compileToFunctions(template, {}, this)
  • createCompiler(baseOptions)
  • createCompilerCreator(function baseCompile(){})

compileToFunctions

export function createCompilerCreator (baseCompile: Function): Function {
  // baseOptions 平台相关的options
  // src\platforms\web\compiler\options.js 中定义
  return function createCompiler (baseOptions: CompilerOptions) {
    function compile() {
      // ...
      // ...
    }

    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}

compile

核心:合并选项,调用baseCompile进行编译

function compile (
      template: string,
      options?: CompilerOptions
    ): CompiledResult {
        
    }

baseCompile - AST

// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  // 把模板转换成 ast 抽象语法树
  // 抽象语法树,用来以树形的方式描述代码结构
  const ast = parse(template.trim(), options)
  if (options.optimize !== false) {
    // 优化抽象语法树
    optimize(ast, options)
  }
  // 把抽象语法树生成字符串形式的 js 代码
  const code = generate(ast, options)
  return {
    ast,
    // 渲染函数
    render: code.render,
    // 静态渲染函数,生成静态 VNode 树
    staticRenderFns: code.staticRenderFns
  }
})

注意这里:

什么是抽象语法树

  • 抽象语法树简称 AST(Abstract Syntax Tree)
  • 使用对象的形式描述树形的代码结构
  • 此处的抽象语法树是用来描述树形结构的 HTML 字符串

为什么要使用抽象语法树

  • 模板字符串转换成 AST 后,可以通过 AST 对模板做优化处理
  • 标记模板中的静态内容,在 patch 的时候直接跳过静态内容
  • patch 的过程中静态内容不需要对比和重新渲染

baseCompile - parse

// 把模板转换成 ast 抽象语法树
// 抽象语法树,用来以树形的方式描述代码结构
const ast = parse(template.trim(), options)


parseHTML(template, {
    warn,
    expectHTML: options.expectHTML,
    isUnaryTag: options.isUnaryTag,
    canBeLeftOpenTag: options.canBeLeftOpenTag,
    shouldDecodeNewlines: options.shouldDecodeNewlines,
    shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
    shouldKeepComment: options.comments,
    outputSourceRange: options.outputSourceRange,
    start, 
    end,
    chars,
    comment)
}

baseCompile - optimize

// 优化抽象语法树 就是标记静态节点等
// src/compiler/optimizer.js 
function optimize(root: ?ASTElement, options: CompilerOptions);

generate

// 把抽象语法树生成字符串形式的 js 代码
  const code = generate(ast, options)
  
  export function generate(
    ast: ASTElement | void,
    options: CompilerOptions
  ): CodegenResult {
    // ....
    const code = ast ? genElement(ast, state) : '_c("div")'
  }
  
  export function genElement (el: ASTElement, state: CodegenState): string {
      //...
      //...
      // 处理静态
      if (el.staticRoot && !el.staticProcessed) {
        return genStatic(el, state)
      }
  }

模板编译过程

2V1huj.png


组件化回顾

  • 一个 Vue 组件就是一个拥有预定义选项的一个 Vue 实例
  • 一个组件可以组成页面上一个功能完备的区域,组件可以包含脚本、样式、模板 vue组件

组件注册

  • 全局组件
cosnt Comp = Vue.component('comp', {
    template: '<div>Hello Component</div>'
})
const vm = new Vue({
  el: '#app',
  render(h) {
      return h(comp)
  }
})
  • 局部组件 2VGVAI.png

  • Vue.extend 2VGgC6.png

回顾首次渲染过程

  • Vue 构造函数
  • this._init()
  • this.$mount()
  • mountComponent()
  • new Watcher() 渲染 Watcher
  • updateComponent()
  • vm._render() -> createElement()
  • vm._uodate()