您现在的位置是:首页 >技术交流 >Vue3源码 第六篇-JavaScript AST transform函数网站首页技术交流
Vue3源码 第六篇-JavaScript AST transform函数
系列文章目录
Vue3源码 第一篇-总览
Vue3源码 第二篇-Reactive API
Vue3源码 第三篇-Vue3是如何实现响应性
Vue3源码 第四篇-Vue3 setup
Vue3源码 第五篇-Vue3 模版compile AST生成篇
文章目录
- 系列文章目录
- 前言
- 一、transform 转换
- 二、traverseNode 遍历节点,traverseChildren 遍历子节点
- 三、ignoreSideEffectTags 忽视副作用的标签
- 四、transformStyle 转换样式
- 五、warnTransitionChildren 检查过度动画的节点
- 六、transformOnce once指令转换
- 七、transformIf 、processIf if转换
- 八、transformFor、processFor
- 九、transformExpression 转换表达式
- 十、transformSlotOutlet 转换slot
- 十一、transformElement 转换元素
- 十二、trackSlotScopes 追踪slot的scopes
- 十三、transformText 转换文本
- 总结
前言
上一篇中了解了AST的生成过程,但生成的AST并非最终的AST,Vue3在生成渲染函数之前,对AST进行了进一步的转换和整理,也就是这篇文章的主要内容JavaScriptAST,为什么需要JavaScript AST呢?不是已经生成了AST么?其实可以看到,上一篇我们生成的AST只是对HTML节点的描述信息,但节点如何转变成渲染函数还无法表达,例如文字、元素、IF语句、FOR语句,ON事件等,AST对这些节点不具备直接转换成渲染函数的能力,所以需要进一步通过转换函数,将各类不同的节点进行更加具体的转换。转换函数有两大类型,一种为节点转换类型专门处理节点的转换,一种为指令转换类型来处理节点的属性,如有不对地方希望大家多多指正。下文主要先介绍节点转换函数,不多说了,进入正题,开冲!!!!
一、transform 转换
转换的入口函数,上一篇讲到的是const ast = isString(template) ? baseParse(template, options) : template
,下一步就是将ast转换成JavaScript AST的代码transform函数
function baseCompile(template, options = {}) {
// 错误处理及判断
// ......
// 生成语法树
const ast = isString(template) ? baseParse(template, options) : template;
// 获取节点转换器和指令转换器,text,element节点,for if等指令转换器,交给转换函数执行转换
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();
// 将ast转换成JavaScript AST
transform(ast, extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: extend({}, directiveTransforms, options.directiveTransforms || {} // user transforms
)
}));
// 通过语法树生成
return generate(ast, extend({}, options, {
prefixIdentifiers
}));
}
// 转换入口
function transform(root, options) {
// 获取转换所需的上下文,里面包括helpers等属性和各类帮助函数,记录转换过程中的信息和转换中所需的各种辅助函数
const context = createTransformContext(root, options);
// 对整个节点从根节点开始进行递归,对每一个节点进行转换
traverseNode(root, context);
...
// 根节点最后需要在进行一次处理
createRootCodegen(root, context);
...
}
二、traverseNode 遍历节点,traverseChildren 遍历子节点
通过递归该方法遍历所有的节点,对不同的节点类型进行处理,如果节点中包含子节点就会进入遍历子节点的方法,遍历子节点时会再次调用该方法进行递归调用,之后当前节点不需要继续处理子节点时,执行退出函数对当前节点进行转换,执行完退出函数后返回上一层递归。
function traverseNode(node, context) {
// 获取当前节点
context.currentNode = node;
// 获取节点转换的函数
const { nodeTransforms } = context;
// 退出函数,存放转换的回调函数,只有父节点下的子节点都执行完了,父节点才开始转换
const exitFns = [];
// 遍历所有的节点转换函数,下面会介绍这些转换函数
// 这些转换函数并非直接执行转换,还是对节点进行判断,如果符合转换的要求就返回一个退出函数
// 等待之后执行转换,if和for的转换还会对节点进行加工等操作
// 1.ignoreSideEffectTags
// 2.transformStyle
// 3.warnTransitionChildren
// 4.transformOnce
// 5.transformIf
// 6.transformFor
// 7.transformExpression
// 8.transformSlotOutlet
// 9.transformElement
// 10.trackSlotScopes
// 11.transformText
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);
}
}
// 在转换过程中会存在节点改变的情况,比如for/if会替换当前节点。所以这里会对节点进行处理。
if (!context.currentNode) {
// node was removed
return;
}
else {
// node may have been replaced
node = context.currentNode;
}
}
// 当前节点遍历完转换函数后,根据当前节点的类型,执行不同的分支,这里去掉了注释的分支
switch (node.type) {
...
// 插值 {{name}}
case 5 /* INTERPOLATION */:
if (!context.ssr) {
context.helper(TO_DISPLAY_STRING);
}
break;
// if语句,父执行上边的转换后节点会有改变,这里会对分支节点进行进一步处理
case 9 /* IF */:
for (let i = 0; i < node.branches.length; i++) {
traverseNode(node.branches[i], context);
}
break;
// if的分支节点,for循环节点,元素节点,根节点都会遍历其子节点,对其子节点进一步转换
case 10 /* IF_BRANCH */:
case 11 /* FOR */:
case 1 /* ELEMENT */:
case 0 /* ROOT */:
traverseChildren(node, context);
break;
}
// 这里才开始执行真正的转换函数
// 当前节点无需再遍历子节点时执行本节点前面放入的退出函数
// 放在最后执行转换函数保证了子节点都转换完毕了再执行父节点的转换
context.currentNode = node;
let i = exitFns.length;
while (i--) {
exitFns[i]();
}
}
这里就是对每一个子节点进行traverseNode调用
function traverseChildren(parent, context) {
let i = 0;
const nodeRemoved = () => {
i--;
};
for (; i < parent.children.length; i++) {
const child = parent.children[i];
if (isString(child))
continue;
context.parent = parent;
// 记录索引部分处理中对节点进行删除等操作时使用到
context.childIndex = i;
// 在处理中例如if分支处理时会使用该函数移除节点
context.onNodeRemoved = nodeRemoved;
// 调用遍历节点
traverseNode(child, context);
}
}
三、ignoreSideEffectTags 忽视副作用的标签
忽略script和style标签
const ignoreSideEffectTags = (node, context) => {
if (node.type === 1 /* ELEMENT */ &&
node.tagType === 0 /* ELEMENT */ &&
(node.tag === 'script' || node.tag === 'style')) {
context.onError(createDOMCompilerError(59 /* X_IGNORED_SIDE_EFFECT_TAG */, node.loc));
context.removeNode();
}
};
四、transformStyle 转换样式
const transformStyle = node => {
// 如果是元素节点
if (node.type === 1 /* ELEMENT */) {
// 遍历元素节点上的属性值
node.props.forEach((p, i) => {
// 如果存在样式属性,并且有值
if (p.type === 6 /* ATTRIBUTE */ && p.name === 'style' && p.value) {
// 转换为表达式描述,将参数和值进行表达式转换
node.props[i] = {
type: 7 /* DIRECTIVE */,
name: `bind`,
arg: createSimpleExpression(`style`, true, p.loc),
exp: parseInlineCSS(p.value.content, p.loc),
modifiers: [],
loc: p.loc
};
}
});
}
};
五、warnTransitionChildren 检查过度动画的节点
对过渡动画的子节点进行检查,只允许一个子节点,对for、if节点有单独的判断
const warnTransitionChildren = (node, context) => {
if (node.type === 1 /* ELEMENT */ &&
node.tagType === 1 /* COMPONENT */) {
// 是否是内置节点
const component = context.isBuiltInComponent(node.tag);
// 如果节点是transition
if (component === TRANSITION$1) {
return () => {
// 检查是否有多个子节点
if (node.children.length && hasMultipleChildren(node)) {
context.onError(createDOMCompilerError(58 /* X_TRANSITION_INVALID_CHILDREN */, {
start: node.children[0].loc.start,
end: node.children[node.children.length - 1].loc.end,
source: ''
}));
}
};
}
}
};
六、transformOnce once指令转换
const seen = new WeakSet();
const transformOnce = (node, context) => {
// 如果是元素节点,就寻找once指令
if (node.type === 1 /* ELEMENT */ && findDir(node, 'once', true)) {
// 如果已经有了就直接返回
if (seen.has(node)) {
return;
}
seen.add(node);
// 添加helper
context.helper(SET_BLOCK_TRACKING);
// 创建once表达式
return () => {
const cur = context.currentNode;
if (cur.codegenNode) {
cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */);
}
};
}
};
七、transformIf 、processIf if转换
if的转换分为2步,第一步是执行processIf函数对if节点进行加工,加工完之后再返回一个退出函数等待子节点执行完毕后进行codegen
const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/, (node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// 获取兄弟节点
const siblings = context.parent.children;
// 获取if节点的位置
let i = siblings.indexOf(ifNode);
let key = 0;
while (i-- >= 0) {
const sibling = siblings[i];
if (sibling && sibling.type === 9 /* IF */) {
key += sibling.branches.length;
}
}
// 返回结束子元素转换后的该节点的处理函数,Codegen会在下一篇中具体讲解
return () => {
if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(branch, key, context);
}
else {
// attach this branch's codegen node to the v-if root.
const parentCondition = getParentCondition(ifNode.codegenNode);
parentCondition.alternate = createCodegenNodeForBranch(branch, key + ifNode.branches.length - 1, context);
}
};
});
});
function processIf(node, dir, context, processCodegen) {
// 如果指令不是else,并且不存在表达式或者表达式内容为空时需要报错,重新设置表达式内容为true
if (dir.name !== 'else' &&
(!dir.exp || !dir.exp.content.trim())) {
const loc = dir.exp ? dir.exp.loc : node.loc;
context.onError(createCompilerError(27 /* X_V_IF_NO_EXPRESSION */, dir.loc));
dir.exp = createSimpleExpression(`true`, false, loc);
}
// 检查表达式是否符合规范有无关键字等
if (dir.exp) {
validateBrowserExpression(dir.exp, context);
}
// 如果指令是if
if (dir.name === 'if') {
// 构建if的表达式
const branch = createIfBranch(node, dir);
// 这里重新构建if节点,将刚才创建的if分支的表达式放入到分支数组中去,
// 其他的else-if和else分支也会在之后添加到branches中去
const ifNode = {
type: 9 /* IF */,
loc: node.loc,
branches: [branch]
};
// 对节点进行替换
// 需要把上下文if节点的父节点素的子节点替换成新的if节点,同时也要替换掉当前的if节点
// context.parent.children[context.childIndex] = context.currentNode = node;
context.replaceNode(ifNode);
// 返回代码生成函数作为结束函数,等待子元素都转换完毕后调用
if (processCodegen) {
return processCodegen(ifNode, branch, true);
}
}
// 非if分支
else {
// 获取父节点下所有的子节点
const siblings = context.parent.children;
const comments = [];
// 获取当前节点位置
let i = siblings.indexOf(node);
while (i-- >= -1) {
// 获取兄弟元素 找到相邻的 V-if,如果识别到v-else 和 v-else-if 需要向前寻找v-if
const sibling = siblings[i];
// 如果是注释就从父节点中移除,并添加到注释集合中
if (sibling && sibling.type === 3 /* COMMENT */) {
context.removeNode(sibling);
comments.unshift(sibling);
continue;
}
// 空文本也移除出去
if (sibling &&
sibling.type === 2 /* TEXT */ &&
!sibling.content.trim().length) {
context.removeNode(sibling);
continue;
}
// 如果是if分支就需要移除该节点,并且创建if分支表达式
if (sibling && sibling.type === 9 /* IF */) {
// 删除当前正在traverseNode的节点
context.removeNode();
// 创建if分支的对象表达式
const branch = createIfBranch(node, dir);
if (comments.length) {
branch.children = [...comments, ...branch.children];
}
// 检查用户是否在不同的分支上强制使用相同的键
{
const key = branch.userKey;
if (key) {
sibling.branches.forEach(({ userKey }) => {
if (isSameKey(userKey, key)) {
context.onError(createCompilerError(28 /* X_V_IF_SAME_KEY */, branch.userKey.loc));
}
});
}
}
// 将自身分支添加到if主分支上,因为在上边已经重建构建了if分支节点,这里将else-if 和 else的分支
// 表达式写入if节点的branches中去
sibling.branches.push(branch);
const onExit = processCodegen && processCodegen(sibling, branch, false);
// 因为节点被移除,需要手动遍历节点,现在的节点类型为10,是IF_BRANCH类型
traverseNode(branch, context);
// 因为再次执行traverseNode时,因为node.type已经改为10了,所以无法执行if分支的结束函数,
// 所以需要这里手动调用
if (onExit)
onExit();
// 执行完后重置当前节点
context.currentNode = null;
}
// 兄弟节点中不存在v-if就需要报错,每次处理分支都会删除if分支节点,所以一旦出现找不到的情况
// 就说明没有if节点
else {
context.onError(createCompilerError(29 /* X_V_ELSE_NO_ADJACENT_IF */, node.loc));
}
break;
}
}
}
八、transformFor、processFor
跟if类似都需要先执行加工函数processFor对节点进行加工,之后再返回退出函数。
const transformFor = createStructuralDirectiveTransform('for', (node, dir, context) => {
const { helper, removeHelper } = context;
// forNode方法是在执行processFor函数时触发
return processFor(node, dir, context, forNode => {
// 创建循环渲染函数表达式,并添加遍历完所有子项后退出时的迭代器
const renderExp = createCallExpression(helper(RENDER_LIST), [
forNode.source
]);
// 获取key属性
const keyProp = findProp(node, `key`);
const keyProperty = keyProp
? createObjectProperty(`key`, keyProp.type === 6 /* ATTRIBUTE */
? createSimpleExpression(keyProp.value.content, true)
: keyProp.exp)
: null;
// 是否是稳定的片段
const isStableFragment = forNode.source.type === 4 /* SIMPLE_EXPRESSION */ &&
forNode.source.constType > 0 /* NOT_CONSTANT */;
const fragmentFlag = isStableFragment
? 64 /* STABLE_FRAGMENT */
: keyProp
? 128 /* KEYED_FRAGMENT */
: 256 /* UNKEYED_FRAGMENT */;
// 获取codegenNode,具体的之后的文章讲解
forNode.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, renderExp, fragmentFlag +
(` /* ${PatchFlagNames[fragmentFlag]} */` ), undefined, undefined, true /* isBlock */, !isStableFragment /* disableTracking */, node.loc);
return () => {
// 已经遍历了所有子代,完成代码生成
let childBlock;
// 是否是template节点
const isTemplate = isTemplateNode(node);
// 获取子节点
const { children } = forNode;
// 检查是否把key写在了子节点上
// <template v-for> key should be placed on the <template> tag
if (isTemplate) {
node.children.some(c => {
if (c.type === 1 /* ELEMENT */) {
const key = findProp(c, 'key');
if (key) {
context.onError(createCompilerError(32 /* X_V_FOR_TEMPLATE_KEY_PLACEMENT */, key.loc));
return true;
}
}
});
}
// 如果有多个子节点或第一个子节点的类型不是元素(类似于仅包裹了text)
const needFragmentWrapper = children.length !== 1 || children[0].type !== 1 /* ELEMENT */;
const slotOutlet = isSlotOutlet(node)
? node
: isTemplate &&
node.children.length === 1 &&
isSlotOutlet(node.children[0])
? node.children[0] // api-extractor somehow fails to infer this
: null;
if (slotOutlet) {
// <slot v-for="..."> or <template v-for="..."><slot/></template>
childBlock = slotOutlet.codegenNode;
if (isTemplate && keyProperty) {
// <template v-for="..." :key="..."><slot/></template>
// we need to inject the key to the renderSlot() call.
// the props for renderSlot is passed as the 3rd argument.
injectProp(childBlock, keyProperty, context);
}
}
else if (needFragmentWrapper) {
// <template v-for="..."> 具有文本或多元素
// 为每个循环生成一个片段块
childBlock = createVNodeCall(context, helper(FRAGMENT), keyProperty ? createObjectExpression([keyProperty]) : undefined, node.children, 64 /* STABLE_FRAGMENT */ +
(` /* ${PatchFlagNames[64 /* STABLE_FRAGMENT */]} */`
), undefined, undefined, true);
}
else {
// 普通的v-for,直接使用子的codegenNode
// but mark it as a block.
childBlock = children[0]
.codegenNode;
if (isTemplate && keyProperty) {
injectProp(childBlock, keyProperty, context);
}
if (childBlock.isBlock !== !isStableFragment) {
if (childBlock.isBlock) {
// switch from block to vnode
removeHelper(OPEN_BLOCK);
removeHelper(CREATE_BLOCK);
}
else {
// switch from vnode to block
removeHelper(CREATE_VNODE);
}
}
childBlock.isBlock = !isStableFragment;
if (childBlock.isBlock) {
helper(OPEN_BLOCK);
helper(CREATE_BLOCK);
}
else {
helper(CREATE_VNODE);
}
}
renderExp.arguments.push(createFunctionExpression(createForLoopParams(forNode.parseResult), childBlock, true /* force newline */));
};
});
});
function processFor(node, dir, context, processCodegen) {
// 如果不存在表达式
if (!dir.exp) {
context.onError(createCompilerError(30 /* X_V_FOR_NO_EXPRESSION */, dir.loc));
return;
}
// 只能是简单的表达式,因为在表达式转换之前应用了 vFor 转换。
const parseResult = parseForExpression(
dir.exp, context);
if (!parseResult) {
context.onError(createCompilerError(31 /* X_V_FOR_MALFORMED_EXPRESSION */, dir.loc));
return;
}
const { addIdentifiers, removeIdentifiers, scopes } = context;
// 获取源,值得别名,键的别名,index的别名
const { source, value, key, index } = parseResult;
const forNode = {
type: 11 /* FOR */,
loc: dir.loc,
source,
valueAlias: value,
keyAlias: key,
objectIndexAlias: index,
parseResult,
children: isTemplateNode(node) ? node.children : [node]
};
// 和if一样也要替换当前节点
context.replaceNode(forNode);
// 记录vFor
scopes.vFor++;
// 执行获取codegen代码,作为退出函数
const onExit = processCodegen && processCodegen(forNode);
// 返回for节点的退出函数
return () => {
scopes.vFor--;
if (onExit)
onExit();
};
}
九、transformExpression 转换表达式
对表达式进行检查,确保表达式里不包含关键字,无论是插值还是指令中是否包含非法的关键字,比如{{let a}}
{{for}}
const transformExpression = (node, context) => {
// 如果是插值的话
if (node.type === 5 /* INTERPOLATION */) {
// 验证表达式是否没问题,是否含有关键字等
node.content = processExpression(node.content, context);
}
// 如果是元素节点
else if (node.type === 1 /* ELEMENT */) {
// handle directives on element
// 获取节点行的属性
for (let i = 0; i < node.props.length; i++) {
// 获取属性
const dir = node.props[i];
// do not process for v-on & v-for since they are special handled
// 7是指令类型,这里过滤了for指令,for指令有单独的处理器
if (dir.type === 7 /* DIRECTIVE */ && dir.name !== 'for') {
// 获取表达式
const exp = dir.exp;
// 获取参数
const arg = dir.arg;
// do not process exp if this is v-on:arg - we need special handling
// for wrapping inline statements.
if (exp &&
exp.type === 4 /* SIMPLE_EXPRESSION */ &&
!(dir.name === 'on' && arg)) {
dir.exp = processExpression(exp, context,
// slot args must be processed as function params
dir.name === 'slot');
}
if (arg && arg.type === 4 /* SIMPLE_EXPRESSION */ && !arg.isStatic) {
dir.arg = processExpression(arg, context);
}
}
}
}
};
十、transformSlotOutlet 转换slot
const transformSlotOutlet = (node, context) => {
// 判断是否是slot节点
if (isSlotOutlet(node)) {
const { children, loc } = node;
const { slotName, slotProps } = processSlotOutlet(node, context);
const slotArgs = [
context.prefixIdentifiers ? `_ctx.$slots` : `$slots`,
slotName
];
if (slotProps) {
slotArgs.push(slotProps);
}
if (children.length) {
if (!slotProps) {
slotArgs.push(`{}`);
}
slotArgs.push(createFunctionExpression([], children, false, false, loc));
}
if (context.scopeId && !context.slotted) {
if (!slotProps) {
slotArgs.push(`{}`);
}
if (!children.length) {
slotArgs.push(`undefined`);
}
slotArgs.push(`true`);
}
node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);
}
};
十一、transformElement 转换元素
对所有元素标签进行转换,包括html标签以及组件,下面省略的部分属性和特殊处理代码,重点看普通html标签的处理过程
const transformElement = (node, context) => {
return function postTransformElement() {
// 如果不是元素或组件就直接退出,比如文字会直接退出,if,for等节点转换后type都不为1,所以会跳过
node = context.currentNode;
if (!(node.type === 1 /* ELEMENT */ &&
(node.tagType === 0 /* ELEMENT */ ||
node.tagType === 1 /* COMPONENT */))) {
return;
}
// tag 为 html元素的标签,props为属性值比如id,on(事件)等
const { tag, props } = node;
...
let vnodePatchFlag;
let patchFlag = 0;
...
// props
// 构建属性值
if (props.length > 0) {
// 构建属性,并且根据属性计算patchFlag
const propsBuildResult = buildProps(node, context);
// 获取属性值
vnodeProps = propsBuildResult.props;
// 获取修补flag
patchFlag = propsBuildResult.patchFlag;
// 获取动态节点属性
dynamicPropNames = propsBuildResult.dynamicPropNames;
const directives = propsBuildResult.directives;
vnodeDirectives =
directives && directives.length
? createArrayExpression(directives.map(dir => buildDirectiveArgs(dir, context)))
: undefined;
}
// children
if (node.children.length > 0) {
...
// 这里省略了keep-alive的处理
...
...
// 省略组件相关的处理
...
// 如果只有一个子节点,并且不是TELEPORT
else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
// 获取子节点
const child = node.children[0];
const type = child.type;
// 子节点是否是动态文字,看类型是否是插值,或者是插值和静态的组合
// {{name}} 这种是type为5的插值类型, hello {{name}} 会在text转换中转换为 type为8的组合表达式
// 这两种均为动态的子节点
const hasDynamicTextChild = type === 5 /* INTERPOLATION */ ||
type === 8 /* COMPOUND_EXPRESSION */;
//
if (hasDynamicTextChild &&
// 获取常量类型,更新patchFlag
getConstantType(child, context) === 0 /* NOT_CONSTANT */) {
// patchFlag = patchFlag | 1 patchFlag 与 0001 按照位或运算,为之后patch过程做优化
patchFlag |= 1 /* TEXT */;
}
// pass directly if the only child is a text node
// (plain / interpolation / expression)
if (hasDynamicTextChild || type === 2 /* TEXT */) {
vnodeChildren = child;
}
else {
vnodeChildren = node.children;
}
}
else {
vnodeChildren = node.children;
}
}
// 动态节点
if (patchFlag !== 0) {
{
if (patchFlag < 0) {
// special flags (negative and mutually exclusive)
vnodePatchFlag = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`;
}
else {
// bitwise flags 按位获取pathch flag
// PatchFlagNames 为以下值
// [1 /* TEXT */]: `TEXT`,
// [2 /* CLASS */]: `CLASS`,
// [4 /* STYLE */]: `STYLE`,
// [8 /* PROPS */]: `PROPS`,
// [16 /* FULL_PROPS */]: `FULL_PROPS`,
// [32 /* HYDRATE_EVENTS */]: `HYDRATE_EVENTS`,
// [64 /* STABLE_FRAGMENT */]: `STABLE_FRAGMENT`,
// [128 /* KEYED_FRAGMENT */]: `KEYED_FRAGMENT`,
// [256 /* UNKEYED_FRAGMENT */]: `UNKEYED_FRAGMENT`,
// [512 /* NEED_PATCH */]: `NEED_PATCH`,
// [1024 /* DYNAMIC_SLOTS */]: `DYNAMIC_SLOTS`,
// [2048 /* DEV_ROOT_FRAGMENT */]: `DEV_ROOT_FRAGMENT`,
// [-1 /* HOISTED */]: `HOISTED`,
// [-2 /* BAIL */]: `BAIL`
// 这里其实就是获取到底该元素有多少种patchFlag,上面合并patchFlag用的是|运算,这里解的时候
// 用&运算,就知道上面到底合并了多少种
// 如果patchFlag为9 从上表中我们能知道是1+8也就是text和props的组合 1000 | 0001 = 1001 (9)
// 我们拆解时做 & 运算 1001 & 0001 = 0001 所以得到 这里包含 text
// 1001 & 1000 = 1000 如果和class做运算 1001 & 0010 = 0000 这里filter条件就为false
// 所以就通过这种巧妙地方式进行合并和拆解。
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n])
.join(`, `);
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`;
}
}
if (dynamicPropNames && dynamicPropNames.length) {
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames);
}
}
// 创建生成node节点的对象
// 在这里添加type,patchFlag,isBlock等属性
node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren, vnodePatchFlag, vnodeDynamicProps, vnodeDirectives, !!shouldUseBlock, false /* disableTracking */, node.loc);
};
};
十二、trackSlotScopes 追踪slot的scopes
这个暂时还不知道什么情况会使用,有了解的欢迎解答
const trackSlotScopes = (node, context) => {
if (node.type === 1 /* ELEMENT */ &&
(node.tagType === 1 /* COMPONENT */ ||
node.tagType === 3 /* TEMPLATE */)) {
// We are only checking non-empty v-slot here
// since we only care about slots that introduce scope variables.
const vSlot = findDir(node, 'slot');
if (vSlot) {
vSlot.exp;
context.scopes.vSlot++;
return () => {
context.scopes.vSlot--;
};
}
}
};
十三、transformText 转换文本
主要处理含有文本节点的父节点,需要对子文本节点进行组合,比如hello {{name}}
在AST中生成的节点是分开的2个节点,但JavaScript AST会将2个节点进行合并。
const transformText = (node, context) => {
if (node.type === 0 /* ROOT */ ||
node.type === 1 /* ELEMENT */ ||
node.type === 11 /* FOR */ ||
node.type === 10 /* IF_BRANCH */) {
// perform the transform on node exit so that all expressions have already
// been processed.
return () => {
// 获取ast该节点下的子节点
const children = node.children;
let currentContainer = undefined;
let hasText = false;
// 遍历子节点
for (let i = 0; i < children.length; i++) {
// 存储子节点,后面需要用
const child = children[i];
// 判断node.type为5或者为2就是text类型,5是插值,2是文本
if (isText(child)) {
hasText = true;
// 循环拼接
for (let j = i + 1; j < children.length; j++) {
// 先获取下一个子节点
const next = children[j];
// 如果下一个子节点是文本
if (isText(next)) {
// 不存在当前容器,第一次循环为undefined
if (!currentContainer) {
// 构建当前容器,重新组装该节点,将type改为复合表达式(8),将自身放入新节点的子节点中
currentContainer = children[i] = {
type: 8 /* COMPOUND_EXPRESSION */,
loc: child.loc,
children: [child]
};
}
// merge adjacent text node into current
// 将下一节点加入到容器的子节点中
currentContainer.children.push(` + `, next);
// 把下一个节点删除
children.splice(j, 1);
// 减一,在删除了开始节点后面的节点后,splice已改变了数组长度,这样需要j--才可以获取到对的下一个节点
j--;
}
else {
// 如果存在节点不是text就清空当前容器,并且退出
currentContainer = undefined;
break;
}
}
}
}
// 如果内部不存在纯文本或插值,或者已经合并好了并且为根节点或html标签节点
if (!hasText ||
// if this is a plain element with a single text child, leave it
// as-is since the runtime has dedicated fast path for this by directly
// setting textContent of the element.
// for component root it's always normalized anyway.
(children.length === 1 &&
(node.type === 0 /* ROOT */ ||
(node.type === 1 /* ELEMENT */ &&
node.tagType === 0 /* ELEMENT */)))) {
return;
}
// pre-convert text nodes into createTextVNode(text) calls to avoid
// runtime normalization.
// 当前的children已经是合并和删减完的children了,里面包含文本和插值的合并,以及其他无法合并的节点
for (let i = 0; i < children.length; i++) {
// 获取第一个子节点
const child = children[i];
// 子节点是文字或插槽,或者是已经合并好的复合表达式
if (isText(child) || child.type === 8 /* COMPOUND_EXPRESSION */) {
const callArgs = [];
// createTextVNode defaults to single whitespace, so if it is a
// single space the code could be an empty call to save bytes.
// 如果节点不是纯文本或者节点内容不为空格
if (child.type !== 2 /* TEXT */ || child.content !== ' ') {
callArgs.push(child);
}
// mark dynamic text with flag so it gets patched inside a block
// 用标记标记动态文本,以便它在块中得到修补
if (!context.ssr &&
getConstantType(child, context) === 0 /* NOT_CONSTANT */) {
callArgs.push(1 /* TEXT */ +
(` /* ${PatchFlagNames[1 /* TEXT */]} */` ));
}
children[i] = {
type: 12 /* TEXT_CALL */,
content: child,
loc: child.loc,
codegenNode: createCallExpression(context.helper(CREATE_TEXT), callArgs)
};
}
}
};
}
};
总结
本篇文章简单介绍了transform的过程中的11个转换函数,以traverseNode为入口,通过递归调用遍历每一个节点,对每一个节点调用这11个转换函数,对符合要求的节点返回退出函数,在每一个节点退出时调用退出函数对节点进行codegen,本篇没有具体介绍codegen相关的代码,我们会在下一个篇章详细介绍有关codegen的代码。在最后如果本文有不正确的地方欢迎指正,希望大家多多点赞支持。