start

The previous section summarized the principles of reactive data in Vue, so here’s a summary of template compilation in Vue. There are many and complex scenarios for template compilation. Now I only learn the parsing and compilation of common tags, but fail to conduct in-depth study and summary of components, instructions, events and other situations.

Template compilation

The basic flow

  • Parsing the template code generates an AST syntax tree, which relies heavily on regex.

  • Generate code from the AST syntax tree.

   with(this){ 
     return _c("div",{id:"app"},_c("div",{class:"content"},_v("Name:"+_s(name)),_c("h5",undefined,_v("Age:"+_s(age)))),_c("p",{style:{"color":"red"}},_v("Static node")))}Copy the code
  • Generate an executable render function
    (function anonymous( ) {
     with(this){ 
     return _c("div",{id:"app"},_c("div",{class:"content"},_v("Name:"+_s(name)),_c("h5",undefined,_v("Age:"+_s(age)))),_c("p",{style:{"color":"red"}},_v("Static node"}}))))Copy the code

Generate AST syntax tree

The code location is parser. Js in complierCopy the code

I mainly rely on regular analysis (my regular is very poor, it is difficult to understand, and then further study it, directly copy the Everest architecture Teacher Jiang Wen)

Implementation steps

  • Parsing begins label first Such as < div id = ‘app’ > = {tagName: ‘div’, attrs: [] {id: app}}

    ParseStartTag [1: < 2: div 3: id=’app’ 4: >]

  • Parsing child node labels (recursive)

  • Note: after parsing the start node, it pushes the node onto the stack. After parsing the end node, it pushes the start node off the stack. In this case, the last point of the stack is the parent node of the current node.

    For example, [div,p] is parsed to

    at which point [div] gets p, and p is inserted into the child node of div by fetching the end of the stack.

