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