• 你知道Vue3 的模版编译原理吗?
  • 发布于 2个月前
  • 308 热度
    0 评论
Vue 3 使用了一个新的编译器 @vue/compiler-dom 来处理模板,它的主要作用是将用户编写的template编译为js中可执行的render函数。

编译分为三个阶段,分别是:parse、transform、codegen。
parse 阶段将模板字符串转化为语法抽象树 AST。
transform 阶段则是对 AST 进行了一些转换处理。

codegen 阶段根据 AST 生成对应的 render 函数字符串。


parse与 AST 构建
首先,编译器将模板字符串解析成抽象语法树。
<template>
  <div>
    <p>{{ message }}</p>
    <button @click="toggle">{{ isShown ? 'Hide' : 'Show' }}</button>
  </div>
</template>
解析器 (parse.ts) 将模板字符串解析成 AST。所有的 AST 节点定义都在 compiler-core/ast.ts 文件中,下面是一个元素节点的定义:
export interface BaseElementNode extends Node {
  type: NodeTypes.ELEMENT // 类型
  ns: Namespace // 命名空间 默认为 HTML,即 0
  tag: string // 标签名
  tagType: ElementTypes // 元素类型
  isSelfClosing: boolean // 是否是自闭合标签 例如 <br/> <hr/>
  props: Array<AttributeNode | DirectiveNode> // props 属性,包含 HTML 属性和指令
  children: TemplateChildNode[] // 字节点
}
转换器对 AST 进行优化和转换。AST结构:
{
  type: 1, // 表示根节点
  tag: 'div',
  children: [
    {
      type: 1,
      tag: 'p',
      children: [
        {
          type: 2, // 表示文本节点
          content: '{{ message }}'
        }
      ]
    },
    {
      type: 1,
      tag: 'button',
      children: [
        {
          type: 2,
          content: '{{ isShown ? "Hide" : "Show" }}'
        }
      ],
      props: [
        {
          type: 7, // 表示事件绑定
          name: 'click',
          value: {
            type: 4, // 表示表达式节点
            content: 'toggle'
          }
        }
      ]
    }
  ]
}
transfrom
Vue 会对 AST 进行一些转换操作,主要是根据不同的 AST 节点添加不同的选项参数,这些参数在 codegen 阶段会用到,这一步只是针对模板的简要解析,无法转换为最终的dom树,因为有些特殊指令可能生成多个元素节点或者不生成节点,比如v-for和v-if等。

代码生成
代码生成阶段最后生成了一个字符串,优化后的 AST 被转换成渲染函数的代码。生成渲染函数 (codegen/index.ts):将 AST 转换成 JavaScript 代码。
const _hoisted_1 = { class: "text-red" };
const _hoisted_2 = { key: 0 };
// 堆代码 duidaima.com
export function render(_ctx, _cache) {
  return (
    openBlock(),
    createElementBlock("div", null, [
      createVNode("p", null, toDisplayString(_ctx.message), 1 /* TEXT */),
      createVNode(
        "button",
        {
          onClick: _cache[0] || (_cache[0] = (...args) => _ctx.toggle && _ctx.toggle(...args))
        },
        toDisplayString(_ctx.isShown ? "Hide" : "Show"),
        1 /* TEXT */
      )
    ])
  );
}
上述代码最后返回一个 render() 函数,作用是生成对应的 VNode。

总结
parse的作用就是将template生成ast对象。则需要对template从左到右依次处理,碰到element标签还需要递归处理,并把添加到element.children上,最终返回一个ast抽象语法树。parse生成的ast是对模板的完整描述,不能直接拿来生成代码,缺乏语义化,并且没有包含编译优化的相关属性,还需要进一步转换。

接下来创建transform上下文,通过traverseNode遍历ast节点,通过createRootCodegen创建根代码生成节点(还包含一些静态提升)。traverseNode递归的遍历ast中的每个节点,执行一些转换函数(主要包含3种转换函数:Element、表达式和Text)。

最终将优化后的 AST 转换成渲染函数的代码。
用户评论