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:

  1. el, of the parent elementastnode
  2. state.CodegenStateobject
  3. checkSkip/altGenElement/altGenNodeTemporarily 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:

  1. To optimize theel.childrenOnly one term withv-forAnd are notslot/templateTag case, no longer go through the back door map directly go genElement;
  2. callel.children.mapI’m going to change each of these termsrenderFunction, 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:

  1. node:astThe node object
  2. state.CodegenStateThe 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:

  1. genElementcallgenStatic
export function genElement () :string {
  // genElement calls genStatic
  if(el.staticRoot && ! el.staticProcessed) {return genStatic(el, state)
  }
}
Copy the code
  1. genStaticcallgenElement
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
  1. genElementcallgenChildren
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 casediv.staticRIs 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:

  1. hahahahrenderFunction as follows:
"[_v(\"hahahah\")]"
Copy the code
  1. <article>hahahah</article>renderFunction:
"[_c('article',[_v(\"hahahah\")])]"
Copy the code
  1. <div class="staticR"><article>hahahah</article></div>renderThe 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
  1. 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:

  1. Mark the currentel.staticProcessedtrueTo prevent repeated processing;
  2. Recursive callsgenElement, generate the rendering function of each node;
  3. whengenElementThe last of theelseIt handles rendering of normal elements. First callgenDataGets on the elementdataAnd then callgenChildren()The child elements are treated byrenderAn array of function code items, each rendering a child element;
  4. genStaticTo obtaingenElementReturns the result of the packagewith(this)Statement and thenpushstaticRenderFns;
  5. genStaticIs 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 "")