preface
The first two articles focused on parsing and optimizing the VUE compiler:
- Parse the COMPONENT’s HTML template into an AST object
- Static marking is performed based on AST syntax tree. First, whether each node is a static node is marked, and then the static root node is further marked so that the update of the static root node can be skipped in subsequent updates to improve performance
Let’s take a look at how the VUE compiler generates run rendering functions from the AST syntax tree.
Deep source
CreateCompiler () method – entry
File Location:/src/compiler/index.js
Chief among them is the generate(AST, options) method, which is responsible for generating render functions from the AST syntax tree.
/* All the work up to this point was just to build platform-specific compilation options, For example, web platform 1, parse HTML template into AST 2, statically mark ast tree 3, generate ast render function - static render function into code.staticrenderfns array - dynamic render function code.render - Vnode */ can be obtained by executing the render function during future rendering
export const createCompiler = createCompilerCreator(function baseCompile(template: string, options: CompilerOptions) :CompiledResult {
The AST object of each node sets all the information about the element, such as tag information, attribute information, slot information, parent node, child node, etc. */
const ast = parse(template.trim(), options)
/* Optimize, iterate over AST, statically mark each node - mark each node as a static node and ensure that these static nodes are skipped in subsequent updates - mark static root nodes for the render function generation stage, optimize the render function that generates static root nodes, iterate over AST, statically mark each node */
if(options.optimize ! = =false) {
optimize(ast, options)
}
/* Generate render functions from the AST syntax tree like: code.render = "_c('div',{attrs:{"id":"app"}},_l((arr),function(item){return _c('div',{key:item},[_v(_s(item))])}),0)" */
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
Copy the code
The generate () method
File Location:src\compiler\codegen\index.js
When code is assigned, the main content is generated by the genElement(ast, state) method.
/* Generate render functions from AST: -render as string code -staticrenderfns as multiple string code of the form 'with(this){return XXX}' */
export function generate (
ast: ASTElement | void./ / ast objects
options: CompilerOptions // Compile options
) :CodegenResult {
/* Instantiate the CodegenState object, taking the compile option to get state, which has most of the same properties as options */
const state = new CodegenState(options)
/* Generates string format code such as: '_c(tag, data, children, normalizationType)' - data forms JSON strings for attributes on nodes such as '{key: xx, ref: xx,... }' -children is an array of string codes for all child nodes in the format of '['_c(tag, data, children)',... NormalizationType 'and -normalization is the fourth parameter of _C, which means the normalization type of the node. Note: Code does not have to be _c, but it could be something else, such as _m(0) */ if the entire component is static
const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
return {
render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
}
}
Copy the code
GenElement () method
File Location:src\compiler\codegen\index.js
export function genElement (el: ASTElement, state: CodegenState) :string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if(el.staticRoot && ! el.staticProcessed) {1. Place the current static node's render function into the array staticRenderFns. 2. Return an executable function _m(idx, true or ") */
return genStatic(el, state)
} else if(el.once && ! el.onceProcessed) {/* If (condition, condition, condition); /* If (condition, condition, condition, condition); render1 : Render2 '2 is a static node contained within the V-for directive, resulting in _O (_c(tag, data, children), number, key)'. Get '_m(idx, true of' ") */
return genOnce(el, state)
} else if(el.for && ! el.forProcessed) {_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)}) '*/
return genFor(el, state)
} else if(el.if && ! el.ifProcessed) {/* Process a node with a V-if instruction, resulting in a ternary expression: 'condition? render1 : render2` */
return genIf(el, state)
} else if (el.tag === 'template'&&! el.slotTarget && ! state.pre) {/* When the current node is a template tag and not a slot and node with v-pre instructions, go here to generate the render function for all the child nodes, and return an array of the format: `[_c(tag, data, children, normalizationType), ...] ` * /
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
/* Generate the slot rendering function, and get: '_t(slotName, children, attrs, bind)' */
return genSlot(el, state)
} else {
/* Component or element handles both dynamic components and common elements (custom components, native tags, platform-reserved tags, such as every HTML tag in a Web platform) */
let code
if (el.component) {
_c(compName, data, children) */
code = genComponent(el.component, el, state)
} else {
// Handle common elements (custom components, native tags)
let data
if(! el.plain || (el.pre && state.maybeComponent(el))) {/* Non-ordinary elements or components with v-pre instructions go here, process all attributes of the node, and return a JSON string, such as: '{key: xx, ref: xx,... } '* /
data = genData(el, state)
}
['_c(tag, data, children)', ['_c(tag, data, children)',... NormalizationType ', where the normalization type of the normalization node is omitted */
const children = el.inlineTemplate ? null : genChildren(el, state, true)
/* Get the final string format of the code: _c(tag, data, children, normalizationType) */
code = `_c('${el.tag}'${
data ? `,${data}` : ' ' // data
}${
children ? `,${children}` : ' ' // children
}) `
}
/* If the transformCode method is provided, the final code is processed by this method in each module. The framework does not provide this method, but even if it does, The final format is also _C (tag, data, children) module transforms */
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
/ / return code
return code
}
}
Copy the code
GenChildren () method
File Location:src\compiler\codegen\index.js
/* Generate a render function for all child nodes and return an array of the format '[_c(tag, data, children, normalizationType),... ` * /
export function genChildren (el: ASTElement, state: CodegenState, checkSkip? : boolean, altGenElement? :Function, altGenNode? :Function
) :string | void {
// Get all child nodes
const children = el.children
if (children.length) {
// First child node
const el: any = children[0]
// optimize single v-for
if (children.length === 1&& el.for && el.tag ! = ='template'&& el.tag ! = ='slot'
) {
/* Optimization: - Condition: only one child && has a V-for instruction on the child && tags are not template or slot - mode: Call genElement directly to generate the render function for that node, without going through the loop and calling genCode to get the render function */
const normalizationType = checkSkip
? state.maybeComponent(el) ? ` `, 1 : ` `, 0
: ` `
return `${(altGenElement || genElement)(el, state)}${normalizationType}`
}
// Get node normalized type, return number: 0, 1, 2
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0
// is a function that generates code
const gen = altGenNode || genNode
/* Return an array in which each element is a child node render function format: ['_c(tag, data, children, normalizationType)',...] * /
return ` [${children.map(c => gen(c, state)).join(', ')}]${
normalizationType ? `,${normalizationType}` : ' '
}`}}Copy the code
GenNode () method
File Location:src\compiler\codegen\index.js
function genNode (node: ASTNode, state: CodegenState) :string {
// Handle normal element nodes
if (node.type === 1) {
return genElement(node, state)
} else if (node.type === 3 && node.isComment) {
// Process text comment nodes
return genComment(node)
} else {
// Process text nodes
return genText(node)
}
}
Copy the code
GenComment () method
File Location:src\compiler\codegen\index.js
// Returns the value in the format '_e(XXXX)'
export function genComment (comment: ASTText) :string {
return `_e(The ${JSON.stringify(comment.text)}) `
}
Copy the code
GenText () method
File Location:src\compiler\codegen\index.js
// Get the return value in the format of '_v(XXXXX)'
export function genText (text: ASTText | ASTExpression) :string {
return `_v(${text.type === 2
? text.expression // no need for () because already wrapped in _s()
: transformSpecialNewlines(JSON.stringify(text.text))
}) `
}
Copy the code
GenData () method
File Location:src\compiler\codegen\index.js
/* Process a number of attributes on a node and generate a JSON string consisting of these attributes, such as data = {key: xx, ref: xx,... } * /
export function genData(el: ASTElement, state: CodegenState) :string {
// A JSON string composed of the attributes of the node
let data = '{'
/* Handle the instructions first, since the instructions may change those attributes to execute the instruction compilation method before generating other attributes, such as v-text, V-html, v-model for the Web platform, and then add the corresponding attributes to the EL object, such as V-text: El.textcontent = _S (value, dir) V-html: el.innerhtml = _S (value, dir) When the directive still has tasks at run, such as V-model, return directives: cache [{ name, rawName, value, arg, modifiers }, ...}] */
const dirs = genDirectives(el, state)
if (dirs) data += dirs + ', '
// key, data = {key: XXX}
if (el.key) {
data += `key:${el.key}, `
}
// data = {ref: XXX}
if (el.ref) {
data += `ref:${el.ref}, `
}
// The node with ref attribute is inside the node with v-for instruction, data = {refInFor: true}
if (el.refInFor) {
data += `refInFor:true,`
}
// data = {pre: true}
if (el.pre) {
data += `pre:true,`
}
, data = {tag: 'component'}
if (el.component) {
data += `tag:"${el.tag}", `
}
/* staticClass: xx, class: xx, staticStyle: xx, style: xx } module data generation functions */
for (let i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el)
}
/* Other attributes, get data = {attrs: static attribute string} or data = {attrs: '_d(static attribute string, dynamic attribute string)'} attributes */
if (el.attrs) {
data += `attrs:${genProps(el.attrs)}, `
}
// DOM props, the result is the same as el. Attrs
if (el.props) {
data += `domProps:${genProps(el.props)}, `
}
-data = {' on${eventName}:handleCode '} or - {' on_d(${eventName}:handleCode ', `${eventName},handleCode`) } event handlers */
if (el.events) {
data += `${genHandlers(el.events, false)}, `
}
/* Events with the.native modifier, -data = {' nativeOn${eventName}:handleCode '} or - {' nativeOn_d(${eventName}:handleCode ', `${eventName},handleCode`) */
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true)}, `
}
/* Non-scoped slots, data = {slot: slotName} slot target only for non-scoped slots */
if(el.slotTarget && ! el.slotScope) { data +=`slot:${el.slotTarget}, `
}
// scoped slots, data = {scopedSlots: '_u(XXX)'}
if (el.scopedSlots) {
data += `${genScopedSlots(el, el.scopedSlots, state)}, `
}
/* Data = {model: {value, callback, expression}} component V-model */
if (el.model) {
data += `model:{value:${el.model.value },callback:${el.model.callback },expression:${el.model.expression }}, `
}
/* Inline-template: data = {inlineTemplate: {render: function() {render: function}, staticRenderFns: [ function() {}, ... ] }} * /
if (el.inlineTemplate) {
const inlineTemplate = genInlineTemplate(el, state)
if (inlineTemplate) {
data += `${inlineTemplate}, `}}// Delete the last comma of the JSON string, and add the closing parentheses.
data = data.replace($/ /,.' ') + '} '
/* V-bind dynamic argument wraparound must use the same V-bind object to apply the dynamic binding argument to merge auxiliary objects in order to properly handle the class/style/mustUseProp properties. * /
if (el.dynamicAttrs) {
data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)}) `
}
// v-bind data wrap
if (el.wrapData) {
data = el.wrapData(data)
}
// v-on data wrap
if (el.wrapListeners) {
data = el.wrapListeners(data)
}
return data
}
Copy the code
GenDirectives () method
File Location:src\compiler\codegen\index.js
Cache: [{name, rawName, value, arg, modiFIERS},...}] */ Directives are returned if there is a runtime task
function genDirectives(el: ASTElement, state: CodegenState) :string | void {
// Get the instruction array
const dirs = el.directives
// There is no command
if(! dirs)return
// The processing result of the instruction
let res = 'directives:['
// Is used to indicate whether the directive needs to complete tasks at run time, such as the INPUT event of the V-model
let hasRuntime = false
let i, l, dir, needRuntime
// Iterate over the instruction array
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i]
needRuntime = true
// Get the processing method of the node's current instruction, such as v-html, V-text, V-model for the Web platform
const gen: DirectiveFunction = state.directives[dir.name]
if (gen) {
// Execute the compilation method of the instruction, and return true if the instruction still needs to complete part of the task at runtime, such as v-modelneedRuntime = !! gen(el, dir, state.warn) }if (needRuntime) {
// indicates that the directive has tasks at run time
hasRuntime = true
// res = directives:[{ name, rawName, value, arg, modifiers }, ...]
res += `{name:"${dir.name}",rawName:"${dir.rawName}"${dir.value ? `,value:(${dir.value}),expression:The ${JSON.stringify(dir.value)}` : ' '
}${dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ' '
}${dir.modifiers ? `,modifiers:The ${JSON.stringify(dir.modifiers)}` : ' '
}}, `}}// Res is returned only if the directive has a run-time task
if (hasRuntime) {
return res.slice(0, -1) + '] '}}Copy the code
GenDirectives () method
File Location:src\compiler\codegen\index.js
/* If there is no dynamic attribute, return: 'attrName,attrVal,... 'If dynamic property exists, return: '_d(static property string, dynamic property string)' */
function genProps(props: Array<ASTAttr>) :string {
// Static attributes
let staticProps = ` `
// Dynamic attributes
let dynamicProps = ` `
// Iterate over the property array
for (let i = 0; i < props.length; i++) {
/ / property
const prop = props[i]
/ / property values
const value = __WEEX__
? generateValue(prop.value)
: transformSpecialNewlines(prop.value)
if (prop.dynamic) {
// Dynamic properties, 'dAttrName,dAttrVal... `
dynamicProps += `${prop.name}.${value}, `
} else {
// Static attribute, 'attrName:attrVal,... '
staticProps += `"${prop.name}":${value}, `}}// Close the static attribute string and remove the ',' at the end of the static attribute.
staticProps = ` {${staticProps.slice(0, -1)}} `
if (dynamicProps) {
// Return _d(static attribute string, dynamic attribute string)
return `_d(${staticProps}[${dynamicProps.slice(0, -1)}]) `
} else {
// Indicates that there is no dynamic attribute in the property array
return staticProps
}
}
Copy the code
GenHandlers () method
File Location:src\compiler\codegen\events.js
/ * generate custom event code dynamic: 'nativeOn | on_d (staticHandlers, [dynamicHandlers])' static: ` nativeOn | on ${staticHandlers} ` * /
export function genHandlers (events: ASTElementHandlers, isNative: boolean) :string {
// Native is nativeOn, otherwise on
const prefix = isNative ? 'nativeOn:' : 'on:'
/ / static
let staticHandlers = ` `
/ / dynamic
let dynamicHandlers = ` `
Events = [{name: {value: callback function name,...}}] */
for (const name in events) {
const handlerCode = genHandler(events[name])
if (events[name] && events[name].dynamic) {
// dynamic, dynamicHandles = 'eventName,handleCode,... , `
dynamicHandlers += `${name}.${handlerCode}, `
} else {
// staticHandlers = `eventName:handleCode,... , `
staticHandlers += `"${name}":${handlerCode}, `}}// Close the static event-handling code string, removing the trailing ','
staticHandlers = ` {${staticHandlers.slice(0, -1)}} `
if (dynamicHandlers) {
// Dynamic, on_d(statickHandles, [dynamicHandlers])
return prefix + `_d(${staticHandlers}[${dynamicHandlers.slice(0, -1)}]) `
} else {
// Static, 'on${staticHandlers}'
return prefix + staticHandlers
}
}
Copy the code
GenStatic () method
File Location:src\compiler\codegen\index.js
Hoist static sub-trees out */ hoist static sub-trees out */ static sub-trees out */
function genStatic(el: ASTElement, state: CodegenState) :string {
// Indicates that the current static node has been processed
el.staticProcessed = true
/* Some elements (templates) need to behave differently in the V-pre node. All pre nodes are static roots, so they can be used to wrap state changes and reset them when exiting the Pre node. */
const originalPreState = state.pre
if (el.pre) {
state.pre = el.pre
}
/* Push the static root render function into the staticRenderFns array, for example: [' with(this){return _c(tag, data, children)} '] */
state.staticRenderFns.push(`with(this){return ${genElement(el, state)}} `)
state.pre = originalPreState
/* Returns an executable function: _m(idx, true or ") idx = Subscript */ of the current static node's rendering function in the staticRenderFns array
return `_m(${state.staticRenderFns.length - 1
}${el.staticInFor ? ',true' : ' '
}) `
}
Copy the code
GenOnce () method
File Location:src\compiler\codegen\index.js
/* If (condition, condition, condition) {if (condition, condition); /* If (condition, condition, condition); render1 : Render2 2, the current node is a static node contained in the V-for directive, resulting in _O (_c(tag, data, children), number, key) '3, the current node is a pure V-once node, Get '_m(idx, true of' ") 'v-once */
function genOnce(el: ASTElement, state: CodegenState) :string {
// The v-once directive marking the current node has already been processed
el.onceProcessed = true
if(el.if && ! el.ifProcessed) {/* If the v-if instruction &&if instruction has not been processed, then the node with the V-if instruction is processed, resulting in a ternary expression: condition? render1 : render2 */
return genIf(el, state)
} else if (el.staticInFor) {
/* Indicates that the current node is wrapped in a static node that also contains the v-for instruction key */
let key = ' '
let parent = el.parent
while (parent) {
if (parent.for) {
key = parent.key
break
}
parent = parent.parent
}
// If the key does not exist, the v-once node can only be used inside the V-for node with the key
if(! key) { process.env.NODE_ENV ! = ='production' && state.warn(
`v-once can only be used inside v-for that is keyed. `,
el.rawAttrsMap['v-once'])return genElement(el, state)
}
// Generate _o(_c(tag, data, children), number, key) '
return `_o(${genElement(el, state)}.${state.onceId++}.${key}) `
} else {
_m(idx, true or "") */
return genStatic(el, state)
}
}
Copy the code
GenFor () method
File Location:src\compiler\codegen\index.js
_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)}) '*/
export function genFor(el: any, state: CodegenState, altGen? :Function, altHelper? : string) :string {
// An iterator for v-for, such as an array
const exp = el.for
// Alias for iteration
const alias = el.alias
// iterator v-for = "(item,idx) in obj", such as iterator1 = idx
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ' '
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ' '
// the v-for directive must use key when on the component
if(process.env.NODE_ENV ! = ='production'&& state.maybeComponent(el) && el.tag ! = ='slot'&& el.tag ! = ='template' &&
!el.key
) {
state.warn(
` <${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
`v-for should have explicit keys. ` +
`See https://vuejs.org/guide/list.html#key for more info.`,
el.rawAttrsMap['v-for'].true /* tip */)}// indicates that the v-for directive on the current node has been processed
el.forProcessed = true // avoid recursion
// return 'exp (alias, iterator1, iterator2){return _c(tag, data, children)})'
return `${altHelper || '_l'}((${exp}), ` +
`function(${alias}${iterator1}${iterator2}) {` +
`return ${(altGen || genElement)(el, state)}` +
'}) '
}
Copy the code
GenIf () method
File Location:src\compiler\codegen\index.js
// Process a node with a V-if instruction, resulting in a ternary expression, condition? render1 : render2
export function genIf(el: any, state: CodegenState, altGen? :Function, altEmpty? : string) :string {
// The v-if directive that marks the current node has already been processed to avoid invalid recursion
el.ifProcessed = true // avoid recursion
// Get the ternary expression, condition? render1 : render2
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions(conditions: ASTIfConditions, state: CodegenState, altGen? :Function, altEmpty? : string) :string {
// If the length is empty, an empty node rendering function is returned
if(! conditions.length) {return altEmpty || '_e()'
}
// Take the first condition object from the conditions array {exp, block}
const condition = conditions.shift()
// The result is a ternary expression string, condition? Render function 1: Render function 2
if (condition.exp) {
/* If condition. Exp is true, a ternary expression is obtained; if not, the next element in the array is recursively found until the condition is found, and a ternary expression */ is returned
return ` (${condition.exp})?${genTernaryExp(condition.block) }:${genIfConditions(conditions, state, altGen, altEmpty) }`
} else {
return `${genTernaryExp(condition.block)}`
}
// v-if with v-once should generate code like (a)? _m(0):_m(1)
function genTernaryExp(el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
Copy the code
GenIf () method
File Location:src\compiler\codegen\index.js
_t(slotName, children, attrs, bind) */
function genSlot(el: ASTElement, state: CodegenState) :string {
// Slot name
const slotName = el.slotName || '"default"'
// Generate all child nodes
const children = genChildren(el, state)
// Result string _t(slotName, children, attrs, bind)
let res = `_t(${slotName}${children ? `,function(){return ${children}} ` : ' '}`
const attrs = el.attrs || el.dynamicAttrs
? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr= > ({
// slot props are camelized
name: camelize(attr.name),
value: attr.value,
dynamic: attr.dynamic
})))
: null
const bind = el.attrsMap['v-bind']
if((attrs || bind) && ! children) { res +=`,null`
}
if (attrs) {
res += `,${attrs}`
}
if (bind) {
res += `${attrs ? ' ' : ',null'}.${bind}`
}
return res + ') '
}
Copy the code
GenComponent () method
File Location:src\compiler\codegen\index.js
/* Generate dynamic component render function, Return '_C (compName, data, children)' componentName is el.component, take it as argument to shun flow's pessimistic refinement */
function genComponent(componentName: string, el: ASTElement, state: CodegenState) :string {
// All child nodes
const children = el.inlineTemplate ? null : genChildren(el, state, true)
// Return '_c(compName, data, children)', where compName is the value of the is attribute
return `_c(${componentName}.${genData(el, state)}${children ? `,${children}` : ' '
}) `
}
Copy the code
conclusion
What is the process of generating a render function?
There are two types of renderings generated by the compiler:
render
Function that is responsible for generating dynamic nodesvnode
staticRenderFns
In the arrayStatic rendering function
Is responsible for generating static nodesvnode
_C (tag, attr, children, normalizationType) : _C (tag, attr, children, normalizationType)
tag
Is the tag nameattr
Is a property objectchildren
Is an array of child nodes in which each element is of the format_c(tag, attr, children, normalizationTYpe)
In the form of,normalization
Represents the normalized type of the node, which is a number 0, 1, 2
How are static nodes handled?
The processing of static nodes is divided into two steps:
- Static nodes will be generated
vnode
Function instaticRenderFns
In the array - Returns a
_m(idx)
The executable function of thestaticRenderFns
The subscript in the array isidx
Function to generate static nodesvnode
How are v-once, V-IF, V-for, components, etc handled?
- pure
v-once
Node processing mode andStatic node
consistent v-if
The result of node processing is oneTernary expression
v-for
The result of the node’s processing is executable_l
Function, which is responsible for generatingv-for
The node’svnode
- The result of processing the component is the same as that of a normal element
_c(compName)
The executable code that generates the componentvnode
The articles
- What does vUE initialization do?
- How to understand vUE responsiveness?
- How does vUE update asynchronously?
- Do you really understand the Vue global Api?
- Did you lose the instance method in VUE?
- Do you know Hook Event?
- Vue compiler parsing
- Optimization of vUE compiler parsing