Vue3源码解读之compiler

1,102 阅读3分钟

前言

compilerVue中三大模块之一,负责模板的编译编译。我们知道,当模板经过编译解析之后就会生成一个render函数,然后在runtime时会被调用以创建实际的dom树。vue3模板编译在这个网站中,我们就能看到,字符串模板经过compiler之后实际生成的render函数。

模板经过compiler之后会生成render函数,那其内部的工作原理又是怎么样的呢?在vue3中新提出的patchFlaghoistcacheHandlers...又是怎么做到的呢?

在这里我们先简单的概述一下,在之后的文章里,我们将会详细展开。在compiler中一共会经历三个过程:

  1. parse:将字符串模板转化为AST
  2. transform:对AST进行优化,添加一些属性值,为后续的步骤做准备
  3. codegen:将AST转化生成为一个render函数

其中在vue2时是没有transform这个步骤的,而是optimize 。两者都是对AST的优化,但transform多了许多新的职责,如上面提到的patchFLag

模块解耦

runtime一样,为了提高可拓展性,compiler也分为comoiler-core和在针对各个平台的compiler,如:

compiler-domcompiler-sfccompiler-ssr,其中各个平台的compiler内部还是调用到compiler-core方法。

  • compiler-dom:浏览器
  • compiler-sfc:单文件组件
  • compiler-ssr:服务端渲染

compiler-dom

现在就从compiler-dom开始。

export function compile(
  template: string,
  options: CompilerOptions = {}
): CodegenResult {
  return baseCompile(
    template,
    extend({}, parserOptions, options, {
      nodeTransforms: [
        ignoreSideEffectTags,
        ...DOMNodeTransforms,
        ...(options.nodeTransforms || [])
      ],
      directiveTransforms: extend(
        {},
        DOMDirectiveTransforms,
        options.directiveTransforms || {}
      ),
      transformHoist: __BROWSER__ ? null : stringifyStatic // 是否优化hoist
    })
  )
}

可以看到,在compile函数内部调用了baseCompilebaseCompiler就是来源于compiler-core

在看看调用时传入的参数,传入了template和由许多对象混入而成options。其中parseOptions则是由compiler-dom提供的针对dom平台的option

让我们继续看看compiler-dom提供的parseOptions

export const parserOptions: ParserOptions = {
  isVoidTag, // 是否自闭和
  isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag), // 是否为平台元素的标签
  isPreTag: tag => tag === 'pre', // 是否为pre标签
  decodeEntities: __BROWSER__ ? decodeHtmlBrowser : decodeHtml, // 对内容进行解码

  // 判断是否为内部自带的标签
  isBuiltInComponent: (tag: string): symbol | undefined => {},

  // 获取命名空间
  getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {},

  // 获取标签的textMode,会影响parse
  getTextMode({ tag, ns }: ElementNode): TextModes {}
}

其中包含这些属性和方法:

  • isVoidTag:是否自闭合
  • isNativeTag:是否为平台提供的原生标签
  • isPreTag:是否为pre标签
  • decodeEntities:对内容进行解码
  • isBuiltInComponent:判断是否为内部自带的标签
  • getNamespace:获取命名空间
  • getTextMode:获取标签的textMode

其中textMode十分重要,它影响了prase的解析,不同的textMode在解析时判断结束以及具体的解析都不相同。

baseCompile

在上面我们看到compiler-domcompile函数的核心在于内部调用了compiler-core提供的baseCompiler函数。现在我们就来看看baseCompile函数到底做了些什么

export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  // 参数校验
  const onError = options.onError || defaultOnError
  const isModuleMode = options.mode === 'module' // dom平台为false
  /* istanbul ignore if */
  if (__BROWSER__) {
    if (options.prefixIdentifiers === true) {
      onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
    } else if (isModuleMode) {
      onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
    }
  }

  // 标记代码生成模式:函数模式/模块模式
  const prefixIdentifiers =
    !__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
    
  if (!prefixIdentifiers && options.cacheHandlers) {
    onError(createCompilerError(ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED))
  }
  if (options.scopeId && !isModuleMode) {
    onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
  }

  // 1.将模板字符串转为ast
  const ast = isString(template) ? baseParse(template, options) : template
  
  // 根据prefixIdentifiers获取默认的transform方法
  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
    prefixIdentifiers
  )
  
  // 2.优化ast
  transform(
    ast,
    extend({}, options, {
      prefixIdentifiers,
      nodeTransforms: [
        ...nodeTransforms,
        ...(options.nodeTransforms || []) // user transforms
      ],
      directiveTransforms: extend(
        {},
        directiveTransforms,
        options.directiveTransforms || {} // user transforms
      )
    })
  )

  // 3.将ast转为render函数
  return generate(
    ast,
    extend({}, options, {
      prefixIdentifiers
    })
  )
}
  1. 进行参数检验,标记代码的生成模式prefixIdentifiers
  2. 调用baseParse将模板字符串转为ast
  3. 根据prefixIdentifiers来获取默认的nodeTransformsdirectiveTransforms
  4. 调用transformast进行优化
  5. 调用generate根据ast生成代码

可以看到我们上面提到的三个过程都在baseCompile中被调用。在接下来的文章中,我们就会逐个细细解读。