继续上一篇的节奏,上一篇我们分析了Vue3
的render
函数的生成过程,在genNode
函数中,我们看到了很多节点类型的生成函数;
今天我们就来具体分析一下这些节点类型的生成函数,看看它们是如何生成的;、
genNode
我们先简单的回顾一下上一篇的genNode
函数,具体可以看上一篇的文章:【源码&库】Vue3模版解析后的AST转换为render函数的过程
js
function genNode(node, context) {
// 如果是 string 类型,则直接作为代码
if (isString(node)) {
context.push(node);
return;
}
// 如果是 symbol 类型,则使用辅助函数生成代码
if (isSymbol(node)) {
context.push(context.helper(node));
return;
}
// 根据节点类型,执行不同的生成函数
switch (node.type) {
case 1: // 元素节点
case 9: // if
case 11: // for
// 这些节点直接递归生成
assert(
node.codegenNode != null,
`Codegen node is missing for element/if/for node. Apply appropriate transforms first.`
);
genNode(node.codegenNode, context);
break;
case 2: // 文本节点
genText(node, context);
break;
case 4: // 表达式节点
genExpression(node, context);
break;
case 5: // 插值节点
genInterpolation(node, context);
break;
case 12: // fragment 节点
genNode(node.codegenNode, context);
break;
case 8: // 复合表达式节点
genCompoundExpression(node, context);
break;
case 3: // 注释节点
genComment(node, context);
break;
case 13: // 生成 createVNode 的调用
genVNodeCall(node, context);
break;
case 14: // 生成普通函数调用
genCallExpression(node, context);
break;
case 15: // 生成对象表达式
genObjectExpression(node, context);
break;
case 17: // 生成数组表达式
genArrayExpression(node, context);
break;
case 18: // 生成函数表达式
genFunctionExpression(node, context);
break;
case 19: // 生成条件表达式
genConditionalExpression(node, context);
break;
case 20: // 生成缓存表达式
genCacheExpression(node, context);
break;
case 21: // 生成节点列表
genNodeList(node.body, context, true, false);
break;
case 22:
break;
case 23:
break;
case 24:
break;
case 25:
break;
case 26:
break;
case 10:
break;
default:
{
assert(false, `unhandled codegen node type: ${node.type}`);
const exhaustiveCheck = node;
return exhaustiveCheck;
}
}
}
在这里我们可以看到有很多的节点类型,如下:
genText
: 文本节点genExpression
: 表达式节点genInterpolation
: 插值节点genCompoundExpression
: 复合表达式节点genComment
: 注释节点genVNodeCall
: 生成 createVNode 的调用genCallExpression
: 生成普通函数调用genObjectExpression
: 生成对象表达式genArrayExpression
: 生成数组表达式genFunctionExpression
: 生成函数表达式genConditionalExpression
: 生成条件表达式genCacheExpression
: 生成缓存表达式genNodeList
: 生成节点列表
我们就来一一分析一下这些节点类型的生成函数,我们这一章还是一样可以通过如下示例代码进行调试和分析:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id='app'></div>
</body>
<script src="./vue.global.js"></script>
<script>
const {createApp, h} = Vue;
const app = createApp({
template: '直接在这里写模板语法'
});
debugger;
app.mount('#app');
</script>
</html>
genText
genText
函数用来生成文本节点,具体代码如下:
js
function genText(node, context) {
// 如果是纯文本,则直接通过 JSON.stringify 转成字符串
// 最后的结果是 return "xxx"
context.push(JSON.stringify(node.content), node);
}
验证代码如下:
js
const app = createApp({
template: 'xxx'
});
生成的代码如下:
js
return function render(_ctx, _cache) {
with (_ctx) {
return "xxx"
}
}
genExpression
genExpression
函数用来生成表达式节点,具体代码如下:
js
function genExpression(node, context) {
// 获取表达式 和 是否静态标识
const { content, isStatic } = node;
// 如果是静态的,直接将内容作为字符串进行处理,否则就直接将表达式进行返回
context.push(isStatic ? JSON.stringify(content) : content, node);
}
验证代码如下:
js
const app = createApp({
template: '{{ 1 + 1 }}'
});
生成的代码如下:
js
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString } = _Vue
return _toDisplayString(1 + 1)
}
}
genInterpolation
可以看到上面的最终生成的结果会有一个toDisplayString
的函数包装,这个函数是由genInterpolation
函数生成的;
而这个函数就是由genInterpolation
函数生成的,这个函数就是用来处理插值表达式的,具体代码如下:
js
function genInterpolation(node, context) {
// 从上下文中获取 push、helper 和 pure
const { push, helper, pure } = context;
// pure 用来标记是否是纯的,如果是纯的,则会添加 PURE_ANNOTATION
// 也就是 /*#__PURE__*/ 标记
// 有了这个标记,在代码优化阶段,就会告诉编译器,可以安全地删除或替换它的调用,而不影响程序的行为
if (pure)
push(PURE_ANNOTATION);
// TO_DISPLAY_STRING 就是生成 toDisplayString 函数的辅助函数
// 通过调用 helper(TO_DISPLAY_STRING) 就会生成:_toDisplayString
push(`${helper(TO_DISPLAY_STRING)}(`);
// 递归生成插值表达式的内容,按照我们的示例就会进入上面的 genExpression 函数
genNode(node.content, context);
// 最后将 ) 添加到代码中
push(`)`);
}
验证代码和结果同上;
genCompoundExpression
复合表达式节点指的是一个节点中包含多个表达式,例如:
{{ 1 + 1 }} {{ 2 + 2 }}
,这里就是一个复合表达式节点,这个节点中包含了两个表达式,分别是1 + 1
和2 + 2
; 只要是一个节点中包含多个表达式,那么这个节点就是一个复合表达式节点;
genCompoundExpression
函数用来生成复合表达式节点,具体代码如下:
js
function genCompoundExpression(node, context) {
// 直接遍历子节点
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
// 如果是 string 类型,则直接推入到上下文中
if (isString(child)) {
context.push(child);
}
// 否则就递归调用 genNode 函数
else {
genNode(child, context);
}
}
}
验证代码如下:
js
const app = createApp({
template: '{{ 1 + 1 }} {{ 2 + 2 }}'
});
生成的代码如下:
js
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString } = _Vue
return _toDisplayString(1 + 1) + " " + _toDisplayString(2 + 2)
}
}
genComment
genComment
函数用来生成注释节点,具体代码如下:
js
function genComment(node, context) {
// 同上
const { push, helper, pure } = context;
if (pure) {
push(PURE_ANNOTATION);
}
// helper(CREATE_COMMENT) 用来生成 _createCommentVNode 函数
// 将注释内容转换为 JSON 格式的字符串作为内容
push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);
}
验证代码如下:
js
const app = createApp({
template: '<!-- 这是注释 -->'
});
生成的代码如下:
js
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { createCommentVNode: _createCommentVNode } = _Vue
return _createCommentVNode(" 这是注释 ")
}
}
genVNodeCall
genVNodeCall
函数用来生成复合表达式节点,具体代码如下:
js
function genVNodeCall(node, context) {
// 获取 push、helper 和 pure
const { push, helper, pure } = context;
// 获取节点的类型和 props
const {
tag,
props,
children,
patchFlag,
dynamicProps,
directives,
isBlock,
disableTracking,
isComponent
} = node;
// 是否存在指令,如果存在,则需要添加 WITH_DIRECTIVES 标记
// 最后会生成:_withDirectives
if (directives) {
push(helper(WITH_DIRECTIVES) + `(`);
}
// 是否是一个块,块指的是有能成对出现的节点,像 html 标签都是
// 也会有自闭合标签,像 img、input 等,但是他们也都是成块的
// 所以我理解的块指的是不可拆分的一个整体就表示为一个块
// 这里最后会生成:_openBlock
if (isBlock) {
push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `);
}
// 是否纯函数
if (pure) {
push(PURE_ANNOTATION);
}
// 根据是否是块节点和是否是组件选择适当的 createVNode 辅助函数
// 这里最后就就是会生成不同的函数处理函数,可以看下面贴出来的 getVNodeBlockHelper 和 getVNodeHelper 函数
const callHelper = isBlock ? getVNodeBlockHelper(context.inSSR, isComponent) : getVNodeHelper(context.inSSR, isComponent);
push(helper(callHelper) + `(`, node);
// 生成包含 createVNode 调用的参数列表
// genNullableArgs 用于生成可空的参数列表,这个函数就是会移除尾部的控制,然后将剩余的参数列表返回
// 例如 ['div', null, [], null, null] 会生成 ['div', null, []]
// 然后 ['div', null, []] 这个就是一个函数的参数列表,例如 fn('div', null, []) 这样的调用
// 后面会有 genNodeList 函数的讲解
genNodeList(
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
context
);
// 推入函数的结束括号
push(`)`);
// 如果是块节点,则需要推入块节点的结束括号
if (isBlock) {
push(`)`);
}
// 如果存在指令,则推入逗号和生成的指令函数,本质也是通过递归调用 genNode 函数
if (directives) {
push(`, `);
genNode(directives, context);
push(`)`);
}
}
function getVNodeHelper(ssr, isComponent) {
return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE;
}
function getVNodeBlockHelper(ssr, isComponent) {
return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK;
}
验证代码如下:
js
const app = createApp({
template: '<div>xxx</div>'
});
生成的代码如下:
js
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, "xxx"))
}
}
genCallExpression
普通函数调用本身不是由开发者直接书写的,而是内部处理的,例如使用 v-if 会生成一个注释节点来进行标记(如果节点隐藏,没有替代的节点),这个时候就会生成 _createCommentVNode 函数的调用;
genCallExpression
函数用来生成普通函数调用,具体代码如下:
js
function genCallExpression(node, context) {
// 获取 push、helper 和 pure
const { push, helper, pure } = context;
// 这一步是生成调用函数名
const callee = isString(node.callee) ? node.callee : helper(node.callee);
// 纯
if (pure) {
push(PURE_ANNOTATION);
}
// 推入函数名和左括号
push(callee + `(`, node);
// 这一步可以理解为生成调用函数的参数列表
genNodeList(node.arguments, context);
// 推入右括号
push(`)`);
}
验证代码如下:
js
const app = createApp({
template: '<div v-if="true">xxx</div>',
});
生成的代码如下:
js
const _Vue = Vue
const { createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = { key: 0 }
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
return true
? (_openBlock(), _createElementBlock("div", _hoisted_1, "xxx"))
: _createCommentVNode("v-if", true)
}
}
genObjectExpression
对象表达式指的是一个对象的键值对,例如我们使用的 @click="xxx" 最后的解释会变成 onCLick: xxx,这里的 onClick 就是一个对象表达式; 还有其他的情况,例如 v-bind 也会生成一个对象表达式;
genObjectExpression
函数用来生成对象表达式,具体代码如下:
js
function genObjectExpression(node, context) {
// 取出一些辅助函数
const { push, indent, deindent, newline } = context;
// 取出 properties 属性列表
const { properties } = node;
// 如果没有属性,则直接推入 {},表示空对象
if (!properties.length) {
push(`{}`, node);
return;
}
// 判断是否多行,如果是多行,则需要添加缩进
const multilines = properties.length > 1 || properties.some((p) => p.value.type !== 4);
push(multilines ? `{` : `{ `);
multilines && indent();
// 遍历属性列表
for (let i = 0; i < properties.length; i++) {
const { key, value } = properties[i];
// 生成 key
genExpressionAsPropertyKey(key, context);
push(`: `);
// 生成 value
genNode(value, context);
// 如果不是最后一个,则推入逗号和换行
if (i < properties.length - 1) {
push(`,`);
newline();
}
}
// 如果是多行,则需要减少缩进
multilines && deindent();
// 推入右括号
push(multilines ? `}` : ` }`);
}
验证代码如下:
js
const app = createApp({
template: '<div @click="() => {}">xxx</div>'
});
生成的代码如下:
js
const _Vue = Vue
const { } = _Vue
const _hoisted_1 = ["onClick"]
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", { onClick: () => {} }, "xxx", 8 /* PROPS */, _hoisted_1))
}
}
genArrayExpression
数组表达式和对象表达式类似,只不过是一个数组的形式,可用于自定义指令的参数列表;
genArrayExpression
函数用来生成数组表达式,具体代码如下:
js
function genArrayExpression(node, context) {
// 内部是调用 genNodeListAsArray 函数
genNodeListAsArray(node.elements, context);
}
function genNodeListAsArray(nodes, context) {
// 判断是否多行,多行就增加缩进
const multilines = nodes.length > 3 || nodes.some((n) => isArray(n) || !isText(n));
context.push(`[`);
multilines && context.indent();
// 还是使用 genNodeList 来生成数据
genNodeList(nodes, context, multilines);
multilines && context.deindent();
context.push(`]`);
}
验证代码如下:
js
const app = createApp({
template: '<div v-xxx="[xxx, yyy]">{{i}}</div>',
});
生成的代码如下:
js
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveDirective: _resolveDirective, openBlock: _openBlock, createElementBlock: _createElementBlock, withDirectives: _withDirectives } = _Vue
const _directive_xxx = _resolveDirective("xxx")
return _withDirectives((_openBlock(), _createElementBlock("div", null, [
_createTextVNode(_toDisplayString(i), 1 /* TEXT */)
])), [
[_directive_xxx, [xxx, yyy]]
])
}
}
genFunctionExpression
函数表达式指的是需要用函数来处理的情况,表示内容已经是不可控了,例如 v-for、插槽等不可控的内容; v-if 并不会生成函数表达式,因为 v-if 只是一个条件,最后会生成一个三元表达式;
genFunctionExpression
函数用来生成函数表达式,具体代码如下:
js
function genFunctionExpression(node, context) {
// 辅助函数
const {push, indent, deindent} = context;
// 节点的一些属性
const {params, returns, body, newline, isSlot} = node;
// 如果是插槽函数,添加 _withCtx 辅助函数
if (isSlot) {
push(`_${helperNameMap[WITH_CTX]}(`);
}
// 推入函数的左括号,箭头函数的开头
push(`(`, node);
// 如果参数是一个数组,则递归生成参数列表
if (isArray(params)) {
genNodeList(params, context);
}
// 否则就使用 genNode 生成参数
else if (params) {
genNode(params, context);
}
// 推入函数的右括号,箭头函数的结尾
push(`) => `);
if (newline || body) {
push(`{`);
indent();
}
// 如果有返回值,则生成返回值
if (returns) {
// 如果需要换行,输出 return
// 箭头函数如果没有花括号,可以直接返回,所以这里需要添加 return
if (newline) {
push(`return `);
}
// 如果返回值是数组,调用 genNodeListAsArray 生成返回值列表的代码
if (isArray(returns)) {
genNodeListAsArray(returns, context);
}
// 否则就调用 genNode 生成返回值
else {
genNode(returns, context);
}
}
// 没有返回值,但存在函数体
else if (body) {
genNode(body, context);
}
// 如果需要换行,添加换行和缩进
if (newline || body) {
deindent();
push(`}`);
}
// 插槽函数,输出插槽函数的结束符号
if (isSlot) {
push(`)`);
}
}
验证代码如下:
js
const app = createApp({
template: '<div v-for="i in 10">{{i}}</div>',
});
生成的代码如下:
js
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(10, (i) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(i), 1 /* TEXT */))
}), 256 /* UNKEYED_FRAGMENT */))
}
}
genConditionalExpression
条件表达式指的是 v-if、v-else-if、v-else 等条件语句;
genConditionalExpression
函数用来生成条件表达式,具体代码如下:
js
function genConditionalExpression(node, context) {
// 各种情况的属性
const { test, consequent, alternate, newline: needNewline } = node;
// 辅助函数
const { push, indent, deindent, newline } = context;
// 如果测试条件是一个字符串文本
if (test.type === 4) {
// 如果字符串内容不是一个简单的标识符,就需要加括号
const needsParens = !isSimpleIdentifier(test.content);
needsParens && push(`(`);
// 上面的 genExpression 函数就是用来生成表达式的
genExpression(test, context);
needsParens && push(`)`);
}
// 如果测试条件是一个复杂的表达式
else {
push(`(`);
// 递归调用 genNode 函数
genNode(test, context);
push(`)`);
}
// 如果需要换行,添加缩进
needNewline && indent();
// 缩进级别增加
context.indentLevel++;
needNewline || push(` `);
// 三元表达式的问号
push(`? `);
// 递归调用 genNode 函数,这是条件为真的情况
genNode(consequent, context);
// 缩进级别减少
context.indentLevel--;
needNewline && newline();
needNewline || push(` `);
// 三元表达式的冒号
push(`: `);
// 如果是嵌套的条件表达式,需要添加缩进级别
const isNested = alternate.type === 19;
if (!isNested) {
context.indentLevel++;
}
// 这里是条件为假的情况
genNode(alternate, context);
// 如果是嵌套的条件表达式,需要减少缩进级别
if (!isNested) {
context.indentLevel--;
}
// 如果需要换行,执行减少缩进的操作(不带换行)
needNewline && deindent(
true
/* without newline */
);
}
验证代码如下:
js
const app = createApp({
template: '<div v-if="true">xxx</div>',
});
生成的代码如下:
js
const _Vue = Vue
const { createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = { key: 0 }
return function render(_ctx, _cache) {
with (_ctx) {
const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
return true
? (_openBlock(), _createElementBlock("div", _hoisted_1, "123"))
: _createCommentVNode("v-if", true)
}
}
genCacheExpression
缓存表达式主要使用于
v-once
指令,这个指令会将节点缓存起来,不会再进行更新;
genCacheExpression
函数用来生成缓存表达式,具体代码如下:
js
function genCacheExpression(node, context) {
// 辅助函数
const {push, helper, indent, deindent, newline} = context;
// 这里是生成缓存的 key
push(`_cache[${node.index}] || (`);
// 如果是 vnode 节点,则需要添加缩进和设置追踪标记
// 这里设置为 -1,表示不需要追踪
if (node.isVNode) {
indent();
push(`${helper(SET_BLOCK_TRACKING)}(-1),`);
newline();
}
// 设置缓存的值
push(`_cache[${node.index}] = `);
// 还是递归调用 genNode 函数来生成缓存的值
genNode(node.value, context);
// 启用缓存的追踪标记,并返回缓存的值
if (node.isVNode) {
push(`,`);
newline();
push(`${helper(SET_BLOCK_TRACKING)}(1),`);
newline();
push(`_cache[${node.index}]`);
deindent();
}
// 最后推入右括号
push(`)`);
}
验证代码如下:
js
const app = createApp({
template: '<div v-once>xxx</div>',
});
生成的代码如下:
js
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { setBlockTracking: _setBlockTracking, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, createElementVNode: _createElementVNode } = _Vue
return _cache[0] || (
_setBlockTracking(-1),
_cache[0] = _createElementVNode("div", null, [
_createTextVNode(_toDisplayString("xxx"), 1 /* TEXT */)
]),
_setBlockTracking(1),
_cache[0]
)
}
}
genNodeList
本质上
genNodeList
函数是处理所有 list 类型的数据,在上述的很多函数中都会使用到; 本案例为了能更清晰的看到它的作用,能在genNode
中看到它的调用,会使用 v-for + v-memo 来进行验证; v-memo 指令的作用不在本文章中进行讲解,需要的可以自行查阅资料;
genNodeList
函数用来生成节点列表,具体代码如下:
js
function genNodeList(nodes, context, multilines = false, comma = true) {
// 辅助函数
const {push, newline} = context;
// 直接遍历节点
for (let i = 0; i < nodes.length; i++) {
// 获取当前节点
const node = nodes[i];
// 如果是字符串,则直接推入
if (isString(node)) {
push(node);
}
// 如果是数组,则递归调用 genNodeListAsArray 函数
// genNodeListAsArray 函数在上面已经出现过了,这里就不再赘述了
else if (isArray(node)) {
genNodeListAsArray(node, context);
}
// 否则就递归调用 genNode 函数
else {
genNode(node, context);
}
// 如果不是最后一个
if (i < nodes.length - 1) {
// 如果是多行,则推入逗号和换行
if (multilines) {
comma && push(",");
newline();
}
// 否则就推入逗号和空格
else {
comma && push(", ");
}
}
}
}
验证代码如下:
js
const app = createApp({
template: '<div v-for="i in 10" v-memo="[i]">i</div>',
});
生成的代码如下:
js
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createTextVNode: _createTextVNode, isMemoSame: _isMemoSame, withMemo: _withMemo } = _Vue
return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(10, (i, __, ___, _cached) => {
const _memo = ([i])
if (_cached && _isMemoSame(_cached, _memo)) return _cached
const _item = (_openBlock(), _createElementBlock("div", null, [
_createTextVNode("i")
]))
_item.memo = _memo
return _item
}, _cache, 0), 256 /* UNKEYED_FRAGMENT */))
}
}
总结
本章节主要讲解了genNode
函数,这个函数是用来生成节点的,这个函数会根据节点的类型来调用不同的函数来生成节点,例如文本节点、表达式节点、插值节点等等;
而通过这一章也了解到了巨量的Vue
的模版语法可以书写的方式,其中的很多细节也只有翻源码才知道原来还可以这样玩;
到这里我们的模板到AST
的转换,在从AST
的转换到render
函数的代码生成,已经全部完成了;
这一块足足花了五章的内容来进行分析,信息量是巨大的,同时也算是完成了一个里程碑,收获满满,成就感也满满;
历史章节
- 【源码&库】跟着 Vue3 学习前端模块化
- 【源码&库】在调用 createApp 时,Vue 为我们做了那些工作?
- 【源码&库】细数 Vue3 的实例方法和属性背后的故事
- 【源码&库】Vue3 中的 nextTick 魔法背后的原理
- 【源码&库】Vue3 的响应式核心 reactive 和 effect 实现原理以及源码分析
- 【源码&库】跟着 Vue3 的源码学习 reactive 背后的实现原理
- 【源码&库】 Vue3 的依赖收集,这里的依赖指代的是什么?
- 【源码&库】 Vue3 的依赖收集和依赖触发是如何工作的
- 【源码&库】 Vue3 的组件是如何挂载的?
- 【源码&库】 Vue3 的组件是如何更新的?
- 【源码&库】 Vue3 的组件更新核心算法
- 【源码&库】 Vue3 的虚拟DOM生成规则
- 【源码&库】 Vue3 全局组件注册如何实现
- 【源码&库】Vue3的模板转换为AST的过程
- 【源码&库】Vue3的AST转换细节全解析
- 【源码&库】Vue3模版解析后的AST转换为render函数的过程