[Vue Principle] Generation Node Data Mosaic of Compile-Source Edition

Keywords: Javascript Vue Attribute JSON

It's not easy to write articles. Give a compliment to your brother.


Focus on Vue source code sharing, the article is divided into vernacular version and source version, vernacular version helps to understand the working principle, source version helps to understand the internal details, let's learn together.
Research based on Vue version [2.5.17]

If you think the typesetting is ugly, please click on the link below or pull down to pay attention to the public number below.

[Vue Principle] Generation Node Data Mosaic of Compile-Source Edition

In the last article, we talked about the splicing of different nodes. What we need to record in detail is the splicing of node data.

Node data, including props, attrs, events, etc.

In the last article we saw in genElement, each node needs to splice the node data, using the method of genData in the source code below.

function genElement() {

    .....Handling other types of nodes
    var data = genData$2(el, state);    

    var children = genChildren(el, state);


    code = `_c('${el.tag}', $ {
        data ? ("," + data) : ''
    }, $ {
        children ? ("," + children) : ''
    })`

}

genData$2

The source code of this function is a bit long, but don't be afraid. It's all about the judgment of various attributes, so the content is about the same, but it involves specific methods, which will be looked at in detail.

Come on, go through it first.

function genData$2(el, state) {   



   var data = '{';   



   // First parse instructions

   var dirs = genDirectives(el, state);  



   // String of instructions parsed on stitching
   if(dirs) {
        data += dirs + ',';
   }   



   // Components with is bindings are not used directly

   if(el.component) {
        data += `tag: ${el.tag} , ` 
   }   



   // As mentioned in the previous article, dataGenFns contains functions for handling style and class.

   for(var i = 0; i < state.dataGenFns.length; i++) {
        data += state.dataGenFns[i](el);
   }   



   // All attributes

   if(el.attrs) {
        data += ` attrs:{ ${genProps(el.attrs)) } ,` 
   }  



   // essential attribute

   if(el.props) {
        data += ` domProps:{ ${genProps(el.props)} }, ` 
   }   



    // Event

   if(el.events) {
        data += genHandlers(el.events, false) + ",";
   } 


   // Primary events

   if(el.nativeEvents) {
        data += genHandlers(el.nativeEvents, true) + ",";
   }   



   // slot without scope

   if(
        el.slotTarget && !el.slotScope
    ) {
        data += ` slot: ${ el.slotTarget } ,` 
   }   



   // Scope slot

   if(el.scopedSlots) {
        data += genScopedSlots(el.scopedSlots, state) + ",";
   }   



   // Components use v-model

   if(el.model) {

        data += `model:{
            value:${el.model.value},
            callback:${el.model.callback},
            expression:${el.model.expression},
        },`
   }



   data = data.replace(/,$/, '') + '}';   



   return data

}

First, this method returns a serialized string of an object, such as

" { a:b , c:d } "

So braces are added at the beginning and the end, and then the attributes are spliced together in the form of xx:yy.

Now let's take a look at the processing of different attributes one by one.

Stitching instructions

function genDirectives(el, state) {    



    var dirs = el.directives;    



    if (!dirs) return

    var res = 'directives:[';    

    var hasRuntime = false;    

    var i, l, dir, needRuntime;    



    for (i = 0, l = dirs.length; i < l; i++) {


        dir = dirs[i];
        needRuntime = true;        



        // Processing methods for obtaining specific Vue instructions

        var gen = state.directives[dir.name];    

    

        // If this function exists, it proves that the instruction is internal.
        if (gen) {
            needRuntime = gen(el, dir);
        }        



        if (needRuntime) {


            hasRuntime = true;

            res += `{
                name: ${dir.name},
                rawName: ${dir.rawName}
                ${
                    dir.value
                    ? ",value:" + dir.value +", expression:" + dir.value    

                    : ''
                }
                ${ dir.arg ? ( ",arg:" + dir.arg ) : '' }
                ${
                    dir.modifiers
                    ? (",modifiers:" + JSON.stringify(dir.modifiers))
                    : ''
                }
            }, `

        }
    }    



    if (hasRuntime) {        

        return res.slice(0, -1) + ']'

    }
}

