Vue-next /packages/compile-core/ SRC /codegen.ts
Transform is implemented above, now we will enter the codeGen part at the end of compile. Compared with the former transform, codeGen is really very simple, and the source code is located here
Said at the beginning
My implementation is quite different from the source implementation, because the source code takes into account all sorts of helper, cache, hoist, etc., which I didn’t implement. After the transform, the type attribute mounted on the codegenNode node on the AST is the code structure corresponding to this node, according to which the source code is generated. See the source code for details, this part is relatively clear
The former Transform module is basically written in accordance with the source code structure, and the generated codegenNode structure is basically consistent with the source code. However, because of this, the codeGen part has to be dealt with very, very hard. I hope you will forgive me. Just get the idea
Look at the
Since there is no need to consider a variety of complex structures, I simply divide it into elements, attributes, text, and combined expressions for code generation. The function to generate nodes is naturally reminiscent of the h function exposed in the Runtime module. The source code is createVNode. However, there is little difference between the two. Both can create vNodes. The following is the argument received by the h function
function h(type, props, children) {
// TODO
}
Copy the code
Write about the
createCodegenContext
I don’t really need too much context here, but I’m just going to pretend to write it a little bit, and it’s very simple. Okay
function createCodegenContext() {
const context = {
// state
code: ' '.// Object code
indentLevel: 0.// Indent level
// method
push(code) {
context.code += code;
},
indent() {
newline(++context.indentLevel);
},
deindent(witoutNewLine = false) {
if (witoutNewLine) {
--context.indentLevel;
} else{ newline(--context.indentLevel); }},newline(){ newline(context.indentLevel); }};function newline(n) {
context.push('\n' + ' '.repeat(n));
}
return context;
}
Copy the code
generate
The generate function is the main entry point to CodeGen, where we need to get the context, generate the initial structure of the code, the content is generated recursively by genNode, and of course return the generated code
function generate(ast) {
const context = createCodegenContext();
const { push, indent, deindent } = context;
indent();
push('with (ctx) {');
indent();
push('return ');
if (ast.codegenNode) {
genNode(ast.codegenNode, context);
} else {
push('null');
}
deindent();
push('} ');
return {
ast,
code: context.code,
};
}
Copy the code
genNode
GenNode simply uses switch-case to control the flow and call different methods
function genNode(node, context) {
// If it is a string, push it
if (typeof node === 'string') {
context.push(node);
return;
}
switch (node.type) {
case NodeTypes.ELEMENT:
genElement(node, context);
break;
case NodeTypes.TEXT:
case NodeTypes.INTERPOLATION:
genTextData(node, context);
break;
case NodeTypes.COMPOUND_EXPRESSION:
genCompoundExpression(node, context);
break; }}Copy the code
genElement
To create a VNode, we use the h function, which means we need to pass in tag, props, and children as arguments. Here we take the logic out of generating properties and child nodes
function genElement(node, context) {
const { push, deindent } = context;
const { tag, children, props } = node;
// tag
push(`h(${tag}, `);
// props
if (props) {
genProps(props.arguments[0].properties, context);
} else {
push('null, ');
}
// children
if (children) {
genChildren(children, context);
} else {
push('null');
}
deindent();
push(') ');
}
Copy the code
genProps
So what genProps is going to do is take the property data in the node and concatenate it into an object and push it into the object code, Properties (props. Arguments [0]. Properties)
// <p class="a" @click="fn">hello {{ World }}</p>[{"type": "JS_PROPERTY"."key": {
"type": "SIMPLE_EXPRESSION"."content": "class"."isStatic": true
},
"value": {
"type": "SIMPLE_EXPRESSION"."content": {
"type": "TEXT"."content": "a"
},
"isStatic": true}}, {"type": "JS_PROPERTY"."key": {
"type": "SIMPLE_EXPRESSION"."content": "onClick"."isStatic": true."isHandlerKey": true
},
"value": {
"type": "SIMPLE_EXPRESSION"."content": "fn"."isStatic": false}}]Copy the code
So all we need to do is follow this structure, as follows
function genProps(props, context) {
const { push } = context;
if(! props.length) { push('{}');
return;
}
push('{');
for (let i = 0; i < props.length; i++) {
// Iterate over each prop object to get the key and value nodes
const prop = props[i];
const key = prop ? prop.key : ' ';
const value = prop ? prop.value : prop;
if (key) {
// key
genPropKey(key, context);
// value
genPropValue(value, context);
} else {
// If the key does not exist, it is a V-bind
const { content, isStatic } = value;
const contentStr = JSON.stringify(content);
push(`${contentStr}: ${isStatic ? contentStr : content}`);
}
if (i < props.length - 1) {
push(', ');
}
}
push('},);
}
/ / generated key
function genPropKey(node, context) {
const { push } = context;
const { isStatic, content } = node;
push(isStatic ? JSON.stringify(content) : content);
push(':');
}
/ / generated values
function genPropValue(node, context) {
const { push } = context;
const { isStatic, content } = node;
push(isStatic ? JSON.stringify(content.content) : content);
}
Copy the code
Here must laugh at yourself again, this code is really ugly…..
genChildren
The child node is an array, so you just need to refer to the structure of genProps. However, since my transformText did not generate codegenNode, it had to be processed separately. In addition, the combined expression COMPOUND_EXPRESSION is also processed separately, and the rest of the normal recursive genNode can be processed
function genChildren(children, context) {
const { push, indent } = context;
push('[');
indent();
// Process COMPOUND_EXPRESSION separately
if (children.type === NodeTypes.COMPOUND_EXPRESSION) {
genCompoundExpression(children, context);
}
// Handle TEXT alone
else if (isObject(children) && children.type === NodeTypes.TEXT) {
genNode(children, context);
}
// The rest of the nodes are directly recursive
else {
for (let i = 0; i < children.length; i++) {
const child = children[i];
genNode(child.codegenNode || child.children, context);
push(', ');
}
}
push('] ');
}
Copy the code
genTextData
Both interpolation and text nodes are handled by this function, because the only difference between them in the code generated is whether the child node is a string
function genTextData(node, context) {
const { push } = context;
const { type, content } = node;
// If it is a text node, just pull out the content
// If it is an interpolation, you need to take out content.content
const textContent =
type === NodeTypes.TEXT
? JSON.stringify(content)
: NodeTypes.INTERPOLATION
? content.content
: ' ';
// The default text node has no attributes
push('h(Text, ');
push('null, ');
push(`${textContent}) `);
}
Copy the code
genCompoundExpression
A combined expression is essentially a node, and several of its children may be text nodes or interpolation nodes, which can be directly recursed
function genCompoundExpression(node, context) {
const { push } = context;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (typeof child === 'string') {
push(child);
} else {
genNode(child, context);
}
if(i ! == node.children.length -1) {
push(', '); }}}Copy the code
Q&A
Q: How does h function relate to createVNode? A: The createVNode function (props) is used to pass props to children. The createVNode function (props) is used to pass props to children. The createVNode function (props) is used to pass props to children. So no problem
Q: What are the main differences between your implementation here and the source implementation? A: The implementation in the source code is completely guided by the Type attribute of codegenNode to generate the corresponding structure, while the content of the node is not the main concern. That is to say, my implementation here is based on the function, while the source code is based on the structure, which creates a significant difference. GenChildren, genProps, genPropKey, genObjectExpression, genArrayExpression, genNodeListAsArray, etc. This way you can extract functions from the structure, so you can reuse functions a lot, and you can be more flexible, and this is really dumb code THAT I wrote
Q: What’s the use of this? A: I passed the test I wrote myself
conclusion
To be honest, I’m embarrassed to summarize, because the implementation here is really bad, but in my defense, I generated codegenNode to try to fit the transform implementation of the source code, which would be a bit pointless if I didn’t use it. Let’s just look at figure 1 and have fun, so you don’t want to be lazy