This is the 30th day of my participation in the First Challenge 2022.
Previously on & Background
Parse’s next stage, generate, after generating the AST, is to turn the AST into a rendering function code string. The core method of this process is generate(), and the core method of generate is the genElment method. The first step of the genElement method is to process the static root node, that is, the genStatic method. Promote the static render function to the staticRenderFns array;
The genStatic() method will mark el.satcicProcess to true, and then recursively call genElement() to execute the logic that generates the render function, which in turn needs to get the property information carried on the AST. This is handled by calling the genData method.
GenData is an intermediate step, and genData will call the genDirectives to handle the EL. Directives, which in turn bind the V-Model runtime helper that is the runtime event handler.
Since the call stack is too deep, let’s review the call stack before proceeding:
generate -> genElement -> if(el.staticRoot && ! el.saticProcessed) genStatic -> genElement else { data = genData() }
Here is an example of a static root template:
<div id="app">
+ <div class="staticR">
+
hahahah
+ </div><span v-for="item in someArr" :key="index">{{item}}</span> <input :type="inputType" v-model="inputValue" /> {{ msg }} <some-com :some-key="forProp"></some-com> <div>someComputed = {{someComputed}}</div> <div class="static-div"> Static node </div> </div>Copy the code
In this article we will continue with the steps after genData() to generate genElement:
export function genElement (el: ASTElement, state: CodegenState) :string {
if(...). {}else if (...) {
} else {
let code
if(...). {//
} else {
let data
if(! el.plain || (el.pre && state.maybeComponent(el))) {// genData mentioned above
data = genData(el, state)
}
// Get the data
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : ' ' // data
}${
children ? `,${children}` : ' ' // children
}) `
return code
}
}
Copy the code
Second, the genChildren
Methods location: SRC/compiler/codegen/index. The js – > functions provides genChildren
Method parameters:
el
, of the parent elementast
nodestate
.CodegenState
objectcheckSkip/altGenElement/altGenNode
Temporarily ignore
The el.children () method receives the parent element, processes the array of el.children, and generates the rendering code for all the el.children nodes, like this: [_c(tag, data, children, normalizationType), _c(tag2, data2, children2…)….] . The specific logic is as follows:
- To optimize the
el.children
Only one term withv-for
And are notslot/template
Tag case, no longer go through the back door map directly go genElement; - call
el.children.map
I’m going to change each of these termsrender
Function, and concatenated into an array format string;
export function genChildren (el: ASTElement, state: CodegenState, checkSkip? : boolean, altGenElement? :Function, altGenNode? :Function
) :string | void {
// Get all the children of el
const children = el.children
if (children.length) {
const el: any = children[0]
// optimize single v-for
if (children.length === 1&& el.for && el.tag ! = ='template'&& el.tag ! = ='slot'
) {
// Optimized, only one child with v-for instruction && child is not template/slot tag
// Call genElement directly to generate the node's rendering function, without using map and genCode
const normalizationType = checkSkip
? state.maybeComponent(el) ? ` `, 1 : ` `, 0
: ` `
return `${(altGenElement || genElement)(el, state)}${normalizationType}`
}
// Get the node normalized type, ignore it
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0
// function, a function that generates code
const gen = altGenNode || genNode
// Returns an array, each of which is a string of render code
return ` [${children.map(c => gen(c, state)).join(', ')}]${
normalizationType ? `,${normalizationType}` : ' '
}`}}Copy the code
2.1 genNode
Methods location: SRC/compiler/codegen/index. The js – > function genNode
Method parameters:
node
:ast
The node objectstate
.CodegenState
The instance
Function: call different methods to generate render function code according to different node types; If the node type is 1, it is an element node, and the genElement() method is recursed directly.
Using the genChildren() method above, we recursively re-call genElement if a child node is an element, and recursively generate the render function if the child node has other children.
function genNode (node: ASTNode, state: CodegenState) :string {
if (node.type === 1) {
return genElement(node, state)
} else if (node.type === 3 && node.isComment) {
return genComment(node)
} else {
return genText(node)
}
}
Copy the code
For example, div.demo looks like this:
<div class="demo">
<article>hahahah</article>
<article>hahahah</article>
</div>
Copy the code
The rendering function for genChildren’s returned child element looks like this:
"[ _c('article', '', _s('hahhahahahha')), _c('article', '', _s('hahhahahahha')) ]"
Copy the code
GenStatic returns the result
3.1 Sorting out the call process
Generate () -> genElment() -> genStatic() -> genElement -> genChildren;
Now we are going to look at the result of genStatic and we need to return the next call stack, genElement, to call the code location as shown in the following example:
genElement
callgenStatic
export function genElement () :string {
// genElement calls genStatic
if(el.staticRoot && ! el.staticProcessed) {return genStatic(el, state)
}
}
Copy the code
genStatic
callgenElement
function genStatic () {
// This push statement recursively calls genElement
state.staticRenderFns.push(`with(this){return ${genElement(el, state)}} `)
// This is the return value of genStatic
return `_m(${
state.staticRenderFns.length - 1
}${
el.staticInFor ? ',true' : ' '
}) `
}
Copy the code
genElement
callgenChildren
export function genElement (el: ASTElement, state: CodegenState) :string {
if (...) {
} else {
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : ' '
}${
children ? `,${children}` : ' ' // children
}) `
}
return code
}
}
Copy the code
3.2 Return results layer by layer
- In our case
div.staticR
Is a static root. Let’s use this as an example.
<div class="staticR">
<article>hahahah</article>
</div>
Copy the code
We learned about the call stack in step 3.1, and now we can go backwards to get the return value of genStatic:
hahahah
的render
Function as follows:
"[_v(\"hahahah\")]"
Copy the code
<article>hahahah</article>
的render
Function:
"[_c('article',[_v(\"hahahah\")])]"
Copy the code
<div class="staticR"><article>hahahah</article></div>
的render
The body of the function:
"_c('div', {staticClass:\"staticR\"}, [_c('article',[_v(\"hahahah\")])])"
Copy the code
These render functions aren’t really functions yet, because they’re just strings at compile time, and the code is called at run time. But you can see that _c(…) _c is a call to the _c method, which is also a run-time render helper function. These methods will eventually be mounted to the Vue instance. With (this) allows the browser to find the _C method from this (Vue) instance.
Why is there no “this” here? Good question, if you have this question, you have read; This is because the return value above is concatenated before pushing into staticRenderFns:
function genStatic () {
// Push 'with(this)' is the case
state.staticRenderFns.push(`with(this){return ${genElement(el, state)}} `)
Copy the code
genStatic()
Returns the result
The return value of genStatic is “_m(index, true/ “)”; _m() is also a render helper function, and _c is also a method that Vue has prepared in advance to mount on Vue instance this.
Why is the first argument to _m index? Since we only push into staticRenderFns before returning the value, the last item in the array is the index of the current element.
function genStatic () {
// Return value form: _m(index, true/ ")
return `_m(${
state.staticRenderFns.length - 1
}${
el.staticInFor ? ',true' : ' '
}) `
}
Copy the code
Four,
This essay discusses the first case of genElement. GenStatic gets the result of the render. GenStatic does the following:
- Mark the current
el.staticProcessed
为true
To prevent repeated processing; - Recursive calls
genElement
, generate the rendering function of each node; - when
genElement
The last of theelse
It handles rendering of normal elements. First callgenData
Gets on the elementdata
And then callgenChildren()
The child elements are treated byrender
An array of function code items, each rendering a child element; genStatic
To obtaingenElement
Returns the result of the packagewith(this)
Statement and thenpush
到staticRenderFns
;genStatic
Is used to process the current static root node intoRun-time invocation of the _m method
._m(index of the current element in staticRenderFns, true or "")