接上篇template
模版编译,上篇主要解析了template
模版编译的入口以及解析模版字符串的parseChildren
方法的内部实现。在生成ast
对象之后,继续调用transform
方法转化ast对象,主要是codegenNode
属性的赋值,后续生成render
方法主要依赖codegenNode
属性。
transform方法调用位置
上篇解析完ast
对象之后,回归到baseCompile
方法中,继续执行transform
方法
// baseCompile函数后续操作
const ast = isString(template) ? baseParse(template, options) : template
// ... nodeTransforms directiveTransforms 数组的组成
transform(
ast,
extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {} // user transforms
)
})
)
复制代码
调用transform
方法时传递两个参数,第一个参数ast
对象就是baseParse
进行模版解析的初步结果对象,第二个参数是options
对象(里面包含解析指令以及方法的函数,后续解析到具体调用时再详细分析)。接下来看下transform
方法内部实现。
function transform(root, options) {
const context = createTransformContext(root, options);
traverseNode(root, context);
// ...
}
复制代码
traverseNode循环解析ast对象
transform
方法内部首先是调用createTransformContext
方法生成一个context
对象。然后调用traverseNode
方法(这个方法内部就是使用options
参数中的方法进一步解析指令、方法)。
export 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.currentNo
}
}
// ...
}
复制代码
nodeTransforms处理指令的函数数组
首先是for
循环nodeTransforms
数组,依次调用数组中的每个方法,参数是node
对象(初始化是type
为0
的ast对象) 和 context
对象(调用createTransformContext
方法生成的)。接下来看下nodeTransforms
数组中的方法(大致看下每个函数是解析哪个指令或者方法的,然后以本例为模版详细解析调用的函数,后续再解析其它命中的函数)。
// 回归到baseCompile方法中 - nodeTransforms数组的由来
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(prefixIdentifiers)
// getBaseTransformPreset方法内部实现
export function getBaseTransformPreset(
prefixIdentifiers?: boolean
): TransformPreset {
return [
[
transformOnce,
transformIf,
transformFor,
...(!__BROWSER__ && prefixIdentifiers
? [
// order is important
trackVForSlotScopes,
transformExpression
]
: __BROWSER__ && __DEV__
? [transformExpression]
: []),
transformSlotOutlet,
transformElement,
trackSlotScopes,
transformText
],
// ... directiveTransforms对象
]
}
复制代码
transformOnce
解析v-once指令
transformIf
解析v-if指令
transformFor
解析v-for指令
trackVForSlotScopes
、transformSlotOutlet
、trackSlotScopes
方法 解析slot
插槽
transformExpression
处理表达式
transformElement
处理type
为1的元素对象
transformText
处理type
为0的root
根对象、type
为1的元素对象、type
为11的v-for
等
解析root根节点对象(type=0)
初始化ast
是type
为0
的根节点对象,循环调用nodeTransforms
数组中的方法,最终会命中transformText
方法,返回一个自定的函数() => {}
赋值给onExit
对象,push
到exitFns
数组中。接下来看下traverseNode
方法for
循环之后的内部实现
// traverseNode方法后续 switch-case解析ast的子节点对象
switch (node.type) {
case NodeTypes.COMMENT: // type = 3
if (!context.ssr) {
context.helper(CREATE_COMMENT)
}
break
case NodeTypes.INTERPOLATION: // type = 5
if (!context.ssr) {
context.helper(TO_DISPLAY_STRING)
}
break
case NodeTypes.IF: // type = 9 v-if
for (let i = 0; i < node.branches.length; i++) {
traverseNode(node.branches[i], context)
}
break
case NodeTypes.IF_BRANCH: // type = 0 | 1
case NodeTypes.FOR:
case NodeTypes.ELEMENT:
case NodeTypes.ROOT:
traverseChildren(node, context)
break
}
复制代码
解析动态数据节点(type=5)
当解析完type=0
的root
对象时,根据switch-case
的选项调用traverseChildren
方法解析children
数组中的子节点对象。
export function traverseChildren(
parent: ParentNode,
context: TransformContext
) {
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
context.onNodeRemoved = nodeRemoved
traverseNode(child, context)
}
}
复制代码
可以看到traverseChildren
函数中大致的实现是循环parent
的children
数组中的元素,再次调用traverseNode
方法进行解析,如果child
是字符串则跳过。
命中transformExpression函数
以本例为模版,根节点的第一个子对象是type
为5
的动态数据message
的结果对象,所以在nodeTransforms
数组中会调用transformExpression
方法
// transformExpression 方法
export const transformExpression: NodeTransform = (node, context) => {
if (node.type === NodeTypes.INTERPOLATION/* 5 */) {
node.content = processExpression(
node.content as SimpleExpressionNode,
context
)
} else if () {} //...
}
// processExpression 方法
export function processExpression(
node: SimpleExpressionNode,
context: TransformContext,
asParams = false,
asRawStatements = false
): ExpressionNode {
if (__BROWSER__) {
if (__DEV__) {
// simple in-browser validation (same logic in 2.x)
validateBrowserExpression(node, context, asParams, asRawStatements)
}
return node
}
// ...
}
复制代码
这个方法调用processExpression
方法处理表达式,内部实现大致是校验content
属性(就是message
)是否为空、校验下new Function()
在当前运行环境下执行会不会报错等等,最后返回传入的node.content
。在nodeTransforms
数组循环完之后进入switch-case
// traverseNode方法中type为5时执行的switch-case分支
context.helper(TO_DISPLAY_STRING)
// TO_DISPLAY_STRING 是一个 Symbol对象
export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ``)
// methods
helper(name) {
context.helpers.add(name)
return name
}
// helpers: new Set() helpers是一个set对象
复制代码
在context
的helpers
属性(new Set()
对象)中添加key
(Symbol('toDisplayString')
)、 value
(1
)值。switch-case
结束之后会调用while
循环,依次执行exitFns
数组中的函数(就是nodeTransforms
循环执行的结果存放在这个数组中)
// traverseNode 后续循环执行exitFns数组中的方法
context.currentNode = node
let i = exitFns.length
while (i--) {
exitFns[i]()
}
复制代码
type
为5
的对象循环时并没有返回数组,所以没有方法可以执行,本例后续会解析type
为2
的文本对象,因为type
为2
是空节点没有命中任何方法,这里直接跳过了。
解析元素节点对象(type=1)
最后执行type为1的元素节点对象
命中transformExpression函数
在nodeTransforms
数组循环中首先会命中transformExpression
中node.type === NodeTypes.ELEMENT
的分支判断
// transformExpression方法node.type=1的分支后续
else if (node.type === NodeTypes.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
if (dir.type === NodeTypes.DIRECTIVE/* 7 */ && dir.name !== 'for') {
const exp = dir.exp
const arg = dir.arg
if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION &&
!(dir.name === 'on' && arg)
) {/* ... */}
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {/* ... */}
}
}
}
复制代码
当node.type===1
时会循环props
数组,判断props
对象的type
属性是否为7并且不是v-for
指令,再判断exp
(属性值)的type
是否为4
并且属性名不是on
(本例中属性为on
事件监听),再判断arg
(属性名)的type
是否为4
并且不是静态的(本例中isStatic
值为true
),所以条件判断都不成立(若条件成立,后续还是执行processExpression
方法进行一些校验)。
命中transformElement函数
nodeTransforms
数组循环再次命中transformElement
方法
export const transformElement: NodeTransform = (node, context) => {
if (
!(
node.type === NodeTypes.ELEMENT/* 1 */ &&
(node.tagType === ElementTypes.ELEMENT/* 0 */ ||
node.tagType === ElementTypes.COMPONENT)
)
) {
return
}
return function postTransformElement() {}
}
复制代码
满足node.type===1
并且node.tagType === 0
,所以返回postTransformElement
方法。
命中transformText函数
因为node.type === 1
,执行transformText
方法,所以返回() => {}
自定义函数
export const transformText: NodeTransform = (node, context) => {
if (
node.type === NodeTypes.ROOT ||
node.type === NodeTypes.ELEMENT ||
node.type === NodeTypes.FOR ||
node.type === NodeTypes.IF_BRANCH
) {
return () => { /* ... */ }
}
}
复制代码
循环结束之后执行switch-case
命中case NodeTypes.ELEMENT
分支,执行traverseChildren
方法解析子节点(继续调用traverseNode
方法解析子节点),因为本例中button
元素的内部子节点是一个type
为2
的文本节点,所以没有命中nodeTransforms
中的方法(exitFns
数组为空),switch-case
也没有命中,因此while
循环也没有执行任何方法。type
为1
的children
数组解析完了,再次回到type=1
的元素对象解析过程中,开始while
循环执行exitFns
数组中的方法。
traverseNode中执行回调函数(type=1)
执行postTransformElement方法
第一个是postTransformElement
方法
function postTransformElement() {
const { tag, props } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
const vnodeTag = isComponent
? resolveComponentType(node as ComponentNode, context)
: `"${tag}"`
const isDynamicComponent =
isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT
let vnodeProps: VNodeCall['props']
let vnodeChildren: VNodeCall['children']
let vnodePatchFlag: VNodeCall['patchFlag']
let patchFlag: number = 0
let vnodeDynamicProps: VNodeCall['dynamicProps']
let dynamicPropNames: string[] | undefined
let vnodeDirectives: VNodeCall['directives']
// ... shouldUseBlock = false
// props
if (props.length > 0) {
const propsBuildResult = buildProps(node, context)
// 将返回的属性解析对象进行赋值
vnodeProps = propsBuildResult.props // 属性对象
patchFlag = propsBuildResult.patchFlag // 8
dynamicPropNames = propsBuildResult.dynamicPropNames // [name]
const directives = propsBuildResult.directives
vnodeDirectives =
directives && directives.length
? (createArrayExpression(
directives.map(dir => buildDirectiveArgs(dir, context))
) as DirectiveArguments)
: undefined // undefined
}
}
复制代码
该方法中判断是否是组件node.tagType === 1
(本例tagType
为0
);vnodeTag
赋值,本例中为"button"
;是否是动态组件isDynamicComponent
(本例因为vnodeTag
是字符串,所以是false
)
buildProps解析props属性
然后调用buildProps
方法解析props
属性
export function buildProps(
node: ElementNode,
context: TransformContext,
props: ElementNode['props'] = node.props,
ssr = false
): {
props: PropsExpression | undefined
directives: DirectiveNode[]
patchFlag: number
dynamicPropNames: string[]
} {
const { tag, loc: elementLoc } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = []
const runtimeDirectives: DirectiveNode[] = []
let patchFlag = 0
let hasRef = false
let hasClassBinding = false
let hasStyleBinding = false
let hasHydrationEventBinding = false
let hasDynamicKeys = false
let hasVnodeHook = false
const dynamicPropNames: string[] = []
const analyzePatchFlag = ({ key, value }: Property) => {}
for (let i = 0; i < props.length; i++) {
const prop = props[i]
if (prop.type === NodeTypes.ATTRIBUTE /* 6 */) {}
else {
// directives
const { name, arg, exp, loc } = prop
const isVBind = name === 'bind' // false
const isVOn = name === 'on' // true
// ...
const directiveTransform = context.directiveTransforms[name]
if (directiveTransform) {
// has built-in directive transform.
const { props, needRuntime } = directiveTransform(prop, node, context)
// ...
} else {
// ...
}
}
}
}
复制代码
在buildProps
方法中会循环props
中的每个属性,调用context.directiveTransforms[name]
对应的方法
命中transformOn函数
本例的属性名为on
,所以调用transformOn
方法(在getBaseTransformPreset
方法返回数组的第二个元素中定义)
// createSimpleExpression方法
export function createSimpleExpression(
content: SimpleExpressionNode['content'],
isStatic: SimpleExpressionNode['isStatic'] = false,
loc: SourceLocation = locStub,
constType: ConstantTypes = ConstantTypes.NOT_CONSTANT
): SimpleExpressionNode {
return {
type: NodeTypes.SIMPLE_EXPRESSION,
loc,
content,
isStatic,
constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType
}
}
// transformOn方法
export const transformOn: DirectiveTransform = (
dir,
node,
context,
augmentor
) => {
const { loc, modifiers, arg } = dir as VOnDirectiveNode
if (!dir.exp && !modifiers.length) {}
let eventName: ExpressionNode
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
if (arg.isStatic) {
const rawName = arg.content
eventName = createSimpleExpression(
toHandlerKey(camelize(rawName)),
true,
arg.loc
)
}
else {}
} else {}
// ...
let shouldCache: boolean = context.cacheHandlers && !exp
if (exp) {
// ...
}
let ret: DirectiveTransformResult = {
props: [
createObjectProperty(
eventName,
exp || createSimpleExpression(`() => {}`, false, loc)
)
]
}
// ...
return ret
}
复制代码
transformOn
方法的内部实现,首先属性名(arg
对象)的type
为4
(简单表达式)并且isStatic
为true
,则调用createSimpleExpression
方法创建简单表达式对象{ type: 4, content: onClick, constType: 3, ... }
。这里对arg
的name
(事件名称)做了处理(首先是中划线转驼峰,再是因为是on
事件监听,所以在name
开头加上on
并且name
的首字母大写,本例中name
由click
-> onClick
)。最后调用createObjectProperty
方法。
export function createObjectProperty(
key: Property['key'] | string,
value: Property['value']
): Property {
return {
type: NodeTypes.JS_PROPERTY, // type=16
loc: locStub,
key: isString(key) ? createSimpleExpression(key, true) : key,
value
}
}
复制代码
createObjectProperty
方法最终返回一个type
为16
的对象,其中key
为type=4
的arg
对象,value
为type=4
的exp
对象,所以transformOn
方法返回{ props: [{type: 16, key, value, ...}] }
。回归到buildProps
方法中,调用完directiveTransform
(就是transformOn
函数)方法的后续实现
// buildProps函数中调用完directiveTransform对应的方法之后的内部实现
const { props, needRuntime/* undefined */ } = directiveTransform(prop, node, context)
!ssr && props.forEach(analyzePatchFlag)
properties.push(...props)
if (needRuntime) {}
复制代码
props
数组中的每个元素依次调用analyzePatchFlag
方法,然后将props
的每个元素push
到properties
数组中。看下analyzePatchFlag
函数的内部实现
// buildProps方法内部定义的analyzePatchFlag函数
const analyzePatchFlag = ({ key, value }: Property) => {
// isStaticExp判断key的type === 4 并且 key的isStatic === true
if (isStaticExp(key)) {
const name = key.content
const isEventHandler = isOn(name) // 是否是on开头的事件监听
// ...
if (name === 'ref') {
hasRef = true
} else if (name === 'class') {
hasClassBinding = true
} else if (name === 'style') {
hasStyleBinding = true
} else if (name !== 'key' && !dynamicPropNames.includes(name)) {
dynamicPropNames.push(name)
}
// ...
}
else {}
}
复制代码
以本例分析,analyzePatchFlag
方法的作用是将key.content
放到dynamicPropNames
数组中(收集属性名称)。后续回归到buildProps
方法中
// buildProps方法后续
if (mergeArgs.length) {/* ... */}
else if (properties.length) {
propsExpression = createObjectExpression(
dedupeProperties(properties), // 属性对象组成的数组[prop]
elementLoc
)
}
// createObjectExpression方法
export function createObjectExpression(
properties: ObjectExpression['properties'],
loc: SourceLocation = locStub
): ObjectExpression {
return {
type: NodeTypes.JS_OBJECT_EXPRESSION, // type = 15
loc,
properties
}
}
复制代码
后续调用createObjectExpression
方法返回一个type
为15
的对象,properties
属性为prop
构成的数组。
// buildProps方法最后部分
if (hasDynamicKeys) {}
else {
if (dynamicPropNames.length) {
patchFlag |= PatchFlags.PROPS // 0|8 = 8
}
}
// ...
return {
props: propsExpression,
directives: runtimeDirectives,
patchFlag,
dynamicPropNames
}
复制代码
最终buildProps
方法返回了一个对象,表示属性的进一步解析结果{ props: {...}, patchFlag: 8, ... }
,之后回归到transformElement
方法中,解析完属性之后开始处理children
数组
// transformElement方法 处理 children 后续
if (node.children.length > 0) {
// ...
const shouldBuildAsSlots = isComponent && vnodeTag !== TELEPORT && vnodeTag !== KEEP_ALIVE
if (shouldBuildAsSlots) {}
else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
const child = node.children[0]
const type = child.type
const hasDynamicTextChild /* false */ =
type === NodeTypes.INTERPOLATION ||
type === NodeTypes.COMPOUND_EXPRESSION
// ...
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
vnodeChildren = child as TemplateTextChildNode
} else {
vnodeChildren = node.children
}
} else {}
}
if (patchFlag !== 0) {
if (__DEV__) {
if (patchFlag < 0) {/* patchFlag = 8 */}
else {
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n])
.join(`, `)
// PatchFlagNames对象中 { 8: 'PROPS' }
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
}
} else {
vnodePatchFlag = String(patchFlag)
}
if (dynamicPropNames && dynamicPropNames.length) {
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
// 在name首尾添加[] -> "["onClick"]"
}
}
node.codegenNode = createVNodeCall(
context,
vnodeTag,
vnodeProps,
vnodeChildren,
vnodePatchFlag,
vnodeDynamicProps,
vnodeDirectives,
!!shouldUseBlock,
false /* disableTracking */,
node.loc
)
复制代码
当children
长度为1
时(本例只有一个子节点),首先是给vnodeChildren
赋值child=node.children[0]
,vnodePatchFlag="8 /* PROPS */"
,vnodeDynamicProps=["onClick"]
,最后调用createVNodeCall
方法创建codegenNode
属性对象
export function createVNodeCall(
context: TransformContext | null,
tag: VNodeCall['tag'],
props?: VNodeCall['props'],
children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag'],
dynamicProps?: VNodeCall['dynamicProps'],
directives?: VNodeCall['directives'],
isBlock: VNodeCall['isBlock'] = false,
disableTracking: VNodeCall['disableTracking'] = false,
loc = locStub
): VNodeCall {
if (context) {
if (isBlock) {
context.helper(OPEN_BLOCK)
context.helper(CREATE_BLOCK)
} else {
context.helper(CREATE_VNODE)
}
if (directives) {
context.helper(WITH_DIRECTIVES)
}
}
return {
type: NodeTypes.VNODE_CALL, // type = 13
tag,
props,
children,
patchFlag,
dynamicProps,
directives,
isBlock,
disableTracking,
loc
}
}
// CREATE_VNODE定义
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
复制代码
createVNodeCall
方法内部判断isBlock
是否为true
,本例为false
,所以在contetx
的helpers
对象中添加Symbol('createVNode')
,最终返回type
为13
的对象
执行transformText函数中返回的自定义方法
执行完postTransformElement
函数之后,继续执行自定义() => {}
方法,看下这个方法的内部实现
() => {
const children = node.children
let currentContainer: CompoundExpressionNode | undefined = undefined
let hasText = false
for (let i = 0; i < children.length; i++) {
const child = children[i]
// isText: node.type === 5 || 2
if (isText(child)) {
hasText = true
for (let j = i + 1; j < children.length; j++) {
const next = children[j]
if (isText(next)) {
if (!currentContainer) {
currentContainer = children[i] = {
type: NodeTypes.COMPOUND_EXPRESSION,
loc: child.loc,
children: [child]
}
}
// merge adjacent text node into current
currentContainer.children.push(` + `, next)
children.splice(j, 1)
j--
} else {
currentContainer = undefined
break
}
}
}
}
}
复制代码
首选是for
循环,循环node
的children
数组,判断child
是否为文本节点(node.type
等于5
或者2
),然后再判断child
的下一个元素是否也是文本节点,本来中type
为1
的children
长度是1(只有一个子节点),所以跳出for
循环,执行后续代码
() => {
// 自定义函数for循环之后的 内部实现
if (!hasText ||
(children.length === 1 && (node.type === NodeTypes.ROOT || (node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT)))
) {
return
}
}
复制代码
以本例为模版来说,type
为1
的元素满足children
数组长度是1并且node.tagType === 0
,所以return
返回了。至此,type
为1
节点的exitFns
数组中的回调函数已经全部执行完成了,主要是生成codegenNode
属性值(type
为13
的对象),其中对props
属性和children
子节点都做了进一步的分析。
traverseNode中执行回调函数(type=0)
最后执行type
为0
的根节点的回调函数,依然是上面的自定义函数() => {...}
。首先是循环children
数组(type
为0
的根节点的children
数组是type
为5,2,1
三个对象)。for
循环的内部实现大致是,如果child
和child
的下一个元素都是文本节点,则将child
节点替换为type
为8
的新对象,新对象的children
属性为[child, '+', next(child的下一个元素)]
(包含三个元素的数组),所以现在type
为0
的对象的children
属性的值变成了[{type:8, children:[{type:5,...}, '+', {type:2}], ...}, {type:1, ...}]
的新对象(原先type
为5
和2
的两个相邻文本对象替换为type
为8
的对象)。最后执行自定义函数的结尾部分。
// createCallExpression方法定义
export function createCallExpression<T extends CallExpression['callee']>(
callee: T,
args: CallExpression['arguments'] = [],
loc: SourceLocation = locStub
): InferCodegenNodeType<T> {
return {
type: NodeTypes.JS_CALL_EXPRESSION, // type = 14
loc,
callee,
arguments: args
} as any
}
// type为0的根节点对象 执行自定义函数的后续部分实现
() => {
// ...
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
const callArgs: CallExpression['arguments'] = []
// 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 !== NodeTypes.TEXT || child.content !== ' ') {
callArgs.push(child)
}
// mark dynamic text with flag so it gets patched inside a block
if (
!context.ssr &&
getConstantType(child, context) === ConstantTypes.NOT_CONSTANT
) {
callArgs.push(
PatchFlags.TEXT +
(__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``)
)
}
children[i] = {
type: NodeTypes.TEXT_CALL, // type = 12
content: child,
loc: child.loc,
codegenNode: createCallExpression(
context.helper(CREATE_TEXT),
// export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
callArgs
)
}
}
}
}
复制代码
最后的部分仍然是循环children
数组(本例为type
是8
和1
两个对象),进一步转化type
为8
的对象。以本例分析是将child
对象和"1 /* TEXT */"
字符串存放在callArgs
数组中,然后child
重新赋值为type=12
的新对象,新对象的codegenNode
属性赋值为createCallExpression
方法的返回值{ type: 14, callee: Symbol('createTextVNode'), arguments: callArgs数组, ... }
。那么type
为0
的回调函数也执行完成了(主要是对文本节点对象做了一个合并)。回归到transform
方法中,当traverseNode
函数执行完成之后,看下后续的内部实现。
if (options.hoistStatic/* true */) {
hoistStatic(root, context)
}
if (!options.ssr) {
createRootCodegen(root, context)
}
// 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
// hoistStatic方法定义
export function hoistStatic(root: RootNode, context: TransformContext) {
walk(
root,
context,
// Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes.
isSingleElementRoot(root, root.children[0]) // false(children.length !== 1)
)
}
复制代码
首先会调用walk
方法(依本例解析,walk
方法中的条件判断不会命中,这里不详述了)。
createRootCodegen创建根codegenNode属性
之后调用createRootCodegen
方法
function createRootCodegen(root: RootNode, context: TransformContext) {
const { helper } = context
const { children } = root
if (children.length === 1) {
} else if (children.length > 1) {
let patchFlag = PatchFlags.STABLE_FRAGMENT // 64
let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT] // 'STABLE_FRAGMENT'
root.codegenNode = createVNodeCall(
context,
helper(FRAGMENT), // export const FRAGMENT = Symbol(__DEV__ ? `Fragment` : ``)
undefined,
root.children,
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
undefined,
undefined,
true // isBlock
)
}
}
复制代码
此方法内部调用createVNodeCall
函数,这个函数我们上文提到过,最终返回一个type
为13
的对象,因为isBlock
为true
,所以在context
对象的helpers
(Set
对象)中新增Symbol('openBlock')
和Symbol('createBlock')
,patchFlag
为64
,isBlock
为true
,children
为root.children
(type
为0
的children
数组),tag
是Symbol('Fragment')
。最后将context
上的一些对象赋值到了root
对象上。
总结
transform
方法将baseParse
函数解析的ast
对象作为源,进一步转化。主要是通过traverseNode
函数(当遇到根对象或者元素对象的时候会循环调用,解析子节点对象),这个方法内部首先是依次观察指令函数是否命中,收集回调函数进行循环执行,执行过程中会对元素节点的属性进行解析(以本例为模版,@click
会命中transformOn
处理事件监听对象,存储方法和事件回调函数);会对子节点进行解析(将两个相邻的文本节点进行合并,生成新的对象)等等。在根节点上生成codegenNode
属性,属性中囊括了children
子节点,属性props
、patchFlag
处理标志位等等。最后将context
上的部分属性转移到root
根对象上,为generate
方法铺垫(例如helpers
对象,其中存放的就是Vue
暴露的创建节点的方法:创建动态数据的toDisplayString
函数、创建节点的createVNode
函数、创建文本节点的createTextVNode
函数)。后文将解析generate
函数,是如何利用codegenNode
属性组装render函数字符串的。