Skip to content

初识编译器

什么是编译器

编译器可以理解为一段将一种语言"翻译"成另一种语言的程序。一个完整的编译过程通常会包含词法分析、语法分析、语义分析、中间代码生成、优化、目标代码生成等步骤。

vue3中的编译器作用

我们清楚组件的渲染是通过渲染函数render实现的,但在vue中可以通过template定义DOM结构,template中会包含一些HTML标签、插值表达式、组件标签、指令等,它是无法直接在JavaScript环境下运行的,所以需要将template转为渲染函数,这个过程就是编译。所以vue中的编译器的主要工作就是将模板编译为渲染函数。

如果你看过之间介绍组件实例的创建过程的文章,你可能记得在执行完setup后会执行一个finishComponentSetup函数,在finishComponentSetup中有一个重要的操作就是检查组件实例中是否存在render函数,如果不存在则需要根据template编译出渲染函数。

ts
function finishComponentSetup () {
  // ...
  
  if (!instance.render) {
    if (!isSSR && compile && !Component.render) {
      const template =
        (__COMPAT__ &&
          instance.vnode.props &&
          instance.vnode.props['inline-template']) ||
        Component.template
      if (template) {
        if (__DEV__) {
          startMeasure(instance, `compile`)
        }
        const { isCustomElement, compilerOptions } = instance.appContext.config
        const { delimiters, compilerOptions: componentCompilerOptions } =
          Component
        const finalCompilerOptions: CompilerOptions = extend(
          extend(
            {
              isCustomElement,
              delimiters
            },
            compilerOptions
          ),
          componentCompilerOptions
        )
        if (__COMPAT__) {
          finalCompilerOptions.compatConfig = Object.create(globalCompatConfig)
          if (Component.compatConfig) {
            extend(finalCompilerOptions.compatConfig, Component.compatConfig)
          }
        }
        Component.render = compile(template, finalCompilerOptions)
        if (__DEV__) {
          endMeasure(instance, `compile`)
        }
      }
    }

    instance.render = (Component.render || NOOP) as InternalRenderFunction

    if (installWithProxy) {
      installWithProxy(instance)
    }
  }
  // ...
}

vue3中编译器的编译过程

vue3中编译器的整个编译过程可以概括为:解析template生成模板抽象语法树ASTparse过程)、将AST转换为JavaScript ASTtransform过程)、根据JavaScript AST生成JavaScript代码(generate过程),即组件渲染函数。

AST是抽象语法树,是使用JavaScript Object来描述模板结构的一种方式。如:

html
<ul :class="ulClass">
  <li v-for="item in data" :key="item.id">{{ item.text }}</li>
</ul>

对应的AST为:

ts
const ast = {
  type: NodeTypes.ROOT, // 根节点
  children: [
    // ul
    {
      type: NodeTypes.ELEMENT, // 标签类型
      tag: 'ul',
      props: [
        {
          type: NodeTypes.DIRECTIVE, // 指令类型
          name: 'bind', // 指令名
          // 指令参数
          arg: {
            type: NodeTypes.SIMPLE_EXPRESSION,
            content: 'class',
            isStatic: true // 是否是静态的
          },
          // 指令表达式
          exp: {
            type: NodeTypes.SIMPLE_EXPRESSION,
            content: 'ulClass',
            isStatic: false
          }
        }
      ],
      children: [
        // li
        {
          type: NodeTypes.ELEMENT,
          tag: 'li',
          props: [
            {
              type: NodeTypes.DIRECTIVE,
              name: 'for',
              arg: undefined,
              exp: {
                type: NodeTypes.SIMPLE_EXPRESSION,
                content: 'item in data',
                isStatic: false
              }
            },
            {
              type: NodeTypes.DIRECTIVE,
              name: 'bind',
              arg: {
                type: NodeTypes.SIMPLE_EXPRESSION,
                content: 'key',
                isStatic: true
              },
              exp: {
                type: NodeTypes.SIMPLE_EXPRESSION,
                content: 'item.id',
                isStatic: false
              }
            },
          ],
          children: [
            {
              type: NodeTypes.INTERPOLATION, // 插值类型
              content: {
                type: NodeTypes.SIMPLE_EXPRESSION,
                content: 'item.text', // 插值表达式
                isStatic: false
              }
            }
          ]
        }
      ]
    }
  ]
}

总结

vue3中编译器的主要作用是是根据模板编译出渲染函数。编译过程可概括为:由template解析出模板AST、将模板AST转换为JavaScript AST、根据JavaScript AST生成`渲染函数。

vue3的编译器在编译过程中,还会进行编译优化,如vnode.patchFlagBlock、静态提升等。

初识编译器 has loaded