Appearance
transform 流程
在 parse 阶段通过光标偏移分析将整个代码文本内容转换成了一个 NodeType 类型树,那么 transform 的流程就是对这颗树进行分析和针对 Vue 内容的一些优化,最终交给 codegen 阶段处理。例外这是一个平台差异最大的一个流程。
整体流程
对于 transform 的流程,我们可以将其看做一个洋葱圈模型, 在深度优先变量 AST 树的时候,对于每一个节点类型都通过 nodeTransforms 数组中从前到后依次执行,并将每一个执行的结果缓存到 exitFuncs 数组中,再从后向前执行。 其图形如下
下面具体分析
- 合并各个环境 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 属性上,这样
- 通过 AST 语法树维护了原生的树结构 。
- 通过 codegenNode 的判断对编译做一个提升 。比如:
例如: transformText
html
<div>{{ val }} -- {{num}}</div>
AST 树:
codegenNode 树:
重点:
发现 codegenNode 的 children 不一定是数组类型的了,可能变成了一个对象类型的
为什么需要洋葱圈模型?
仔细观察发现对于 transform 内置的转换插件,其可以分为两大类:
- 简单的内容转换,如 transformText、transformOn、transformVMemo、transformBind、transformElement 等
- 结构化转换, 如 transformIf、transformFor
对于第二种会改变原来的 AST 语法树的结构,所以需要将其结果放到 exisFunc 重新处理