First, we need to understand what string this method will return, such as

It returns such a string.

`directives:[{
    name:"test",
    rawName:"v-test:a.b.c",
    value:222,
    expression:"arr",
    arg:"a",
    modifiers:{"b":true,"c":true}
}]`

Each instruction is parsed into an object string and then spliced into an array of strings.

So here's a detailed record of some possible confusions

state.directives in functions

In the CodegenState article above, we have written about this

state.directives is an array that contains the processing functions of the internal instructions of the Vue, as follows

v-on,v-bind,v-cloak,v-model ,v-text,v-html

The variable needRuntime in a function

A token indicating whether the instruction data needs to be parsed into an object string, like this

`{
    name:xxx, rawName:xxx,
    value:xxx, expression:xx,
    arg:xx, modifiers:xx
}`

That is, does this instruction need to be spliced into render strings?

So what commands do you need and what commands do you not need?

Custom instructions need to be parsed and spliced into render strings

But Vue's internal instructions, some useful, some unnecessary, so come up with a needRunTime to judge.

The instructions of Vue, first of all, get a specific processing method and assign it to gen.

When gen returns true after processing, render, false or no splicing is needed.

For example, data from v-model instructions need to be spliced together with render, while v-text and v-html do not.

Look at the following examples

For example, the template above spliced into the following string and found that v-html did not appear in the string array of directives.

`_c('div',{    

    directives:[{        

        name:"model",

        rawName:"v-model",        

        value:arr,

        expression:"arr"

    }],    

    domProps:{        

        "innerHTML":_s(<span></span>)

    }
})`

The variable hasRuntime in the function

A token indicating whether a return instruction string is required

genDirectives handles an array of instructions, and when the array is empty, there is no return value.

Then the render string will not have a directive string

If the instruction is not empty, hasRunTime is set to true, requiring a string to be returned

And add], at the end of the string, so that the string array is complete

Stitching Component

The parsing component here resolves the binding component with the is attribute

Simply, it's ok ay to stitch together the attributes of a tag.

Look at examples.

The original tag name is stitched behind the tag

` _c("test",{tag:"div"}) `

Stitching style

As mentioned in the previous article, state.dataGenFns is an array

Two functions are stored, one is parsing class, the other is parsing style.

It's very simple to put down the source code here.

Analyzing class

function genData(el) {    

    var data = '';    

    if (el.staticClass) {

        data += "staticClass:" + el.staticClass + ",";
    }   

    if (el.classBinding) {

        data += "class:" + el.classBinding + ",";
    }    

    return data

}

Analytical style

function genData$1(el) {    

    var data = '';    

    if (el.staticStyle) {

        data += "staticStyle:" + el.staticStyle + ",";
    }    

    if (el.styleBinding) {

        data += "style:(" + el.styleBinding + "),";
    }    

    return data

}

It's really too simple, just splicing a few attributes directly.

Just give me an example.

`_c('div',{
    staticClass:"a",
    class:name,
    staticStyle:{"height":"0"},
    style:{width:0}
})
`

Stitching attributes

Attribute splicing has only one function and the content is very simple.

function genProps(props) {    



    var res = '';   



    for (var i = 0; i < props.length; i++) {        

        var prop = props[i];


        res += prop.name + ":" + 
               prop.value + ",";
    }    



    return res.slice(0, -1)

}

As you can see, although there is only one way, in genData,, there are two kinds of splicing results

Stitching to el.attr

Stitching to el.props

Why do they splice together in different places?

Because it looks at where your attributes are placed.

If your attribute location is on the tag, it will be spliced into attr

If your attributes are located on the dom, they are spliced into the domProps.

For instance

For example, the following template, bbb is on the label, aaa is on the DOM

The result of splicing is

` _c('div',{
    attrs:{"bbb":"bbb"},
    domProps:{"aaa":11}
}) `

Page labels do not see aaa

aaa can be found in the dom attribute

Splicing events

Event splicing, a lot of content, intended to be placed in another article for detailed record

