1 Introduction
After the previous articles, we parse the template string written by the user through the parsing stage to generate the corresponding abstract syntax tree, and AST
then AST
mark the static nodes and static root nodes in the optimization stage. Now we have finally arrived at the last stage of the three stages of template compilation - the code generation stage. The so-called code generation phase, what code is to be generated? Answer: To generate render
a function string.
We know that Vue
when the instance is mounted, it will call its own function to generate the corresponding options render
on the instance . Simply put, as long as the function is called , the template can be converted into the corresponding virtual . So if you want to call a function, you must have this function first, so where does this function come from? Is it written by the user or generated by yourself? The answer is all possible. We know that in daily development, we can write an option in the component options by hand , and its value corresponds to a function, then this function is a function. When the user writes the function by hand, the user's handwritten function will be called when the component is mounted . What if the user didn't write it? At this time, you need to generate a function based on the content of the template to call when the component is mounted. And the process of generating functions according to the content of the template is the code generation stage to be introduced in this article.template
VNode
Vue
render
DOM
Vue
render
render
render
Vue
Vue
render
render
render
Vue
render
Vue
render
Vue
render
Now we know that the so-called code generation is actually to AST
generate a function according to the abstract syntax tree corresponding to the template, and by calling this function, the virtual corresponding to the template can be obtained DOM
.
2. How to generate render function according to AST
AST
From the above, we know that the main work of the code generation phase is to generate corresponding functions based on the existing render
ones for the components to call when they are mounted. As long as the components call this render
function, they can get AST
the corresponding virtual DOM
ones VNode
. So how to AST
generate render
function according to it? What kind of process is this? Next, let's analyze it in detail.
Suppose you have the following template:
<div id="NLRX"><p>Hello {
{name}}</p></div>
After the template is parsed and optimized, it corresponds to AST
the following:
ast = {
'type': 1,
'tag': 'div',
'attrsList': [
{
'name':'id',
'value':'NLRX',
}
],
'attrsMap': {
'id': 'NLRX',
},
'static':false,
'parent': undefined,
'plain': false,
'children': [{
'type': 1,
'tag': 'p',
'plain': false,
'static':false,
'children': [
{
'type': 2,
'expression': '"Hello "+_s(name)',
'text': 'Hello {
{name}}',
'static':false,
}
]
}]
}
AST
Next, we will generate the corresponding function according to the existing one render
. The process of generating render
a function is actually a recursive process, recursing AST
each node in turn from top to bottom, and AST
creating different VNode
types according to different node types. Next, let's compare the existing templates and AST
actually demonstrate render
the process of generating functions.
-
First of all, the root node
div
is an element typeAST
node, then we need to create an element typeVNode
, weVNode
call the method of creating an element type_c(tagName,data,children)
. We don't care_c()
what it is for now, we only need to know the call_c()
to create an element typeVNode
. Then the following code can be generated:_c('div',{ attrs:{ "id":"NLRX"}},[/*子节点列表*/])
-
The root node
div
has child nodes, then we enter the child node listchildren
to traverse the child nodes, and find that the child nodesp
are also element-type, then continue to create element typesVNode
and put them into the child node list of the root node in the above code, as follows:_c('div',{ attrs:{ "id":"NLRX"}},[_c('p',{ attrs:{ }},[/*子节点列表*/])])
-
In the same way, continue to traverse
p
the child nodes of the node and find that it is a text node, then create a text typeVNode
and insert it intop
the node's child node list. Similarly,VNode
we call_v()
the method to create the text type, as follows:_c('div',{ attrs:{ "id":"NLRX"}},[_c('p',{ attrs:{ }},[_v("Hello "+_s(name))])])
-
At this point, the whole
AST
traversal is complete, and we will repackage the obtained code, as follows:` with(this){ reurn _c( 'div', { attrs:{"id":"NLRX"}, }, [ _c( 'p', { attrs:{} }, [ _v("Hello "+_s(name)) ] ) ] ) } `
-
Finally, we pass the function string obtained above to
createFunction
the function (about this function will be introduced later),createFunction
the function will help us convert the obtained function string into a real function, and assign it torender
the option in the component, so it isrender
a function. as follows:res.render = createFunction(compiled.render, fnGenErrors) function createFunction (code, errors) { try { return new Function(code) } catch (err) { errors.push({ err, code }) return noop } }
AST
The above is the process of generating a function corresponding to a simple template render
. We have already understood the theoretical process, so how is it actually implemented in the source code? Next, we will return to the source code to analyze its specific implementation process.
3. Return to the source code
The source code of the code generation stage is located src/compiler/codegen/index.js
in . Although the source code is very long, the logic is not complicated. The core logic is as follows:
export function generate (ast,option) {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${
code}}`,
staticRenderFns: state.staticRenderFns
}
}
const code = generate(ast, options)
Call generate
the function and pass in the optimized value ast
, generate
first judge whether it is empty inside the function ast
, if it is not empty, call genElement(ast, state)
the function to create VNode
, if it is empty, create an empty element div
type VNode
. The resulting result is then with(this){return ${code}}
returned in a package. It can be seen that it is the function that really works genElement
. Let's continue to look at genElement
what is inside the function.
genElement
The function definition is as follows:
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
// component or element
let code
if (el.component) {
code = genComponent(el.component, el, state)
} else {
const data = el.plain ? undefined : genData(el, state)
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${
el.tag}'${
data ? `,${
data}` : '' // data
}${
children ? `,${
children}` : '' // children
})`
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
genElement
AST
The function logic is very clear, which is to execute different code generation functions according to the different attributes of the current element node. Although there are many kinds of element node attributes, there are only three types that are actually created in the end VNode
, namely element nodes, text nodes, and comment nodes. Next, we will focus on analyzing how to generate render
the functions of these three node types.
3.1 Element node
render
The function code for generating element-type nodes is as follows:
const data = el.plain ? undefined : genData(el, state)
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${
el.tag}'${
data ? `,${
data}` : '' // data
}${
children ? `,${
children}` : '' // children
})`
The function of generating element nodes render
is to generate a _c()
string of function calls. As mentioned above, _c()
the function receives three parameters, which are the label name of the node tagName
, the attribute of the node data
, and the list of child nodes of the node children
. Then we just need to fill in all three parts.
-
Get node attribute data
First judge
plain
whether the attribute istrue
, if it istrue
, it means that the node has no attribute, and the value willdata
be assignedundefined
; if nottrue
, the function will be calledgenData
to obtain the node attributedata
data.genData
The function definition is as follows:export function genData (el: ASTElement, state: CodegenState): string { let data = '{' const dirs = genDirectives(el, state) if (dirs) data += dirs + ',' // key if (el.key) { data += `key:${ el.key},` } // ref if (el.ref) { data += `ref:${ el.ref},` } if (el.refInFor) { data += `refInFor:true,` } // pre if (el.pre) { data += `pre:true,` } // 篇幅所限,省略其他情况的判断 data = data.replace(/,$/, '') + '}' return data }
We can see that
genData
although the source code is very long, its logic is very simple. It is splicing strings, firstdata
assigning a value to it{
, and then judging which attribute data exists, splicing these data intodata
, and finally adding one}
, and finally getting all the attributes of the nodedata
. -
Get the child node list children
Obtaining the child node list
children
is actually traversing the elements in theAST
attributechildren
, and then generating differentVNode
creation function call strings according to the different element attributes, as follows:export function genChildren (el): { if (children.length) { return `[${ children.map(c => genNode(c, state)).join(',')}]` } } function genNode (node: ASTNode, state: CodegenState): string { if (node.type === 1) { return genElement(node, state) } if (node.type === 3 && node.isComment) { return genComment(node) } else { return genText(node) } }
-
After the above two steps are completed,
_c()
a function call string is generated, as follows:code = `_c('${ el.tag}'${ data ? `,${ data}` : '' // data }${ children ? `,${ children}` : '' // children })`
3.2 Text nodes
The text type VNode
can be created by calling a function, so the function _v(text)
to generate a text node is to generate a string of function calls. The function receives the text content as a parameter, and if the text is dynamic text, it uses the properties of the dynamic text node , and if it is pure static text, it uses the properties. Its generated code is as follows:render
_v(text)
_v()
AST
expression
text
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))
})`
}
3.3 Comment node
The comment type VNode
can be created by calling a function, so the function _e(text)
to generate a comment node is to generate a string of function calls. The function receives the comment content as a parameter, and its generated code is as follows:render
_e(text)
_e()
export function genComment (comment: ASTText): string {
return `_e(${
JSON.stringify(comment.text)})`
}
4. Summary
This article introduces the last of the three phases of template compilation - the code generation phase.
First, it introduces why there is a code generation phase and what the code generation phase mainly does. AST
We know that code generation is actually to generate a function based on the abstract syntax tree corresponding to the template to be called when the component is mounted. By calling this function, you can get the virtual corresponding to the template DOM
.
Next, we demonstrate the process of recursively traversing the template and finally generating render
a function through a simple template.
Finally, we return to the source code and understand render
the specific implementation process of the generating function by analyzing the source code.