preface

HTML template -> AST tree structure -> Render Function = code string + with + new Function -> virtual DOM (vnode) -> produce real DOM this chapter project address

expected

Compile the following template

<div id="app" style="font-size:17px; background:gray;">
    <span>hello {{ arr }} world</span>
</div>

<script>
    var vm = new Vue({
        data: {
            message: 'hello Vue'
        }
    })

    vm.$mount('#app')
</script>
Copy the code

Compile the stages of rendering

  • Ast tree structure (syntax level description JS CSS HTML)

  • Render function (to generate the virtual DOM)

  • vnode

To the chase

Method compileToFunction (compiler initialization)

import { parserHTML } from './parser'
import { generate } from './generate'

/ * * *@description Compile to render function *@description HTML template -> AST tree structure -> render Function = code string + with + new Function -> virtual DOM (vnode) -> produce real DOM */
export function compileToFunction(template) {
    /** AST tree structure */
    let root = parserHTML(template)
    
    /** render string */
    let code = generate(root)

    /** render function */
    let render = new Function(`with(this){return ${code}} `)

    return render
}
Copy the code

ParserHTML file (AST syntax level description JS CSS HTML -> vDOM)

/ * * *@description Ast syntax level description JS CSS HTML -> vDOM */

/** Label name */
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
/** The index after the match of the tag name is 1 */
const qnameCapture = ` ((? :${ncname}\ \ :)?${ncname}) `;
/** Matches the start tag */
const startTagOpen = new RegExp(` ^ <${qnameCapture}`);
/** Matches the closing tag */
const endTag = new RegExp(` ^ < \ \ /${qnameCapture}[^ >] * > `);

Attribute Example: A =b A ='b' a="b" */
const attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /;
/** Matches the closing character of the start label such as > /> */
const startTagClose = /^\s*(\/?) >/;

export function parserHTML(html) {
    /** Assemble ast tree structure */
    function createAstElement(tagName, attrs) {
      return {
          tag: tagName,
          type: 1.children: [].parent: null,
          attrs
      }
    }

    /** Core method */
    // The AST tree structure to return
    let root = null;
    // It is used to find the parent tag and is deleted when the tag is finished compiling
    let stack = [];
    
    /** Tags start by assembling the tree structure */
    function start(tagName, attributes) {
      let parent = stack[stack.length - 1];
      let element = createAstElement(tagName, attributes);
      if(! root) { root = element; }if(parent){
          element.parent = parent.tag
          parent.children.push(element)
      }
      stack.push(element);
    }

    /** text */
    function chars(text) {
      text = text.replace(/\s/g."");
      let parent = stack[stack.length - 1];
      if (text) {
          parent.children.push({
              type: 3,
              text
          })
      }
    }

    /** When the label ends */
    function end(tagName) {
      let last = stack.pop();
      if(last.tag ! == tagName) {throw new Error('Mislabeled'); }}/** Intercepts the compiled string */
    function advance(len) { html = html.substring(len) }
    
    /** Parse the starting tag */
    function parseStartTag() {
        const start = html.match(startTagOpen);
        if (start) {
            const match = {
                tagName: start[1].attrs: []
            }
            advance(start[0].length);
            let end;
            let attr;

            // Parse attributes
            while(! (end = html.match(startTagClose)) && (attr = html.match(attribute))) { match.attrs.push({name: attr[1].value: attr[3] || attr[4] || attr[5] })
                advance(attr[0].length)
            }

            if (end) { advance(end[0].length) }

            return match
        }
        return false
    }

    while (html) {
        let textEnd = html.indexOf('<')
        // When the tag starts
        if (textEnd == 0) {
            const startTagMatch = parseStartTag(html); // Parse the start tag
            if (startTagMatch) {
                start(startTagMatch.tagName, startTagMatch.attrs)
                continue;
            }
            const endTagMatch = html.match(endTag);
            if (endTagMatch) {
                end(endTagMatch[1]);
                advance(endTagMatch[0].length);
                continue; }}// The text in the middle of the tag
        let text
        if (textEnd > 0) { text = html.substring(0, textEnd) }
        if (text) {
            chars(text)
            advance(text.length)
        }
    }

    return root
}

Copy the code

Generate file (render function string)

/** Matches {{aaaa}} */
const defaultTagRE = / \ {\ {((? :.|\r? \n)+?) \}\}/g