Event splicing can also be divided into two types: native events and custom events, which are just spliced into different strings, but the processing method is the same.

The method involves various modifiers. Haha, I want to know why I can write such a convenient api.

Bind keys to prevent default events. Just write this

@keyup.enter.prevent="xxx"

Welcome to the next article

Splicing Ordinary Slot

It's the property of slot spliced directly.

` _c('test',[_c('span',{
        attrs:{"slot":"name"},
        slot:"name"
    })]
) `

If the component has slot and there is no slot attribute, then slot will not be spliced, and the default name "default" will be given directly afterwards.

Splicing Scope Slot

function genScopedSlots(slots, state) {   



    return ` 
        scopedSlots:_u([${            

            Object.keys(slots).map(key =>{                

                return genScopedSlot(key, slots[key], state)
            })
            .join(',')
        }])
    `

}



function genScopedSlot(key, el, state) {  



    var fn = `
        function(${el.slotScope}){
            return ${
                el.tag === 'template' 
                ? genChildren(el, state)
                : genElement(el, state)
            }
        }
    `

    return `{ key:${key} , fn: ${fn} }` 

}

This function for dealing with scoped slot s seems a bit complicated, but it's actually a paper tiger.

Don't be afraid. Let's look at an example first.

It's spliced into strings, like this

`  
  _c('div',{
     scopedSlots:_u([{
         key:"heder",
         fn:function(arr){return _c('div')}
     }])
  })
`

This function traverses the el.scopeSlots array. Maybe you don't know what this array is about?

Again, here are two slot s.

After parse parsing, it becomes an ast, which is like this

{    

    tag:"test",    

    scopedSlots:[{        

        slotScope: "arr"

        slotTarget: ""a""
        tag: "div"
    },{        

        slotScope: "arr"

        slotTarget: ""b""
        tag: "div"
    }]
}

Yes, the scopedSlots array in the above object is traversed, and each item in the array is a separate slot.

genScopeSlot is then used to handle it separately, with the source code released on it.

After processing, a new array is formed, and genScopeSlot has nothing to say.

For splicing classification, it is necessary to determine whether the slot location label is template or not.

If it's a template, then his real slot is a child of the template and gets his child directly.

If it's not a template, it's a real slot.

Because template is a template node that comes with Vue, it does not exist.

Mosaic Component VModel

Yes, the model here is just a component v-model.

if (el.model) {

    data += `model: {
        value: $ { el.model.value },
        callback: $ { el.model.callback },
        expression: $ { el.model.expression },
    }, `

}

The official website said how to use this.

v-model on a component defaults to take advantage of prop named value and events named input

That is to say, the starting point is to pass a value to the component and bind an event input.

Nothing to say, just record how the v-model of the component is spliced together.

Example

After parse analysis, ast is obtained

{    

    tag: "test",    

    model:{        

        callback: "function ($$v) {num=$$v}"

        expression: ""num""
        value: "num"
    }
}

It's spliced into words and turned into strings.

` 
  _c('test',{
      model:{
          value:num,
          callback:function ($$v) {num=$$v},
          expression:"num"
      }
  })
`

Take a chestnut

Attribute splicing, let's finish. Finally, let's take an example.

Here's the template. Let's stitch it together.

Resolve it into the render string below, and after reading it, you will have mastered the content of generation.

Later you can see the packaged code written by someone else in Vue.

Even, you can restore it manually. If you are idle, you can write a method by yourself, pass in the render string, and automatically restore it to the template.

` _c('div', {
    attrs: {
        "b": "2"
    },
    domProps: {
        "a": 11
    }
},[
    _c('test', {

       scopedSlots: _u([{
          key: "a",
          fn: function(arr) {
              return _c('strong')
          }
      }]),

      model: {
          value: (num),
          callback: function($$v) {
              num = $$v
          },
          expression: "num"
      }

    },[_c('span')])
]) `

Last

In view of my limited ability, there will inevitably be omissions and errors. Please forgive me. If there are any improper descriptions, please contact me in the background and get the red envelope.

Posted by searain on Mon, 12 Aug 2019 23:10:59 -0700