preface

Everybody is good, in an article to tell everyone about the Vue source learning (a) you don’t know – data reactive principle, reading is also a lot of, the novice deeply realized their learning enthusiasm, so today Lin three heart template compilation principle, continue to tell you about in order to consolidate the knowledge of this series of articles before, Every time I post the code, I will add it together, but I will only comment the new code, the code from the previous article will not comment, you can try to comment it yourself, it will solidify the knowledge.

Share a study method: you can follow the percussion, but to understand the percussion. Perhaps you can leave the comments alone, review them a few days later (or before the interview), and try to comment on what you understand. This will help solidify your knowledge of the source code

If you think the article is good, please click like, thank you!

code

1. The directory

For example, this observer folder is full of code from the previous article. You can check out “Vue Source Code Learning (1)” for what you don’t know – Principles of Data Responsiveness, this article. Also, the code from the previous article is not covered in this article and will not be commented on.

2. New a Vue instance

let vue = new Vue({
  render: h= > h(App)
}).$mount('#app')

console.log(vue)
Copy the code

Maybe many students usually use Vue-CLI development, and vue-CLI has very complete configuration for us, so we may ignore how the vue instance in the entry file goes to new, and how the DIV tag with THE ID of app is rendered to the page.

// This code is for demonstration purposes only
let vue = new Vue({
    el: '#app'.data() {
        return {
            a: 1.b: [1]}},render(h) {
        return h('div', { id: 'hhh' }, 'hello')},template: `
      `
}).$mount('#app')

console.log(vue)
Copy the code

For example, the code above, there is el, a template, render, have $mount, but rendering only render time, so, this a few things who have the power to render this time, or, who told the biggest power

Here’s a picture from the official website:

The above figure can be summarized as follows:

  1. To which root node to render to: determine whether the el attribute is available. If the el attribute is available, call $mount to get the root node
  2. Which template to render:
  • }}}}}}}}}}}}}}}}}}}}}}}}}}}}
  • No render:
    • Have template: take the template and parse it into the format required by the render function, and render it by calling the render function
    • No template: take the root el’s outerHTML and parse it into the format required by the render function, and render it using the render function call

3. Render mode: In any case, should we use the render function

3. Focus on implementation

  1. Implementation of the $mount function
  2. Parsing the template intoAbstract Syntax Tree (AST)
  3. willAbstract Syntax Tree (AST)Convert to render the required format

4. $mount function

The $mount function is important to determine whether the attributes are true or not, and to remember to return the Vue instance so that you can access the instance later

// init.js

const { initState } = require('./state')
const { compileToFunctions } = require('./compiler/index.js')

function initMixin(Vue) {
    Vue.prototype._init = function (options) {
        const vm = this

        vm.$options = options

        initState(vm)

        if(vm.$options.el) {
            vm.$mount(vm.$options.el)
        }
    }

    // Attach the $mount function to the Vue prototype
    Vue.prototype.$mount = function(el) {
        // Use the VM variable to get an instance of Vue (this)
        const vm = this
        // Get $options on the VM
        // $options is attached to the VM when _init
        const options = vm.$options

        // Get the incoming DOM
        el = document.querySelector(el)
        // el = {}

        // If options does not have the render function property
        if(! options.render) {// Get the template property in options
            let template = options.template


            // If the template property does not exist, but the DOM does
            if(! template && el) {// Assign the DOM's outerHTML to the template property
                template = el.outerHTML
            }

            // If there is a template property
            if (template) {
                Pass template to compileToFunctions and generate a render function
                const render = compileToFunctions(template)
                // Assign the generated render function to the options' render property
                options.render = render
            }
        }

        // Remember to return the Vue instance (this)
        $mount('#a'); // Let vue = new vue ().$mount('#a')
        return this}}module.exports = {
    initMixin: initMixin
}
Copy the code

5. com pileToFunctions function

The compileToFunctions are entry functions for template compilation, including the execution of parse and generate, and the return value is a render function

// compiler/index.js

const { parse } = require('./parse.js')
const { generate } = require('./codegen.js')

