transform阶段根据不同的ast节点添加不同的选项参数,这些参数在generate阶段会用到。
大致分为这几个步骤:
1). 生成transform上下文
2). 转换结点
3). 静态提升相关操作
4). 赋值codegenNode
5). 对root结点添加meta信息
具体看下这几步 (packages/compiler-core/src/transform.ts)
生成transform上下文
createTransformContext定义中,options给了默认值。
返回的context是TransformContext类型的实例,包括了一些options,state和methods。 methods方法:
- helper:context.helpers中添加helper
- helperString:context.helpers中添加helper,并返回name在helperNameMap中的映射值
- replaceNode:将node结点替换context.currentNode和ast中相应结点
- removeNode:移除结点;如果不传参,则移除context.currentNode
- onNodeRemoved:移除结点触发事件
- addIdentifiers:非浏览器环境identifiers对应属性+1
- removeIdentifiers:非浏览器环境identifiers对应属性-1
- hoist:创建静态提升的identifier
- cache:添加缓存
traverseNode遍历转换结点
function traverseNode(
node: RootNode | TemplateChildNode,
context: TransformContext
) {
context.currentNode = node
// apply transform plugins
const { nodeTransforms } = context
const exitFns = []
for (let i = 0; i < nodeTransforms.length; i++) {
const onExit = nodeTransforms[i](node, context)
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
}
}
switch (node.type) {
case NodeTypes.COMMENT:
if (!context.ssr) {
// inject import for the Comment symbol, which is needed for creating
// comment nodes with `createVNode`
context.helper(CREATE_COMMENT)
}
break
case NodeTypes.INTERPOLATION:
// no need to traverse, but we need to inject toString helper
if (!context.ssr) {
context.helper(TO_DISPLAY_STRING)
}
break
// for container types, further traverse downwards
case NodeTypes.IF:
for (let i = 0; i < node.branches.length; i++) {
traverseNode(node.branches[i], context)
}
break
case NodeTypes.IF_BRANCH:
case NodeTypes.FOR:
case NodeTypes.ELEMENT:
case NodeTypes.ROOT:
traverseChildren(node, context)
break
}
// exit transforms
context.currentNode = node
let i = exitFns.length
while (i--) {
exitFns[i]()
}
}
- context.currentNode暂存当前结点
- 从context中取出nodeTransforms(getBaseTransformPreset返回属性与用户自定义参数的组合),定义返回值数组
- 依次执行nodeTransforms中的transformOnce、transformIf、transformFor、transformElement、transformText、transformSlotoutlet等方法,并保存结果到exitFns中
- 若node被移除,则退出循环;否则,还原暂存的结点,继续执行下一个transformXX指令
- 循环退出之后,根据node.type的类型,执行后续操作,其中包括了子节点的递归和if的其他分支。
- 最后,执行transformXX的回调
以transformIf为例,
createStructuralDirectiveTransform方法,转换v-if、v-for指令,并生成回调方法。 首先根据第一个参数name是string类型还是正则表达式,生成matches方法; 然后返回包含matches和第二个参数fn的闭包函数。这个函数就是transformIf方法。
const onExit = nodeTransforms[i](node, context)
transformIf --> processIf --> 返回exitFn,processIf的逻辑感兴趣的同学可以自己了解下。
hoistStatic
function hoistStatic(root: RootNode, context: TransformContext) {
walk(
root,
context,
new Map(),
// Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes.
isSingleElementRoot(root, root.children[0])
)
}
function isSingleElementRoot(
root: RootNode,
child: TemplateChildNode
): child is PlainElementNode | ComponentNode | TemplateNode {
const { children } = root
return (
children.length === 1 &&
child.type === NodeTypes.ELEMENT &&
!isSlotOutlet(child)
)
}
看一下walk对ast的遍历
- 初始化hasHoistedNode、hasRuntimeConstant为false
- 遍历结点的children,element类型和text call类型才可以被提升
- element类型
- 不是singleELementRoot,并且不是StaticType.NOT_STATIC(通过getStaticType获得)。如果是StaticType.HAS_RUNTIME_CONSTANT,hasRuntimeConstant置为true;用context.hoist设置codegenNode和codegenNode.patchFlag;hasHoistedNode置为true;继续下次循环,不需要子节点递归的过程;
- 其他情况(结点可能存在动态子节点,但是可能有可以被静态提升的属性)。如果结点的codegenNode.type是NodeTypes.VNODE_CALL,flag(通过getPatchFlag(codegenNode)获得)不存在或者为PatchFlags.NEED_PATCH、PatchFlags.TEXT中的一个,存在可提升的静态属性,那么codegenNode.props就是context.hoist提升后的props
- text call类型
- getStaticType有静态内容,提升codegenNode,hasHoistedNode置为true;如果是StaticType.HAS_RUNTIME_CONSTANT,hasRuntimeConstant置为true;
- 递归遍历(根据子节点类型)
- element类型
- v-for
- v-if分支
- element类型
- 如果hasRuntimeConstant为false,hasHoistedNode为true,调用context.transformHoist(children, context, node);transformHoist在不同的平台会有各自的实现。
createRootCodegen
非ssr的情况下,调用createRootCodegen(root, context),对root的codegenNode属性赋值,codegenNode 是codegen阶段生成代码要用到的数据。
- root只有一个子节点
- 这个子节点是element,将其转换为block,将root.codegenNode赋值为child.codegenNode
- 其他情况,例如单,if结点,for结点(已经是block了),单个text结点(已经patch过),直接将root.codegenNode赋值为child子节点
- root有多个子节点,调用createVNodeCall转换为fragment block,如下图所示,最后呈现出来。
- root没有子节点,直接返回null
添加meta信息
// finalize meta information
root.helpers = [...context.helpers]
root.components = [...context.components]
root.directives = [...context.directives]
root.imports = [...context.imports]
root.hoists = context.hoists
root.temps = context.temps
root.cached = context.cached