Skip to content

transform 流程

在 parse 阶段通过光标偏移分析将整个代码文本内容转换成了一个 NodeType 类型树,那么 transform 的流程就是对这颗树进行分析和针对 Vue 内容的一些优化,最终交给 codegen 阶段处理。例外这是一个平台差异最大的一个流程。

整体流程

对于 transform 的流程,我们可以将其看做一个洋葱圈模型, 在深度优先变量 AST 树的时候,对于每一个节点类型都通过 nodeTransforms 数组中从前到后依次执行,并将每一个执行的结果缓存到 exitFuncs 数组中,再从后向前执行。 其图形如下

image-20230303171156296

下面具体分析

  1. 合并各个环境 transform 的钩子函数
ts
// 生成针对Node 和 针对属性 的transform方法
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(prefixIdentifiers)

if (!__BROWSER__ && options.isTS) {
  const { expressionPlugins } = options
  if (!expressionPlugins || !expressionPlugins.includes("typescript")) {
    options.expressionPlugins = [...(expressionPlugins || []), "typescript"]
  }
}

这样就得到结构如下的两个数组

js
nodeTransforms = [
  transformOnce,
  transformIf,
  transformMemo,
  transformFor,
  ...[],
  ...[transformExpression],
  transformSlotOutlet,
  transformElement,
  trackSlotScopes,
  transformText,
]
js
directiveTransforms = {
  on: transformOn,
  bind: transformBind,
  model: transformModel,
}

洋葱圈转换流程

ts
/**
 * 处理 AST 对象
 * @param node
 * @param context
 * @returns
 */
export function traverseNode(node: RootNode | TemplateChildNode, context: TransformContext) {
  // 指向当前 node
  context.currentNode = node
  // apply transform plugins
  const { nodeTransforms } = context
  // 洋葱圈 从后向前 执行回调数组
  const exitFns = []
  // 遍历所有的transform函数并进行回调处理
  for (let i = 0; i < nodeTransforms.length; i++) {
    const onExit = nodeTransforms[i](node, context)
    // 如果存在返回体,就将返回值放到 exitFus 数组中
    if (onExit) {
      if (isArray(onExit)) {
        exitFns.push(...onExit)
      } else {
        exitFns.push(onExit)
      }
    }
    if (!context.currentNode) {
      // node was removed
      return
    } else {
      // node may have been replaced
      node = context.currentNode
    }
  }

  // exit transforms
  context.currentNode = node
  let i = exitFns.length
  while (i--) {
    exitFns[i]()
  }
}

codegenNode 对象

这个步骤其实除了 IF FOR 等破坏 AST 语法树结构的 transform,其他 transform 转换的内容其实没有作用于 AST 对象上,而是将其保留结构的情况下放在了 AST 对象的 codegenNode 属性上,这样

  1. 通过 AST 语法树维护了原生的树结构 。
  2. 通过 codegenNode 的判断对编译做一个提升 。比如:

例如: transformText

html
<div>{{ val }} -- {{num}}</div>

AST 树:

image-20230306162930868

codegenNode 树:

image-20230306163300861

重点:

  1. 发现 codegenNode 的 children 不一定是数组类型的了,可能变成了一个对象类型

  2. 为什么需要洋葱圈模型?

仔细观察发现对于 transform 内置的转换插件,其可以分为两大类:

  • 简单的内容转换,如 transformText、transformOn、transformVMemo、transformBind、transformElement 等
  • 结构化转换, 如 transformIf、transformFor

对于第二种会改变原来的 AST 语法树的结构,所以需要将其结果放到 exisFunc 重新处理