function compileToFunctions (template) {

    // Pass the template into the parse function and generate an abstract syntax tree (AST).
    // The abstract syntax number is a tree that describes the STRUCTURE of the DOM, including HTML, CSS, and JS code
    // Use the variable ast to receive the AST
    let ast = parse(template)

    // Pass the AST generated above into generate function
    // Generate a function code in render format
    / / format is probably similar _c (' div '{id: "app"}, _c (' div', undefined, _v (" hello "+ _s (name)), _c (' span, undefined, _v (" world"))))
    // _c for create element, _v for create text, _s for text json. stringify-- parses the object into text
    let code = generate(ast)


    // Use with to change the point of this to make it easier for code to retrieve data from this (i.e., the Vue instance)
    let renderFn = new Function(`with(this){return ${code}} `)

    // Return the generated render function
    return renderFn
}

module.exports = {
    compileToFunctions: compileToFunctions
}
Copy the code

6. Parse function (convert template to an abstract syntax tree)

  • First you need regular expressions that match various rules (start tags, end tags, curly braces, etc.)
  • CreateASTElement: Function that converts a node into an AST object
  • HandleStartTag: Function that handles the start tag
  • HandleEndTag: The function that handles the closing tag
  • HandleChars: Functions that process text nodes
  • Parse: the entry function for turning to the AST
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // The name of the matching tag is abc-123
const qnameCapture = ` ((? :${ncname}\ \ :)?${ncname}) `; // Match special tags such as ABC :234 before ABC: optional
const startTagOpen = new RegExp(` ^ <${qnameCapture}`); // The matching tag starts with the form 
const startTagClose = /^\s*(\/?) >/; // Match tag end >
const endTag = new RegExp(` ^ < \ \ /${qnameCapture}[^ >] * > `.'g'); // Match tag endings such as  to capture tag names inside
const attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /; // Match attribute like id="app"

// Global definitions
// root: stores the root node
CurrentParent: used to store a temporary node
let root, currentParent
// An array of temporary storage nodes
let stack = []

// The type of the element node is 1
const ELEMENT_TYPE = 1
// The type of the text node is 3
const TEXT_TYPE = 3


// Convert a node to the corresponding AST function
function createASTElement(tagName, attrs) {
    return {
        tag: tagName, / / tag name
        type: ELEMENT_TYPE, // Node type
        children: [].// An array of child nodes
        attrs, / / property
        parent: null / / the parent node}}function handleStartTag({ tagName, attrs }) {
    // The element is passed in as an AST object
    const element = createASTElement(tagName, attrs)
    if(! root) {// There can be only one root node
        root = element
    }

    // Assign a temporary parent to currentParent
    currentParent = element
    stack.push(element)
}

// Process the closing tag
function handleEndTag(tagName) {
    // The parent node corresponds to the parent node
    
    // So stack = [{div object}, {span object}]

    // So element is a {span object}
    const element = stack.pop()

    // currentParent is a {div object}
    currentParent = stack[stack.length - 1]

    if (currentParent) {
        element.parent = currentParent
        currentParent.children.push(element)
    }
}


// A function that handles text nodes
function handleChars(text) {
    // Remove whitespace
    text = text.replace(/\s/g.' ')
    if (text) {
        currentParent.children.push({
            type: TEXT_TYPE,
            text
        })
    }
}

function parse(html) {
    // The HTML is the template string passed in
    // Keep looping as long as the HTML has length
    while (html) {

        // Get the position of the character '<'
        const textEnd = html.indexOf('<')

        // If the position is 0, the start or end tag is encountered
        // For example 
      
or
if (textEnd === 0) { // Use parseStartTag to parse the start tag const startTagMatch = parseStartTag() // If the parser returns a value, it is the start tag if (startTagMatch) { // Pass the result of parsing to the handleStartTag function: the function that transfers the node to the AST handleStartTag(startTagMatch) // Skip this loop step continue } // If the above parsing does not return a value, the "note" may be the closing tag // The word "may" is used, because it could also be text, such as "< hahahahahahaha", which is also the first <, but it is not the beginning or the end tag // So use the ending tag re to determine if it is the ending tag const endTagMatch = html.match(endTag) // If it is the closing tag if (endTagMatch) { // Pass the parsing length into the advance function: advance function advance(endTagMatch[0].length) // Handle the closing tag handleEndTag(endTagMatch[1]) // Skip this loop step continue}}// Check the text node let text if (textEnd > 0) { // Delete the text text = html.substring(0, textEnd) } if (text) { // Advance the HTML string advance(text.length) // Process the text node handleChars(text) } } // Parse the function that starts the tag function parseStartTag() { // Match the start tag through the re const start = html.match(startTagOpen) let match // If the match is successful if (start) { match = { tagName: start[1].attrs: [] } advance(start[0].length) let end, attr // As long as no > is encountered and the tag has attributes, the parsing loop will continue while(! (end = html.match(startTagClose)) && (attr = html.match(attribute))) {// Advance the HTML string advance(attr[0].length) attr = { name: attr[1].value: attr[3] || attr[4] || attr[5] } match.attrs.push(attr) } if (end) { // If > is displayed, the start tag parsing is complete // HTML string advance 1 advance(1) // Return the parsed object match return match } } } // The function to advance the HTML string // For example
function advance(n) { html = html.substring(n) } Return the root node return root } module.exports = { parse } Copy the code

7. Generate (convert AST to render function format)

  • Match curly braces {{XXX}}
  • Make sure the AST parses into the format required by the Render function
const defaultTagRE = / \ {\ {((? :.|\r? \n)+?) \}\}/g; // Match the curly braces {{}} to capture what's inside the curly braces

function gen(node) {
    if (node.type === 1) {
        // Element node processing
        return generate(node)
    } else {
        // Text node processing
        const text = node.text

        // Check for curly braces {{}}
        if(! defaultTagRE.test(text)) {// If not, return _v and create a text node
            return `_v(The ${JSON.stringify(text)}) `
        }


        // Reset defaulttagre.lastIndex after each assignment
        // lastIndex is increable when the global g is added to the regular rule. For details, check the lastIndex after test is executed in the global G
        let lastIndex = (defaultTagRE.lastIndex = 0);
        const tokens = []
        let match, index

        while ((match = defaultTagRE.exec(text))) {
            // The text will always match as long as {{}} exists
            index = match.index
            if (index > lastIndex) {
                // Capture the text XXX in {{XXX}}
                tokens.push(JSON.stringify(text.slice(lastIndex, index)))
            }

            tokens.push(`_s(${match[1].trim()}) `)


            / / propulsion lastIndex
            lastIndex = index + match[0].length

        }

        // We have matched {{}}, but there is still text left, push it in
        if (lastIndex < text.length) {
            tokens.push(JSON.stringify(text.slice(lastIndex)))
        }

        // The return _v function creates a text node
        return `_v(${tokens.join('+')}) `}}// Generate the code of the render function format
function generate(el) {
    const children = getChildren(el)
    const code = `_c('${el.tag}',${el.attrs.length ? `${genProps(el.attrs)}` : "undefined"
        }${children ? `,${children}` : ""}) `;;
    return code
}

// The function that handles attrs
function genProps(attrs) {
    let str = ' '
    for (let i = 0; i < attrs.length; i++) {
        const attr = attrs[i]

        if (attr.name === 'style') {
            const obj = {}

            attr.value.split('; ').forEach(item= > {
                const [key, value] = item.split(':')
                obj[key] = value
            })
            attr.value = obj
        }
        str += `${attr.name}:The ${JSON.stringify(attr.value)}, `
    }
    return ` {${str.slice(0, str.length)}} `
}

// Get the child nodes and recurse gen
function getChildren(el) {
    const children = el.children
    if (children && children.length) {
        return `${children.map(c => gen(c)).join(', ')}`}}module.exports = {
    generate
}
Copy the code

The flow chart of 8.

conclusion

Some students may feel like some code appears very suddenly, it should be you did not see me before the “Vue source learning (a)” you do not know – data response principle, I suggest you a read in order to see, so THAT I can take you step by step to learn source code, hand knock source code. In the next post, I’m going to talk about how template rendering works.

If you feel learned something, please give me a praise, thank you!

reference

Sharky – How templates are compiled

Study group, touch fish, come in to talk and laugh

Please click the link here