/ * * *@description Compile properties [{name: 'XXX' value: 'XXX'}, {name: 'XXX' value: 'XXX'}] *@description Expect {XXX: 'XXX'} */
function genProps(attrs) {
    let str = ' ';
    for (let i = 0; i < attrs.length; i++) {
        let attr = attrs[i];
        if (attr.name === 'style') {
            let styleObj = {};
            attr.value.replace(/([^;:]+)\:([^;:]+)/g.function() {
                styleObj[arguments[1]] = arguments[2]
            })
            attr.value = styleObj
        }
        str += `${attr.name}:The ${JSON.stringify(attr.value)}, `
    }
    return ` {${str.slice(0, -1)}} `
}

/** Core method */
/ * * *@description Recursively generate and retrieve text */
function gen(el) {
    if (el.type == 1) {
        return generate(el);
    } else {
        let text = el.text;
        if(! defaultTagRE.test(text)) {return `_v('${text}') `;
        } else {
            let tokens = [];
            let match;
            G of the re conflicts with exec. Set it to 0 or retrieve it once
            let lastIndex = defaultTagRE.lastIndex = 0;
            while (match = defaultTagRE.exec(text)) {
                let index = match.index
                if (index > lastIndex) {
                    tokens.push(JSON.stringify(text.slice(lastIndex, index)))
                }
                tokens.push(`_s(${match[1].trim()}) `)
                lastIndex = index + match[0].length
            }

            if (lastIndex < text.length) {
                tokens.push(JSON.stringify(text.slice(lastIndex)))
            }

            return `_v(${tokens.join('+')}) `}}}function genChildren(el) {
    let children = el.children
    if (children) {
        return children.map(c= > gen(c)).join(', ')}return false
}

/ * * *@description Convert the AST tree structure to the render function string *@description _c('div',{id:'app',a:1},_c('span',{},'world'),_v())
 */
export function generate(el) {
    let children = genChildren(el)
    let code = `_c('${el.tag}',${ el.attrs.length? genProps(el.attrs): 'undefined'}${children? `,${children}`:' '}) `
    return code
}
Copy the code

Render function vnode

import { createElement, createTextElement } from './vdom/index'

export function renderMixin(Vue){
    Vue.prototype._c = function() {
        return createElement(this. arguments) } Vue.prototype._v =function(text) {
        return createTextElement(this,text)
    }

    Vue.prototype._s = function(val) {
        if(typeof val == 'object') return JSON.stringify(val)
        return val;
    }

    Vue.prototype._render = function(){
       const vm = this
       let render = vm.$options.render
       let vnode = render.call(vm)
       return vnode
    }
}
Copy the code
import { isObject, isReservedTag } from '.. /utils'

/ * * *@description Create a vnode */ for the label
export function createElement(vm, tag, data = {}, ... children) {
    // Tag is not a component
    if (isReservedTag(tag)) {
        return vnode(vm, tag, data, data.key, children, undefined)}else {
        const Ctor = vm.$options.components[tag]
        return createComponent(vm, tag, data, data.key, children, Ctor)
    }

}

/ * * *@description Create a vNode */ for the component
function createComponent(vm, tag, data, key, children, Ctor) {
    // The component is not a constructor
    if (isObject(Ctor)) {
        Ctor = vm.$options._base.extend(Ctor)
    }

    data.hook = {
        init(vnode) {
            let vm = vnode.componentInstance = new Ctor({_isComponent: true})
            vm.$mount()
        }
    }

    return vnode(vm, `vue-component-${tag}`, data, key, undefined.undefined, {Ctor, children})
}

/ * * *@description Create text vnode */
export function createTextElement(vm, text) {
    return vnode(vm, undefined.undefined.undefined.undefined, text)
}

/** Core method */
/ * * *@description Suit vnode * /
function vnode(vm, tag, data, key, children, text, componentOptions) {
    return {
        vm,
        tag,
        data,
        key,
        children,
        text,
        componentOptions
        / /...}}Copy the code
/ * * *@description Is the object */
export function isObject(data) {
    return typeof data === 'object'&& data ! = =null
}

/ * * *@description Is it a native label */
export function isReservedTag(str) {
    let reservedTag = 'a,div,span,ul,li,p,img,button'
    return reservedTag.includes(str)
}
Copy the code

After the

Next chapter Vue2 Core Principles (easy) – View update (first render) notes