part2
我们来分析插件和gencode
的部分
另外如果没看过parse
以及part1
部分的可以去看下
坏蛋Dan:vue/compiler-core源码分析学习--day2: parse部分
坏蛋Dan:vue/compiler-core源码分析学习--day3: compile部分part1
有些东西指令的加工或者二次加工我们在compiler-dom/compiler-sfc上面说过了,所以这里就不说跟着options传进来的了。
接下来的分析是跟着执行顺序以及getBaseTransformPreset这个方法来的。
另外也会放一些通用函数代码
看名字就是用来处理.once修饰符的场景
这个方法有些没头没脑的,没办法需要和runtime搭配才行。如果发现.once这个修饰符,就将setBlockTracking这个辅助函数放入helper中。注意这里把inVOnce置为true。
然后返回一个回调,这个回调会在traverseNode方法的最后执行,不用担心isVOnce标志位被置为false,在执行的这个回调的时候子节点已经递归处理完毕了。
返回一个节点,类型是JS_CACHE_EXPRESSION
这个节点会作为当前节点的codegenNode, 注意这里的curr指的是当前的节点,也不用担心被切换到其它子节点去了,前面强调了很多次回调执行的时间,这个时候context.currentNode已经确保指回当前的节点了。
transformIf是一个高阶函数,通过传入一个函数给一个函数并最终返回一个函数。
在这里这个返回的函数就是transformIf。
我们传入的正则是/^(if|else|else-if)$/,表示如果prop匹配到if分支就命中这个传入的回调。
代码过长。。。现在有些后悔不拆分为几个主题分几篇文章写了。。。。
跑题了,回到代码中。
这货也是接受一个函数作为参数,不过它并没有返回一个函数,不过很不巧的是它的这个函数参数返回了一个函数,所以这货本质也是一个高阶函数。
而这个函数参数返回的函数最终是作为createStructuralDirectiveTransform这个方法收集到的方法并返回给traverseNode方法中。
这中间有好几层套娃,在开始分析这个函数和它的参数之前,我们来缕清这之间的套娃关系。
直接画图吧。。。。
那么在理解这些函数之间的关系之后我们再回来分析代码就会简单很多。
回到processIf代码中
这个时候的tagType == TEMPLATE就派上用场了,之前parse过滤TEMPLATE的时候是不会过滤IF/FOR/SLOT等场景的。
创建了一个IF_BRANCH类型的节点
而findProp就不看了,就是用来找它的某个静态属性的,在这里用来找key
回到processIf中
如果不是v-else的if指令但是没有表达式,直接报错。老规矩报错的代码咱就不看了。
接着如果context.prefixIdentifiers是true并且这个if指令有表达式也就是exp的话就提前处理这个表达式,为什么是提前呢?因为if指令的处理是在表达式的处理之前
接着如果是v-if指令则创建一个IF_BRANCH类型的节点,而这个branch节点则作为这个IF节点的子节点。
如果不是v-if指令,那就有可能是v-else以及v-else-if。
在处理v-else/else-if之前,还需要处理这个v-else/else-if节点之前的注释节点,都给它们移除先,为什么要这么做呢?因为if branch需要搭配注释节点,如果if条件是false,那么它就会被一个注释节点替代,当它变成true的时候它又会替换回来,当然你的注释都给你留着,不会做删掉你代码的行为的。另外没有用的节点也都去掉,比如空文本节点。
接着是判断这个else-if/else的节点之前是否存在v-if或者v-else-if节点,为什么要这么做呢?因为v-if和v-else-if/else之间是没有联系的,在ast中它们就是独立的节点。所以这个时候发现这是个v-else-if/v-else的节点之后,它得先回去看下是否存在v-if/v-else-if分支才行。
注意这里判断的时候是判断前一个节点里的branches的最后一个节点,为什么呢?因为如果这是个符合要求的if branch,那么它就会被移动到node.branches里面去,这样就能建立联系了。
这里还有一个fix,当transition组件里的第一个节点使用if指令的话,就得忽略掉全部注释,具体原因可以看注释里的issue。除transition之外的就把注释节点放到它的children里面,也就是说原本和注释同级的关系,现在变成了父子关系,这样也保留了注释的位置。
这里还有一个对key属性的处理,你有可能把for block的标志位key放到了一个if branch节点上,如果每个节点都一样的话是有问题的,因为vue会尽可能的复用每一个dom,如果你这俩key一样,当条件变了之后,这个节点和它的子节点们会被会复用,那么你这个if条件就有问题了,当然,如果tag不同应该是可以避免这个问题的。
最后再执行传入的回调,当然同时还得traverseNode一遍这个节点的子节点,为什么呢?因为这个if节点已经被移动到IF类型的节点的 branches里面去了,所以它的子节点不手动调用traverseNode的话是不会被遍历处理的。
注意最后这里把原来应该返回出去的回调给执行了,这个点和v-if分支的不同。
接下来我们来看下这个回调做了什么。
这个函数的调用位置是在节点被移除之后。
看上面的图,这个函数中返回的回调是在traverseNode函数的最后调用的,这就意味着这个时候的ast节点们都已经有了codegenNode了,也就是经过nodeTransforms里注册的插件们的摧残了。
开始先是找到这个IF_BRANCH节点的父节点也就是IF类型的节点的位置,然后把它之前的IF节点的IF_BRANCH分支的节点的个数都加起来作为这个IF类型的节点包括它branch分支里的IF_BRANCH节点的key值,为什么要这么做呢?我们先往下看。
注意这个createChildrenCodegenNode处理的是这个branch的children也就是子节点而不是branches。
还记得之前说过的v-if/v-for等都是会创建一个block的,这里显然就是创建一个block,如果这个branch的子节点(注意是子节点而不是所有子孙节点)不止一个,那就得创建一个fragement来包裹它们,当然,如果第一个节点不是ELEMENT的话那也得用fragement包裹它们,因为它可能是FOR等会创建一个block的类型。
如果节点数只有一个并且第一个子节点是FOR类型的,那直接复用同一个fragement就行了。但是如果节点数不止一个,也就是说在FOR节点下面还跟着一些同级节点,这个时候自然就不能复用同一个fragement了,自然需要重新创建一个fragement。
现在我们再来看下这个injectProp。
这个方法一看就是用来将其中一个节点的属性注入到另一个节点里面
由于这个方法中的场景我们并不清楚,比如这个props出现的条件,我这里思考的几种场景比如for的目标是一个scope variable等都是undefined,所以我这里也只能简单的猜测下发生了什么事。
那么这个指令的处理我们终于分析完了,我们来简单的总结下
最后来看下render function
补充: 1. 貌似这个keyIndex是用来干啥的还是不清楚,其实这个key是每个节点都会有的,到时候runtime的diff操作就需要这个key来判断是否需要re-render。而这个key代表着这个节点的位置。
在看代码之前,我们先来看下/熟悉下v-memo的用处 Built-in Directives | Vue.js (vuejs.org)
这个方法的逻辑没啥好说的,就是创建一个函数调用表达式节点,辅助函数是withMemo。直接来看render function舒服一点
没啥好说的,就是做了cache缓存处理
transformFor的执行流程和v-if类似的,所以图我就不放了,参考transformIf里的即可。
既然是类似的,我这里就不放代码了,到时候接触到再放出来。
我们先来看下processFor的代码
不过有几个地方需要注意。
/(\s\S?)\s+(?:in|of)\s+(\s\S)/
当迭代的是一个object,那么你可以传入三个参数,比如
。回到processFor里面
这俩方法是用来处理scope variable的,当进入这个block的scope的时候就add,当出了就remove。
简单的说下这个processFor方法,重点是parseForExpression,解析出for表达式的数据,然后将它们add到context里的,因为从这里开始已经进入了for的scope里。然后执行回调参数,返回一个函数,这个函数也是和processIf的一样,返回的这个回调会在这个节点被处理完之后再执行,所以这个时候已经退出scope了,自然就得将它们remove。
那么现在来看下传入processFor的回调
在这之前,需要注意的一点是这个函数参数执行的位置并不在processFor的返回函数中,所以它的执行是在transformFor这个插件的处理过程中就执行的。
这里有个地方需要注意:前面说了这个函数参数是在插件执行过程中执行的,但是这个函数参数的返回函数却是在所有插件执行之后才执行的,也就是过了codegen阶段。
简单的总结下:
这个函数参数中实际上是在创建v-for的codegenNode。
有几个特殊场景需要说下:
另外这里还有兼容v-memo的场景。最后生成的render function其实前面分析v-memo的时候你已经看过了,这里再贴一次。
补充:vForNode搭配的辅助函数是renderList
因为篇幅和兼容性问题(filter在3.x中废弃),这里咱就不分析了,感兴趣的大佬可以自行去了解
这个插件处理的场景比较特殊
先来看下/复习下这个v-slot属性 Built-in Directives | Vue.js (vuejs.org)
然后我们来复现下场景
这个场景需要一个for和v-slot同时存在一个template节点上
这个插件很简单,就是这个特殊场景给它加上scope variable。
在出scope的时候再移除这些scope variable。具体为啥要这么做呢?我们后面分析vSlot的时候会分析到,这里先mark下。
这个方法也是相当的简单(不包括processExpression,它里面的东西很多很杂)
就是将这个节点的所有涉及到表达式的内容转换成节点,比如指令的表达式,null语法中的表达式,其中v-on & v-for的场景需要额外插件处理,比较特殊。
这里需要注意的一点是slot的会被转换成一个函数参数节点。
注意这个slotoutlet处理的是而不是v-slot。
然后我们来看下processSlotOutlet的代码
这个方法也没啥好说的,收集prop,找到name字段,然后返回。
这个插件是重点插件
resolveComponentType
:代码分析放下面,这里简单地说就是确定这个组件的具体类型,比如内置组件,自定义组件等。isDynamicComponent
:是否是动态组件。shouldUseBlock
:是否需要开启block
包裹,如果是动态组件/TELEPORT
/SUSPENSE
以及svg/foreignObject
这几种情况都是需要开启的。buildProps
这个方法代码过长,所以这里不贴代码,分析也放到下面去,不然篇幅又不够了,这里简单地说就是处理节点的prop
,给prop
分门别类,比如directives
、props
等。这里的props
就是之前我们埋的一个坑的来源。buildDirectiveArgs
:这个方法也放到下面分析,看名字就知道是给directive
的arg
创建节点的。buildSlots
:同buildProps
。stringifyDynamicPropNames
:这个方法也不看代码了,就是将动态的属性收集然后字符串化,比如'[xxxx, xxx]'
createVNodeCall
:这回我们倒是要看一下了,因为现在是codegenNode
的核心代码没什么内容,但是还是要贴出来,有个意识,知道它里面有哪些东西,这些东西代表着什么
这插件只处理元素和组件节点。
先是处理props
,收集directives/dynamicPropNames/props(attribute)
。
如果存在子节点,那么就有下面几种场景:
v-slot
的节点。teleport
这个内置组件,那么就判断这个子节点是否是一个动态的文本节点({{}}
或者复合表达式比如多个表达式之类的),如果确实是并且它的flag
是NOT_CONSTANT
,那么当前我们要处理的这个元素节点的flag
就标记为PatchFlags.TEXT
,这类型看注释是Indicates an element with dynamic textContent (children fast path)
。也就是声明这个元素节点是一个带有动态文本内容的节点。根据上面三种情况收集子节点。
然后收集这个节点的flag
。
最后创建codegenNode
补充:
这里有一点需要注意,那就是这个transformElement
插件的执行的时间点,它也是作为回调执行的,所以也是在节点被nodeTransforms
里注册的插件遍历完之后再处理的,不过这个不是重点。
还记得之前我们说到过的这些回调的调用顺序是逆向的,就像是调用栈一样,所以transformElement
插件的回调实际上执行的时间是比transformIf
、transformFor
早的,这也就是为什么那俩插件可以判断codegenNode
了。
isComponentTag
:不用看代码了,判断tag
是否是component/Component
。RESOLVE_DYNAMIC_COMPONENT
:也就是辅助函数resolve_dynamic_component
。组件有以下几种场景
component tag
搭配is
属性;第二种则是3.x
中支持的原生元素也可以用is
属性,但是is
属性的值的表达式要用vue:
开头。Teleport、Transition、KeepAlive、Suspense
等。setup bindings
应该是inline mode
场景的,还有一个就是常规的)注意这里的内置组件代码中有一段if (!ssr) context.helper(builtIn)
,这也就是为什么我们不需要手动导入这些内置组件就能直接使用的原因。
resolveSetupReference
:这个方法就不看了,是inline mode
场景的。buildProps
我这里说一下里面做了什么
type == NodeTypes.ATTRIBUTE
,也就是静态属性的场景:ref
并且是在v-for
的scope
里面的,这个时候就需要创建一个key
的content
为ref_for
的对象属性节点,将它push
到properties
数组里。is
搭配tag
为component
或者它的exp
是vue:
开头的,那么它就是一个动态组件,应该跳过,因为这里是处理静态的。这里你应该会奇怪为什么这个的type
是一个ATTRIBUTE
,因为这个属性没有1用:
或者v-bind
开头,所以在最开始分配的时候就把它分配为静态的了。所以我们写代码的时候动态的就尽量用:
或者v-bind
,这样在parse
阶段就可以分辨出来了。v-slot
如果没有搭配component
,那么直接报错v-slot can only be used on components or <template> tags.
,然后跳过v-once/v-memo
,因为已经有插件来处理它们了。key
以及vue:before-update
这俩,那就需要开启block
ref
在v-for
的block
里面也是得创建key
的content
为 res_for
的对象属性节点。v-bind
或者v-on
,但是它没有arg
节点,也就是v-on:xx="xxxx"
的xx
。实际上这是一种特殊的写法,v-bind
的exp
是一个对象,这个时候是可以的,比如:v-bind="{ key: 123 }"
。在2.x
的时候,这种方式是不会覆盖原来已有属性的,而3.x
则相反,后面的会覆盖前面的。具体请看:v-bind Merge Behavior | Vue 3 Migration Guide (vuejs.org)。而v-on
也是可以传入对象来绑定事件的,会创建一个JS_CALL_EXPRESSION
的节点,辅助函数是toHandlers
,这个方法是不是很眼熟?其实我们之前分析injectProp
也就是注入属性的时候,就遇到了这个问题。结合当时分析的内容,我们可以确定这里的props
以及toHandlers
就是上边遇到的那俩。由于我们是动态传入一个对象,所以这个对象只能是在runtime
的时候才能知道是什么东西,所以合并属性的过程自然就只能在runtime
阶段。这也就是为什么需要再包裹一层mergeProps
的辅助函数。context.directiveTransforms
里注册的插件来处理这些指令,这里就暂时不分析插件里做了什么,我们继续先往下看。然后收集这些处理之后的指令。runtimeDirectives
中,因为编译器压根不知道是啥,得等到runtime
阶段才知道,并且为了安全,直接开block
,避免有涉及到before-update
的调用相关的。如果存在v-bind="object"/v-on="object"
的就合并props
,当然是在runtime
阶段,辅助函数是mergeProps
。
接着是根据这些个属性/指令等来进一步确定这个节点的flag
:
v-bind/on="object"
,那么就当做FULL_PROPS
,来看下这个类型的注释When keys change, a full diff is always needed to remove the old key.
,也就是说patch
阶段没办法绕过它的属性,diff
需要全量。另外它和CLASS、PROPS、STYLE
互斥。key
:1. 不是组件但是有动态class
绑定,这个时候flag
类型是CLASS
;2. 不是组件但是有动态样式绑定,flag
为STYLE
,这个类型有部分是可以hoist
的,比如:style="{ color: 'red' }"
就没有动态的值,这个时候可以staticHoist
;3. 有部分属性是动态的,flag
类型是PROPS
,这个时候之前收集的动态prop
就很有用了,到时候patch
时不需要diff
所有属性,只需要diff
这些个动态的即可;4. 最后一种和事件有关,标记为HYDRATE_EVENTS
,也就是混合事件。如果不需要开启block
&& 没有flag
或者flag
是HYDRATE_EVENTS
&& 有ref
属性或者有生命周期监听的hook
(比如@vue:updated
)或者存在动态的指令需要在runtime
确定的,这个时候标记为NEED_PATCH
,来看下这个类型的注释:Indicates an element that only needs non-props patching, e.g. ref or directives (onVnodeXXX hooks). since every patched vnode checks for refs and onVnodeXXX hooks, it simply marks the vnode so that a parent block will track it.
,也就是到时候patch
只关注non-props
比如事件什么的就行了,而不是跟踪所有的属性。
现在我们分门别类好了,但是还有些地方需要深入处理,比如:style="{ xx: xx }"
,这个动态style
的表达式也就是exp
里面的字段就有可能是一个动态的,如果有,那就说明得到runtime
去处理了。CLASS
需要创建一个函数调用表达式节点,辅助函数是NORMALIZE_CLASS
。同理STYLE
的需要NORMALIZE_STYLE
。如果你这个元素/组件节点有动态key
,不好意思,上面的判断style/class
的就不需要了,反正跑不掉,直接NORMALIZE_PROPS
乱棍打死。
最后返回
props
: propsExpression
,属性表达式节点,正常情况下的事件/属性都放到这里面directives
:runtimeDirectives
,自定义指令数组patchFlag
:不多说dynamicPropNames
:动态key
数组shouldUseBlock
:指的是你这个节点是否需要开启block
,比如有用到@vue:before-update
的监听子组件生命周期的情况。这货代码咱也不看了,这里说下发生了什么。由于这货比较特殊,所以它不是一个directiveTreansform
插件,在buildProps
处理完之后才会处理。
v-slot
这玩意儿只会用在component
里面。
上来直接把withCtx
这个辅助函数存到context
里。
如果这个v-slot
是在另一个v-slot
或者v-for
的scope
里面的话,并且只有在这个v-slot
中使用了scope variable
的情况下才会被定义为dynamic
,除此之外则是static
的(前提是prefixIdentifiers: true
)。
v-slot
仅能用于template
或者component
这两种标签上面:
component
上面,会创建一个对象属性节点,key
是arg
,没有就创建一个新的单一表达式,key
的content
是default
,而value
则是一个函数。如果这里的arg
也就是v-slot:xx="aa"
里的xx
是一个动态的,比如v-slot[xx]="aa"
,那么这个v-slot
就会被标记为dynamic
。template
上面的,那就需要判断几种场景:v-if
的场景,如果搭配了v-if
,那就肯定是dynamic
了,因为runtime
阶段可能切换slot
。会创建一个条件表表达式节点。如果是v-elsei(-if)
,操作类似transformIf
里的,找到IF
的节点,让后将当前节点放到IF
节点的branch
里,建立if
的联系。它也是创建一个条件表达式节点。v-for
的场景,它也是动态的。直接就是将for
的renderList
放到createSlot
里面。为什么会有上面两种兼容场景呢?因为我们在transformIf
和transformFor
的时候绕过了v-slot
的场景,所以这里自然就得处理这两种场景。
接着兼容处理,如果你这个component
里存在子节点,但是没有一处地方有v-slot
的指令,那也是可以的,实际上这也是大多数时候我们写的slot
代码,默认是创建一个defaultSlotProperty
节点。
然后就是插旗啦:
SlotFlags.DYNAMIC
,注释:The parent will need to force the child to update because the slot does not fully capture its dependencies.
为了安全考虑,还是把它也一起更新了。SlotFlags.FORWARD
,比如<template></template>
,把一个插槽传给了组件,这样就能盖中盖了。stable
的了这个插件看名字就知道是用来跟踪slot
的变量相关的。
代码也没啥好说的了,和trackVForSlotScopes
这个做的活儿一样,进入到当前节点就将scopes.vSlot++
并且将slot
的数据放到context.identifiers
上。然后返回回调,当回调被执行的时候就意味着所有子节点都已经处理完了,自然就可以退出当前vslot
的scope
了。
这个其实也是没啥好说的..
NodeTypes.INTERPOLATION || NodeTypes.TEXT
。VNode
,我们前面说了codegenNode
是在transformElement
插件中生成的,但是那里处理的时候只是处理元素节点和组件节点,其它的都不处理。所以这里并不冲突。辅助函数是createText
现在我们分析完了nodeTransforms
里面注册的插件(不包括compiler-dom
和compiler-sfc
传入的,感兴趣的可以去之前的文章里看下)
而directiveTransforms
里面的插件好像没看到地方调用?实际上有,在buildProps
中调用了,用来处理元素/组件节点的props
。但是buildProps
方法由于篇幅的问题我们并没有贴出来代码,这里把调用的那部分叠出来
现在就让我们进入到具体的指令处理插件中分析下代码(有的就不分析了,比如v-on
的,之前在compiler-dom
里面分析完了)
这个插件之前在compiler-dom
里面分析过了。
vue/compiler-dom源码分析学习--day3: 转换指令 - 知乎 (zhihu.com)
这里就不说了,来看下render function
exp
:老熟人了,等号右边的表达式modifiers
:也是老熟人,修饰符arg
:也是,等号左边指令右边的内容,比如v-on:click.once="handle"
,这个click
就是arg
,而这个handle
就是exp
,而这个once
自然就是修饰符了。这些都是在parse
阶段就处理好的。
先是对arg
做空值保护,然后处理camel
修饰符。
这个修饰符可以把你的属性名字驼峰化。如果这个arg
是一个动态的,比如v-bind:[test]="xxx"
,那么这个test
就是一个动态的。这个时候就只能借助辅助函数camelize
来处理了,否则可以直接在编译阶段就camlize
。
接下来处理3.2引入的v-bind
的两个修饰符.prop
和.attr
。
.prop
可以强制这个绑定的属性变成property
.attr
同上,变成attribute
那么问题来了,prop
和attribute
有啥不同呢?
可以看下这篇文章,这里就不说了。
injectPrefix
:看名字就知道是用来注入前缀的,实际上确实如此,所以代码就不看了,不过这里还需要处理动态的arg
。最后直接就创建一个对象属性节点并返回。
在分析之前,先来看下/复习下vue3.x
中v-model
的用法,做了一些breaking update
。
Built-in Directives | Vue.js (vuejs.org)
2.x -> 3.x
迁移文档:v-model | Vue 3 Migration Guide (vuejs.org)
另外里面涉及到的inline mode
的代码我这里也不会分析
处理modifier
的部分在compiler-dom
的包里,这里就不再说了,感兴趣的大佬可以去看下之前的文章。vue/compiler-dom源码分析学习--day3: 转换指令 - 知乎 (zhihu.com)
前面都是在处理错误场景,就不多说了。。
不过这里有一个需要注意的,那就是你这个v-model
的exp
不能是来自scope
的variable
,否则会报错:v-model cannot be used on v-for or v-slot scope variables because they are not writable.
。scope variable
不可写,只能读。而v-model
确实需要改写原数据,这样才算是双向绑定。
其实没啥好说的了,实际上v-model
算是一种语法糖写法,它在编译阶段会变成下面这样
其实没啥好说的了,实际上v-model
算是一种语法糖写法,它在编译阶段会变成下面这样
而在组件中则多了一个prop
,那就是modifier
,vue3.x
中允许通过defineProps
去自定义modifier
的行为。
最后将这个节点返回出去
那么现在对于transform
阶段 AST
节点的处理以及codegenNode
的生成我们已经分析完了,现在我们来到generate
阶段,分析每种类型对应的生成render function
的场景。
为了看着方便,我这里把代码再贴一遍。
如果节点自身就是一个字符串形式的,那直接就能渲染了,都不用runtime
的辅助函数。
如果这节点自身就只有一个辅助函数,那直接就放到helpers
集合里
这三种情况是需要递归的,因为可能存在子节点
文本节点,将节点内容字符串化
单节点表达式,如果是静态的内容,直接stringify
处理,如果不是就直接push
,拼接到runtime
的code
里。
pure
:这个和webpack
的tree-shake
相关的。PURE_ANNOTATION
:/*#__PURE__*/
, 声明这个方法可以是pure
纯净的,可以被webpack
的编译器 shake
掉。TO_DISPLAY_STRING
:toDisplayString
辅助函数。由于{{ xxx }}
的xxx
可能是一个复合表达式,所以还需要递归处理这个节点的content
。
这个a()
和b()
就是俩callExpression
这个我们刚分析完的transformText
里如果有两个相连的text
节点,就会组成一个新的节点,这就是TEXT_CALL
节点。这个新节点的codegenNode
是一个callExpression
,所以也需要递归处理。
这个就是我们经常念叨的复合类型,比如一个表达式里面有两个语句。
这个就不多说了,注释节点
这个节点就是最常见的codegenNode
节点类型,我们刚分析完的transformElement
给元素/组件节点生成的codegenNode
就是这个类型。
这里的这个directives
不是内置的,而是custom
的指令,需要withDirectives
辅助函数。
这个genNullableArgs
就不看代码了,就是过滤掉你这节点的null
数据。
getNodeList
:这个方法没啥好说的,就是将子元素递归genNode
处理
这个也没啥好说的,一个函数调用表达式,辅助函数就是callee
。
比如v-on="object"
会变成toHandlers(obj)
的形式
对象表达式节点,比如一个指令的exp
的codegenNode
就有可能是一个JS_OBJECT_EXPRESSION
,:style="{ xx: xxx }"
这个就不多说了,调用gentNodeListAsArray
条件表达式节点,比如v-if
的场景,用的就是这个
缓存表达式,这个有些陌生但实际上我们是遇到过的,不过是在另一个包compiler-dom
分析的时候遇到的。
这个就不看代码了,调用的是genNodeList
,一个block
的表达式,比如v-for
搭配v-memo
的场景。
剩下的ssr only
类型的这里就不看了
那么这个包中的插件也就都分析完了,这里没有把buildProps
和buildSlot
的代码贴出来,可能读起来有些没头没脑。。
总之,如果觉得这篇文章对你有帮助的话,点个赞也是可以哒~
发布于 2023-01-23 09:41・IP 属地广东