[Vue2.0 source code learning] template compilation - template analysis (code generation stage)

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 ASTthen ASTmark 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 rendera function string.

We know that Vuewhen the instance is mounted, it will call its own function to generate the corresponding options renderon 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.templateVNodeVuerenderDOMVuerenderrenderrenderVueVuerenderrenderrenderVuerenderVuerenderVuerender

Now we know that the so-called code generation is actually to ASTgenerate 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

ASTFrom the above, we know that the main work of the code generation phase is to generate corresponding functions based on the existing renderones for the components to call when they are mounted. As long as the components call this renderfunction, they can get ASTthe corresponding virtual DOMones VNode. So how to ASTgenerate renderfunction 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 ASTthe 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,
        }
      ]
    }]
  }

ASTNext, we will generate the corresponding function according to the existing one render. The process of generating rendera function is actually a recursive process, recursing ASTeach node in turn from top to bottom, and ASTcreating different VNodetypes according to different node types. Next, let's compare the existing templates and ASTactually demonstrate renderthe process of generating functions.

  1. First of all, the root node divis an element type ASTnode, then we need to create an element type VNode, we VNodecall 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 type VNode. Then the following code can be generated:

    _c('div',{
          
          attrs:{
          
          "id":"NLRX"}},[/*子节点列表*/])
    
  2. The root node divhas child nodes, then we enter the child node list childrento traverse the child nodes, and find that the child nodes pare also element-type, then continue to create element types VNodeand 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:{
          
          }},[/*子节点列表*/])])
    
  3. In the same way, continue to traverse pthe child nodes of the node and find that it is a text node, then create a text type VNodeand insert it into pthe node's child node list. Similarly, VNodewe call _v()the method to create the text type, as follows:

    _c('div',{
          
          attrs:{
          
          "id":"NLRX"}},[_c('p',{
          
          attrs:{
          
          }},[_v("Hello "+_s(name))])])
    
  4. At this point, the whole ASTtraversal 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))
             ]
           )
         ]
       )
     }
     `
    
  5. Finally, we pass the function string obtained above to createFunctionthe function (about this function will be introduced later), createFunctionthe function will help us convert the obtained function string into a real function, and assign it to renderthe option in the component, so it is rendera 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
      }
    }
    

ASTThe 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.jsin . 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 generatethe function and pass in the optimized value ast, generatefirst 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 divtype 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 genElementwhat is inside the function.

genElementThe 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
  }
}

genElementASTThe 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 renderthe functions of these three node types.

3.1 Element node

renderThe 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 renderis 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.

  1. Get node attribute data

    First judge plainwhether the attribute is true, if it is true, it means that the node has no attribute, and the value will databe assigned undefined; if not true, the function will be called genDatato obtain the node attribute datadata. genDataThe 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 genDataalthough the source code is very long, its logic is very simple. It is splicing strings, first dataassigning a value to it { , and then judging which attribute data exists, splicing these data into data, and finally adding one }, and finally getting all the attributes of the node data.

  2. Get the child node list children

    Obtaining the child node list childrenis actually traversing the elements in the ASTattribute children, and then generating different VNodecreation 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)
      }
    }
    
  3. 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 VNodecan 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()ASTexpressiontext

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 VNodecan 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. ASTWe 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 rendera function through a simple template.

Finally, we return to the source code and understand renderthe specific implementation process of the generating function by analyzing the source code.

Guess you like

Origin blog.csdn.net/weixin_46862327/article/details/131744331
Recommended