import {extend} from '.. /util/index.js'/ / letter a - zA - Z_ - an array of lowercase letters Capital letters const ncname = ` [a zA - Z_] [\ \ - \ \. 0-9 _a - zA - Z] * `; // Label name //? <aaa:aaa> const qnameCapture = '((? :${ncname}\ \ :)?${ncname}) `; Const startTagOpen = new RegExp(' ^<)${qnameCapture}`); Const endTag = new RegExp(' ^<\\/${qnameCapture}[^ >] * > `); // Matches </div> // <div aa = at the end of the tag"123"  bb=123  cc='123'/ / the arguments capture the attribute name and attribute value [1] | | the arguments [2] | | the arguments [2] const attribute = / ^ \ s * ([^ \ s"' < > \ / =] +)? :\s*(=)\s*(? :"([^"] *)"+|'([^'] *)'+|([^\s"'= < > `] +)))? /; / / match attribute / / < div > < br / > const startTagClose = / ^ \ s * (\ /?) > /; // Match tag end > // match dynamic variable +? Match {{}} const defaultTagRE = /\{\{((? :.|\r? \n)+?) \}\}/g; constforIteratorRE = /,([^,\}\]]*)(? : ([^, \} \]] *))? $/ constforAliasRE = /([\s\S]*?) \s+(? :in|of)\s+([\s\S]*)/;
const stripParensRE = /^\(|\)$/g;
const ELEMENT_NDOE='1';
const TEXT_NODE='3'
export functionParseHTML (HTML) {console.log(HTML) // AST tree represents the syntax of HTMLletroot; / / the rootslet currentParent;
    letelementStack = []; Ast / / / * * * * @ syntax elements param {*} tagName * @ param attrs {*} * /function createASTElement(tagName,attrs){
        return{tag:tagName, // tag attrs, // attribute children:[], // child attrsMap: makeAttrsMap(attrs), parent:null, // parent nodetype:ELEMENT_NDOE // Node type}} // console.log(HTML)functionStart (tagName, attrs) {// Create a nodelet element=createASTElement(tagName,attrs);
        if(! root) { root=element; } currentParent=element; //processFor(element); elementStack.push(element); // We can guarantee that the parent is the previous one}functionEnd (tagName) {// End tag // Last element out of the stacklet element=elementStack.pop();
        letparent=elementStack[elementStack.length-1]; // The node is inconsistent, and an exception is thrownif(element.tag! ==tagName) { throw new TypeError(`html tag is error${tagName}`);

        }
        if(parent) {// The child's parent points to element.parent=parent; // Add child elements to parent.children.push(element); } /** * parses to text * @param {*} text */functionChars (text) {// text // parse to text text=text.replace(/\s/g,' '); / / to add text to the current element currentParent. Children. Push ({type:TEXT_NODE, text})} </span></div>while(HTML) {// If it is an HTML taglet textEnd = html.indexOf('<');
        if (textEnd == 0) {
            const startTageMatch = parseStartTag();

            if(startTageMatch) {/ / tags start (startTageMatch. TagName, startTageMatch attrs)} const endTagMatch = HTML. The match (endTag);if(endTagMatch) { advance(endTagMatch[0].length); End (endTagMatch[1])} // End tag} // If it is not 0, it is textlet text;
        if(textEnd > 0) { text = html.substring(0, textEnd); Chars (text); }if(text) { advance(text.length); // Delete text content}}functionadvance(n) { html = html.substring(n); } /** * parsing the start tag * <div id='app'> ={ tagName:'div',attrs:[{id:app}]}
     */

    function parseStartTag() { const start = html.match(startTagOpen); // Match the start tagif(start) {const match = {tagName: start[1], attrs: []} advance(start[0].length);letend, attr; // Start matching attributes if no tag closure is matched and the attribute is matched to the tagwhile(! (end = html.match(startTagClose)) && (attr = html.match(attribute))) { advance(attr[0].length); match.attrs.push({ name: attr[1], value: attr[3] || attr[4] || attr[5] }) }; // Matches the closed labelif (end) {
                advance(end[0].length);
                returnmatch; }}}return root;

}

Copy the code

Convert the AST syntax tree into code

Such as: return _c (” div “, {id: “app”}, _c (” div “, {class: “content”}, _v (” name: “+ _s (name)), _c (h5, undefined, _v (” age: “+ _s (age)))), _c (” p”, {style: {” color “:” red “}}, _v (” static node “)

Where: _c is to create common nodes,_v is to create text points, _s is to be changed from the data value (processing template {{XXX}})

Finally, the string code is returned.

Every normal node generates _c(‘ label name ‘,{attribute}, children (_v text,_c(normal child node))) because it is a tree line structure, recursive nesting is required

const defaultTagRE = /\{\{((? :.|\r? \n)+?) \}\}/g // Matches {{}} /** * attribute * @param {*} attrs */function genProps(attrs){
    let str=' ';
    for(leti=0; i<attrs.length; i++) {letattr=attrs[i]; @click v-model = @click v-model = @click v-model'style',
        //     value:'color:red; border:1px'
        // }
        if(attr.name==='style')
        {
             let obj={};
             attr.value.split('; ').forEach(element => {
                 let [key=' ',value=' ']= element.split(':');
                 obj[key]=value;

             });
             attr.value=obj;
        }
       
       str+=`${attr.name}:${JSON.stringify(attr.value)}`; }return` {${str.slice(0,-1)}} `; }functionGen (el){// or element nodeif(el.type==='1')
    {
         return generate(el);
    }
    else{
        let text=el.text;
        if(! text)return; // a parseif(defaultTagRE.test(el.text))
        {
            defaultTagRE.lastIndex=0
            letIndex =0, match=[], result=[];while(match=defaultTagRE.exec(text)){ index=match.index; // add result.push(' to bb{{aa}}${JSON.stringify(text.slice(lastIndex,index))}`); // Add matching result.push(' _s(${match[1].trim()}) `); lastIndex = index + match[0].length; console.log(lastIndex); } // Example: 11{{sd}}{{SDS}}23 At this point, 23 is not addedif(lastIndex<text.length)
          {
              //result.push(`_v(${JSON.stringify(text.slice(lastIndex))}) `); result.push(JSON.stringify(text.slice(lastIndex))); } console.log(result); / / returnreturn `_v(${result.join('+')}} // No variableselse{
          return `_v(${JSON.stringify(text)}}}} // Three parts tag, attribute, childexport function generate(el){
    letchildren = genChildren(el); // Generate a child stringlet result = `_c("${el.tag}",${
            el.attrs.length? `${genProps(el.attrs)}`  : undefined
        }${
            children? `,${children}` :undefined
        })`;
   
    return result;
}

Copy the code

Generating the render function

    let astStr=generate(ast);
    let renderFnStr = `with(this){ \r\nreturn ${astStr} \r\n}`;
    let render=new Function(renderFnStr);
    return render;
Copy the code

DOM rendering

The basic flow

  • Call the render function to generate the virtual DOM
  • Real DOM generation for the first time
  • Update DOM, through the DIff algorithm to achieve the DOM update. (Summary later)

Generating the virtual DOM

  • There are _c(create normal nodes),_v(create text nodes),_s(process {{XXX}}) methods in the render function, which needs to be implemented in render.js. All methods are mounted on the prototype of Vue.
/ / code position render. Js import {createElement method, createNodeText} the from'./vdom/create-element.js'
export functionRenderMixin (Vue){// Create a node vue.prototype. _c=function() {returncreateElement(... arguments); } // Create a text node vue.prototype. _v=function(text){
        return createNodeText(text);

    }
    Vue.prototype._s=function(val){
        return val===null?"":(typeof val==='object'? JSON.stringify(val):val); } // Method to generate virtual nodes vue.prototype. _render=function(){ const vm=this; // This is the render function const {render}=vm generated in the previous section.$options; / / executionlet node=render.call(vm);
        console.log(node);
    
        returnnode; }} // code location vom/create-element.js /** * Create node * @param {*} param0 */export functioncreateElement(tag,data={},... children){returnvNode(tag,data,data.key,children,undefined); } /** * text node * @param {*} text */export function createNodeText(text){
    
    console.log(text);
    return, vNode (undefined undefined undefined undefined, text)} / * * * * / virtual nodefunction vNode(tag,data,key,children,text){
      return {
           tag,
           data,
           key,
           children,
           text

      }
}
Copy the code
  • Data brokers

    Render function with(this){todo XXX}

    The with statement was originally intended to provide a namespaces style shorthand for hierarchical object access. That is, in the specified code area, the object is called directly by the node name. This in with is the instance VM of Vue. But the reactive data we got in the previous section is all in vm._data, so we need to implement vm.age to get vm._data.age, so we need a proxy. There are two scenarios for implementing an agent

    • Object.defineProperty(Source code used)
    • __defineGetter__ and __defineSetter__
    / / state. In jsfunction initData(vm){
        const options=vm.$options;
        if(options.data) {// Get the return value of the function's execution if data is a functionlet  data=typeof options.data==='function'? (options.data).call(vm):options.data; vm._data=data;for(let key in data)
            {
                proxy(vm,'_data',key)} observe(data)}} // proxyfunction proxy(target,source,key){
        Object.defineProperty(target,key,{
             get() {return target[source][key]
    
             },
             set(newValue){
                target[source][key]=newValue; }})}Copy the code

    Real DOM generation

    patch.js

    /** * create element * @param {*} vnode */function createElement(vnode){
        let {tag,data,key,children,text}=vnode;
        if(typeof tag==='string')
        {
            vnode.el=document.createElement(tag);
            updateProps(vnode);
            children.forEach(child => {
                if(child instanceof Array) { child.forEach(item=>{ vnode.el.appendChild(createElement(item)); })}else{ vnode.el.appendChild(createElement(child)); }}); }else{
            vnode.el=document.createTextNode(text);
    
        }
        return vnode.el;
    
    }
    
    /**
     * jiu
     * @param {*} vnode 
     * @param {*} oldNode 
     */
    
    function updateProps(vnode,oldProps={}){
        let {el,data}=vnode;
        for(let key inOldProps) {// Old and new without deletingif(! data[key]) { el.removeAttribute(key); } } el.style={};for(let key in data)
        {
            if(key==='style')
            {
                for(let styleName indata[key]) { el.style[styleName]=data[key][styleName]; }}else{ el.setAttribute(key,data[key]); }}}Copy the code

The end of the

So far we have implemented template compilation in Vue, virtual DOM and real DOM generation, but not DOM mount because of the life cycle and dependency collection involved, which we continue to summarize in the next section.