This is followed by the template template compilation, which mainly parsed the entry to template compilation and the internal implementation of the parseChildren method that parses template strings. After the AST object is generated, the transform method is called to transform the AST object, mainly by assigning the codegenNode attribute. The subsequent render method mainly relies on the codegenNode attribute.
Transform method call location
After parsing the AST object in the previous part, return to the baseCompile method and continue to execute the transform method
// The baseCompile function
const ast = isString(template) ? baseParse(template, options) : template
/ /... NodeTransforms directiveTransforms The composition of the array
transform(
ast,
extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms].directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {} // user transforms)}))Copy the code
The transform method is called with two parameters. The first parameter ast object is the initial result of baseParse’s template parsing, and the second parameter is the options object (which contains the parsing instructions and method functions, which will be analyzed in detail when parsed to the specific call). Next, take a look inside the transform method implementation.
function transform(root, options) {
const context = createTransformContext(root, options);
traverseNode(root, context);
// ...
}
Copy the code
TraverseNode loops through ast objects
Inside the transform method, we first call the createTransformContext method to generate a context object. Then call the traverseNode method, which internally parses the instructions and methods using the methods in the options argument.
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
}
}
// ...
}
Copy the code
NodeTransforms An array of functions that process the directive
The first is the for loop nodeTransforms array, which calls each method in turn, taking the Node object (initialized as an AST object of Type 0) and the Context object (generated by calling the createTransformContext method). Next take a look at the methods in the nodeTransforms array (take a quick look at which instruction or method each function parses, then use this example as a template to parse the called function in detail, and then any other hit functions).
// Revert to the baseCompile method
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(prefixIdentifiers)
// getBaseTransformPreset is an internal implementation of the method
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 object]}Copy the code
TransformOnce parses the V-once instruction transformIf parses the V-IF instruction transformFor parses the V-for instruction The trackVForSlotScopes, transformSlotOutlet, and trackSlotScopes methods parse slot slot transformExpression processes the expression transformElement TransformText Processes elements of type 1, root root of type 0, element of type 1, v-for of type 11, and so on
Parse root object (type=0)
The ast is initialized as a root object of type 0, looped through the methods in the nodeTransforms array, which eventually hits the transformText method, returning a custom function () => {} that assigns a value to the onExit object, pushed into the exitFns array. Now look at the internal implementation of the traverseNode method after the for loop
// traverseNode methods then switch-case to parse the ast child objects
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
}
Copy the code
Parse dynamic data node (type=5)
When the root object of type=0 is resolved, the traverseChildren method is called with the switch-case option to resolve the child node objects in the Children array.
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)
}
}
Copy the code
As you can see, the rough implementation of the traverseChildren function is to loop through the elements of parent’s children array and then call the traverseNode method again to parse, skipping if the child is a string.
Hit the transformExpression function
Using this example as a template, the root node’s first child object is the result of a dynamic data message of type 5, so the transformExpression method is called in the nodeTransforms array
/ / transformExpression method
export const transformExpression: NodeTransform = (node, context) = > {
if (node.type === NodeTypes.INTERPOLATION/ * * / 5) {
node.content = processExpression(
node.content as SimpleExpressionNode,
context
)
} else if () {} / /...
}
/ / processExpression method
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
}
// ...
}
Copy the code
This method calls the processExpression method to handle expressions, whose internal implementation is basically to check whether the Content property (i.e., message) is empty, whether new Function() is an error when executed under the current operating environment, and so on, before returning the passed Node.content. Enter the switch-case after the nodeTransforms array loop
// switch-case branch when type is 5 in the traverseNode method
context.helper(TO_DISPLAY_STRING)
// TO_DISPLAY_STRING is a Symbol object
export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ` `)
// methods
helper(name) {
context.helpers.add(name)
return name
}
Helpers: new Set() helpers is a Set object
Copy the code
Add key(Symbol(‘toDisplayString’) and value(1) values to the helpers property of context (New Set() object). This completes with a while loop that executes the functions in the exitFns array (which is the array that stored the results of the nodeTransforms loop)
// traverseNode then loops through the methods in the exitFns array
context.currentNode = node
let i = exitFns.length
while (i--) {
exitFns[i]()
}
Copy the code
An object of type 5 does not return an array, so there is no method to execute. This example will then parse a text object of type 2, because type 2 is empty and no method has been hit.
Parse element node object (type=1)
Finally, the element node object of type 1 is executed
Hit the transformExpression function
The branch judgment for Node.type == NodeTypes.ELEMENT in the transformExpression is first hit in the Node.type == NodeTypes.ELEMENT ELEMENT in the nodeTransforms array loop
// transformExpression branch of the method 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) {/ *... * /}}}}Copy the code
When node.type===1, loop through the props array to check whether the type of the props object is 7 and not v-for, and exp is type 4 and not on(in this case, on is the event name). Then check whether the type of arG (attribute name) is 4 and not static (isStatic value is true in this case), so all the condition judgments are not valid (if the conditions are valid, the processExpression method is still used for some verification later).
Hit the transformElement function
The nodeTransforms array loop again hits the transformElement method
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() {}}Copy the code
Node. type===1 and Node. tagType === 0, so return the postTransformElement method.
Hit the transformText function
Since node.type === 1 executes the transformText method, return () => {} custom function
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 () = > { / *... * /}}}Copy the code
At the end of the loop, execute switch-case to hit the Case NodeTypes.ELEMENT branch, execute the traverseChildren method to parse the child nodes (continue calling the traverseNode method to parse the child nodes), Because the inner child of the Button element in this case was a text node of type 2, the method in the nodeTransforms (the exitFns array was empty) was not hit, nor was the switching-case hit, and therefore the while loop did not execute any methods. Once the children array of type 1 is parsed, we return to the element object of type=1 and start the while loop to execute the methods in the exitFns array.
TraverseNode callback (type=1)
Execute the postTransformElement method
The first is the postTransformElement method
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)
// Assign the returned attribute resolution object
vnodeProps = propsBuildResult.props // Attribute object
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}}Copy the code
Node. tagType === 1(in this example, tagType is 0); VnodeTag, in this case “button”; IsDynamicComponent (false in this case because vnodeTag is a string)
BuildProps resolves the props property
The buildProps method is then called to resolve the props property
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 {
// ...}}}}Copy the code
In buildProps approach will cycle each attribute of props, call context. DirectiveTransforms [name] corresponding method
Hit the transformOn function
The property in this case is called on, so call the transformOn method (defined in the second element of the array returned by the getBaseTransformPreset method)
/ / createSimpleExpression method
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 method
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 {}
// ...
letshouldCache: boolean = context.cacheHandlers && ! expif (exp) {
// ...
}
let ret: DirectiveTransformResult = {
props: [
createObjectProperty(
eventName,
exp || createSimpleExpression(` ` () = > {}.false, loc)
)
]
}
// ...
return ret
}
Copy the code
An internal implementation of the transformOn method. If the property name (arG object) is of type 4 and isStatic is true, createSimpleExpression is called to create a simple expression object {type: 4, Content: onClick, constType: 3, … }. The name of the arG (event name) is handled (first dash to hump, and because it is listening on the on event, the name starts with on and the first letter of name is capitalized (in this case, name is click -> onClick). Finally, the createObjectProperty method is called.
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
}
}
Copy the code
The createObjectProperty method finally returns an object of type 16, an arg object with key type=4 and an exp object with value type=4, so the transformOn method returns {props: [{type: 16, key, value…}]}. Go back to the buildProps method and call the subsequent implementation of the directiveTransform(which is the transformOn function) method
// The internal implementation of the directiveTransform method after calling the buildProps function
const { props, needRuntime/* undefined */} = directiveTransform(prop, node, context) ! ssr && props.forEach(analyzePatchFlag) properties.push(... props)if (needRuntime) {}
Copy the code
Each element in the props array calls the analyzePatchFlag method in turn, and then pushes each element of the props into the Properties array. Take a look at the internal implementation of the analyzePatchFlag function
// analyzePatchFlag function defined inside the buildProps method
const analyzePatchFlag = ({ key, value }: Property) = > {
// isStaticExp Determines the key type === 4 and the key isStatic === true
if (isStaticExp(key)) {
const name = key.content
const isEventHandler = isOn(name) // Whether the event listener starts with 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{}}Copy the code
In this example, the analyzePatchFlag method puts key.content into the dynamicPropNames array (to collect attribute names). We then revert to the buildProps method
// The buildProps method follows
if (mergeArgs.length) {/ *... * /}
else if (properties.length) {
propsExpression = createObjectExpression(
dedupeProperties(properties), // Array of property objects [prop]
elementLoc
)
}
/ / createObjectExpression method
export function createObjectExpression(
properties: ObjectExpression['properties'],
loc: SourceLocation = locStub
) :ObjectExpression {
return {
type: NodeTypes.JS_OBJECT_EXPRESSION, // type = 15
loc,
properties
}
}
Copy the code
A subsequent call to the createObjectExpression method returns an object of type 15 with properties as an array of prop.
// buildProps the last part of the method
if (hasDynamicKeys) {}
else {
if (dynamicPropNames.length) {
patchFlag |= PatchFlags.PROPS / / | 0 8 = 8}}// ...
return {
props: propsExpression,
directives: runtimeDirectives,
patchFlag,
dynamicPropNames
}
Copy the code
Finally, the buildProps method returns an object representing further parsing of the property {props: {… }, patchFlag: 8, … }, then go back to the transformElement method, and after parsing the properties, start processing the Children array
// The transformElement method handles children subsequent
if (node.children.length > 0) {
// ...
constshouldBuildAsSlots = isComponent && vnodeTag ! == TELEPORT && vnodeTag ! == KEEP_ALIVEif (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 object {8: 'PROPS'}
vnodePatchFlag = patchFlag + ` / *${flagNames}* / `}}else {
vnodePatchFlag = String(patchFlag)
}
if (dynamicPropNames && dynamicPropNames.length) {
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
// Add [] -> "["onClick"]"} } node.codegenNode = createVNodeCall( context, vnodeTag, vnodeProps, vnodeChildren, vnodePatchFlag, vnodeDynamicProps, vnodeDirectives, !! shouldUseBlock,false /* disableTracking */,
node.loc
)
Copy the code
Child =node.children[0], vnodePatchFlag=”8 /* PROPS */”, VnodeDynamicProps =[“onClick”], and then call the createVNodeCall method to create the codegenNode property object
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 definition
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ` `)
Copy the code
The createVNodeCall method internally determines whether isBlock is true, which in this case is false, so add Symbol(‘createVNode’) to the helpers object in Contetx and return an object of type 13
Executes the custom method returned in the transformText function
After executing the postTransformElement function, proceed with the custom () => {} method and take a look at the internal implementation of this method
() = > {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
}
}
}
}
}
Copy the code
Type = 5 or 2), and then determine whether the child’s next element is also a text node. Children of type 1 is 1 in length (only one child node). So break out of the for loop and execute the following code
() = > {// Internal implementation of a custom function after the for loop
if(! hasText || (children.length ===1 && (node.type === NodeTypes.ROOT || (node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT)))
) {
return}}Copy the code
Using this example as a template, the element of type 1 satisfies the children array of length 1 and Node. tagType === 0, so return. At this point, the callbacks in the exitFns array of type 1 have all been executed, generating the codegenNode property value (an object of type 13) with further analysis of both the props property and the children node.
TraverseNode callback (type=0)
() => {… () => {… () => {… }. The first is the cyclic children array (the children array of the root node of type 0 is objects of type 5,2,1). The internal implementation of the for loop is roughly to replace the child node with a new object of type 8 if both the child and its next element are text nodes, The children attribute of the new object is [child, ‘+’, next(the next element of child)] (an array of three elements), So now the object of type 0 children attribute value into [{type: 8, children: [{type: 5,…}, ‘+’ {2} type:],…}, {type: 1, …}] (two adjacent text objects of type 5 and 2 are replaced with objects of type 8). Finally, the end of the custom function is executed.
// createCallExpression method definition
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
}
// The root object of type 0 performs the subsequent partial implementation of the custom function() = > {// ...
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
)
}
}
}
}
Copy the code
The final part again loops through the Children array (in this case, objects of type 8 and 1), further converting objects of type 8. CallArgs =1; callArgs =1; callArgs =1; callArgs =1; callArgs =1; The codegenNode attribute of the new object is assigned to the createCallExpression method return value {type: 14, Callee: Symbol(‘createTextVNode’), arguments: callArgs array,… }. So the callback of type 0 is done (basically a merge of the text node object). Return to the transform method and, once the traverseNode function has been executed, look at the subsequent internal implementation.
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 method definition
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))}Copy the code
The walk method is called first.
CreateRootCodegen creates the root codegenNode attribute
The createRootCodegen method is then called
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)}}Copy the code
This method internally calls the createVNodeCall function, which, as mentioned above, ultimately returns an object of type 13 because isBlock is true, Add Symbol(‘openBlock’) and Symbol(‘createBlock’) to the helpers(Set object) of context object. PatchFlag = 64, isBlock = true Children is root.children(an array of children of type 0), and tag is Symbol(‘Fragment’). Finally, assign some objects from the context to the root object.
conclusion
The Transform method takes the AST object parsed by the baseParse function as the source for further transformation. TraverseNode functions (which are cycled when the root object or element object is traversed and the child node object is parsed). Internally, this method first observes whether the instruction function is matched and then collects the callback function for cyclic execution, during which the attributes of the element node are parsed (using this example as a template). @click hits transformOn handling event listeners, storing methods and event callbacks); Child nodes are parsed (merging two adjacent text nodes to produce a new object), and so on. Generate the codegenNode property on the root node, which contains the children node, properties props, patchFlag processing flag bits, and so on. Finally, transfer some properties of the context to the root object, paving the way for the generate method (such as helpers object, which holds the node creation method exposed by Vue) : ToDisplayString creates dynamic data, createVNode creates a node, and createTextVNode creates a text node. I’ll explain how the generate function uses the codegenNode property to assemble the Render function string.