vue3 模版编译在上一篇分析,知道vue模版编译经过以下几个阶段
下面结合vue 中的v-if模版语法来深入解析一下整个编译过程;
来一个v-if代码转换例子
以下例子可以看到vue的条件渲染语法,最终生成了一个三目表达式
// vue模版
<div id="app">
<h1 v-if="title">我是A</h1>
<p v-else>我是B</p>
</div>
// 最终转换code
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", _hoisted_1, [
(_ctx.title)
? (_openBlock(), _createElementBlock("h1", _hoisted_2, "我是A"))
: (_openBlock(), _createElementBlock("p", _hoisted_3, "我是B"))
]))
}
generate--生成code
generate的时候,根据AST 节点类型,判断是节点类型是js条件表达式(JS_CONDITIONAL_EXPRESSION),调用genConditionalExpression来生成条件表达式语句,
function genConditionalExpression(
node: ConditionalExpression,
context: CodegenContext,
) {
// 从节点上拿到关键consequent, alternate属性
const { test, consequent, alternate, newline: needNewline } = node
const { push, indent, newline } = context
// 判断如果是简单表达式, 调用genExpression生生成简单表达式
if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
const needsParens = !isSimpleIdentifier(test.content)
needsParens && push(`(`)
genExpression(test, context)
needsParens && push(`)`)
// 否则处理节点
} else {
push(`(`)
genNode(test, context)
push(`)`)
}
needNewline && indent()
needNewline || push(` `)
push(`? `)
// 条件分支A---解析consequent属性, 调用genNode生成(_openBlock(), _createElementBlock("h1", _hoisted_2, "我是A"))
genNode(consequent, context)
needNewline && newline()
needNewline || push(` `)
push(`: `)
// 条件分支B---解析alternate属性,调用genNode生成(_openBlock(), _createElementBlock("p", _hoisted_3, "我是B"))
genNode(alternate, context)
}
可以看到除了基础语法的生成,主要解析了AST节点上的consequent, alternate属性;
这个时候可以打印看到AST中的该节点的信息
transform--转换v-if语法
基于baseParse阶段生成的AST,并且在扩展了AST节点属性和指令节点的转换
transform(
ast,
extend({}, resolvedOptions, {
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []), // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {}, // user transforms
),
}),
)
主要转换的内容如下
// 语法包括
transformOnce,
transformIf,
transformMemo,
transformFor,
transformFilter,
trackVForSlotScopes,
transformExpression,
transformSlotOutlet,
transformElement,
trackSlotScopes,
transformText,
// 指令包括
{
on: transformOn,
bind: transformBind,
model: transformModel,
},
- 判断语法中有if,会将之前生产的JS AST,创建branch节点属性,组装新的ifNode,替换原有的节点;
- 如果是v-else语法,移除该节点,生成branch节点属性,赋值到兄弟节点的branchs属性上;
- 处理v-if语法时,会识别到if|else|else-if,会替换v-if的节点,并且会生成对应的node中的codegenNode属性,并且在codegenNode属性下追加了alternate属性;
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// #1587: We need to dynamically increment the key based on the current
// node's sibling nodes, since chained v-if/else branches are
// rendered at the same depth
const siblings = context.parent!.children
let i = siblings.indexOf(ifNode)
let key = 0
while (i-- >= 0) {
const sibling = siblings[i]
if (sibling && sibling.type === NodeTypes.IF) {
key += sibling.branches.length
}
}
return () => {
if (isRoot) {
// 新增codegenNode节点
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context,
) as IfConditionalExpression
} else {
// 获取到父节点
const parentCondition = getParentCondition(ifNode.codegenNode!)
// 追加alternate属性
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context,
)
}
}
})
},
)
调用createCodegenNodeForBranch以后,生成的Node数据结构如下,含有consequent,alternate属性
{
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test,
consequent,
alternate,
newline,
loc: locStub,
}
经过以上处理, 此时的AST 节点上完善了codegenNode, branches等节点属性
可以从接口中看到,在tranform阶段,tranformIf阶段,对baseParse生成的AST节点,做了以下属性的扩展
export interface ConditionalExpression extends Node {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION
test: JSChildNode
consequent: JSChildNode
alternate: JSChildNode
newline: boolean
}
baseParse生成AST
parse阶段,生成的AST, 此时的AST, 对比babel AST 仅仅扩展了components, dectives等相关属性, 还没有codegenNode, branches等节点属性
{
"type": 0,
"source": "<script setup>\nimport { ref } from 'vue'\nconst title = ref("aa")\n</script>\n\n<template>\n <h1 v-if="title">我是A</h1>\n <p v-else>我是B</p>\n \n</template>\n",
"children": [
{
"type": 1,
"tag": "h1",
"ns": 0,
"tagType": 0,
"props": [],
"children": [
{
"type": 2,
"content": "我是A",
"loc": {
"start": {
"column": 22,
"line": 7,
"offset": 108
},
"end": {
"column": 25,
"line": 7,
"offset": 111
},
"source": "我是A"
}
}
],
"loc": {
"start": {
"column": 5,
"line": 7,
"offset": 91
},
"end": {
"column": 30,
"line": 7,
"offset": 116
},
"source": "<h1 v-if="title">我是A</h1>"
}
},
{
"type": 1,
"tag": "p",
"ns": 0,
"tagType": 0,
"props": [],
"children": [
{
"type": 2,
"content": "我是B",
"loc": {
"start": {
"column": 15,
"line": 8,
"offset": 131
},
"end": {
"column": 18,
"line": 8,
"offset": 134
},
"source": "我是B"
}
}
],
"loc": {
"start": {
"column": 5,
"line": 8,
"offset": 121
},
"end": {
"column": 22,
"line": 8,
"offset": 138
},
"source": "<p v-else>我是B</p>"
}
}
],
"helpers": {},
"components": [],
"directives": [],
"hoists": [],
"imports": [],
"cached": 0,
"temps": 0,
"loc": {
"start": {
"line": 1,
"column": 1,
"offset": 0
},
"end": {
"line": 1,
"column": 1,
"offset": 0
},
"source": ""
}
}