preface

  • Article ideas article and implement article 】 【 】, in this paper, for the second edition, at the end of the article has a link to the first edition, synchronous read advice to see two window, or please read – “the Vue | article | implementation ast

Implement the Template to generate the render function

The target

{{name}} 111 =>>
/ / {
// tag: 'div',
// parent: null,
// attrs: [],
// children: [{
// tag: null,
/ / parent: parent div,
// text: name {{name}}"
/ /}, {
// tag: 'span',
/ / parent: parent div,
// attrs: [],
// children: [{
// tag:null,
/ / parent: parent div,
// text: 111
/ /}]
/ /}]
// }
Copy the code

Define the regular

const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; / / to a-z a-z _ at the beginning Behind can connect \ - \. 0-9 _a - zA - regular basis of Z] / /, matching tag name:
const qnameCapture = ` ((? :${ncname}\ \ :)?${ncname}) `; // Named re is used to match situations like 
      
const startTagOpen = new RegExp(` ^ <${qnameCapture}`); // The re at the beginning of the tag captures the tag name
const endTag = new RegExp(` ^ < \ \ /${qnameCapture}[^ >] * > `); // Match tags like  at the end
const attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /; // Match attributes
const startTagClose = /^\s*(\/?) >/; // Match tag end >
const defaultTagRE = / \ {\ {((? :.|\r? \n)+?) \}\}/g   // Matches the mustatine syntax
Copy the code

Generate ast syntax trees from HTML strings

Iterates through the HTML string to get tag names, attributes, content, and so on
compiler/index.js
export function compilerToFunction(template){
    let ast = parseHTML(template);
}
function parseHTML(html) {... }Copy the code

Define a “forward” function that intercepts HTML

 // Truncate the string and update the string
    function advance(n) {
        html = html.substring(n)
    }
Copy the code

The re extracts valid information, then intercepts the original HTML string and continues the loop until the HTML is empty

  • Handling start tags<div id="11">
    • Processing label name
    • Processing properties
function parseHTML(html) {
     while (html){
        // 1. Tags that start with < must be tags
        let textEnd = html.indexOf('<');
        if(textEnd == 0) {// Process the label name
            const startTagMatch = parseStartTag();
            if(startTagMatch){
                // Pass the information to the corresponding handler
                start(startTagMatch.tagName,startTagMatch.attrs);
                continue; }... }}function parseStartTag(){
        // match match returns object text end extension
        const start = html.match(startTagOpen);
        if(start){
            const match = {
                tagName: start[1].attrs:[]
            }
            advance(start[0].length);
            let attr,end;
            // 1. Not the end (judge by startTagClose) 2. If an attribute exists, the attribute is processed and the matched attribute is passed to the attrs of the return object
            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);
            }
            // Attribute processing completes the loop, intercepting the string and returning the retrieved information
            if(end){
                advance(end[0].length);
                returnmatch; }}}... }Copy the code
  • Handling closing tags

function parseHTML(html) {
    // debugger;
    while (html){
        // 1. Tags that start with < must be tags
        let textEnd = html.indexOf('<');
        if(textEnd == 0){
            ...
            // The end tag intercepts the string and passes the information to the corresponding handler
            const endTagMatch = html.match(endTag);
            if (endTagMatch) {
                advance(endTagMatch[0].length);
                end(endTagMatch[1]);// Pass in the end tag
                continue; }}... }}Copy the code
  • Handling text

function parseHTML(html) {
    // debugger;
    while (html){
      ...
        let text;
        // If it is text, get text
        if(textEnd >= 0){
            text = html.substring(0,textEnd);
        }
        // If text exists, the string is intercepted and the obtained information is passed to the corresponding handler
        if(text){ advance(text.length); chars(text); }}}Copy the code
  • Corresponding information processing function
function start(tagName,attrs) {
console.log(tagName,attrs,"====== Start tag properties");
}
function end(tagName){
console.log(tagName,"====== End tag");
}
function chars(text){
    console.log(text,"= = = = = = text");
}
Copy the code

Tree + stack data structure generates ast tree

Generate a tree structure

Process and obtain information to generate ast tree

By a root node continuously divergent, and the subscale (leaf structure) the same, associative tree structure

Note that this scenario fits well with a data structure — a tree — that diverges from a root node and has the same subscale (leaf structure); We can use a tree structure to describe the acquired information, that is, generate an AST tree according to the template

compiler/index.js
Define tree cell structure:
  • The label corresponding{tag: tagName, // tag type: 1, // tag type: 1, // children:[], // attrs, // attribute set parent:null // parent element};
  • Text corresponding{type:3,text:text}
    // The tag element generates an AST tree element
    function createASTElement(tagName,attrs) {
        return {
            tag: tagName, / / tag name
            type: 1.// Label type
            children: [].// List of children
            attrs, // Attribute collection
            parent:null // Parent element}}// Store text directly into this structure when processing it
	{
        type: 3,
        text
    }
Copy the code
Generate corresponding tree units according to the obtained information
  • The corresponding AST element is created in the start handler
  // Information processing functions
    function start(tagName,attrs) {
        console.log(tagName,attrs,"====== Start tag properties");
        let element = createASTElement(tagName,attrs);
        if(! root){ root = element } currentParent = element; stack.push(element) }Copy the code
Using the stack structure record processing label process, the label information object is pushed into the stack when processing the start label, and when processing the close label
   function end(tagName){
        console.log(tagName,"====== End tag");
       // The label's parent information object is added to the stack.
        let element = stack.pop();
       // The child structure is constructed by storing the children attribute of the top element
        currentParent = stack[stack.length - 1];
        // Record the parent of the tag when the tag is closed
        if(currentParent){
            element.parent = currentParent;
            currentParent.children.push(element);
        }
       // Finally, check whether the stack is empty at the end of the processing. If it is not empty, it indicates an exception
    }
Copy the code
Finally, return to root and the AST tree is complete

Generate the render function based on the AST

The AST can convert strings by deep traversal and then string concatenation

The entry function accepts the root and returns a string
"_c('div','{... } ', '[]')"
Copy the code
compiler/generate.js
export function generate(el) {
    let children = genChildren(el);
    let code = `_c('${el.tag}',
    ${el.attrs.length ? `${genProps(el.attrs)}` : 'undefine'}
    ${
        children ? `,${children}`: ' '
    }) `;
    return code;
}
Copy the code
Recursively process the child nodes
  • If it is a tag element, call generate directly
  • If it’s text, it’s processed{{}}Variable problem, call _s method to convert variable to string
const defaultTagRE = / \ {\ {((? :.|\r? \n)+?) \}\}/g   / / mustatine syntax
function genChildren(el) {
    const children = el.children;
    if(children){
        return children.map(child= > gen(child)).join(', '); }}function gen(node) {
    if(node.type == 1) {return generate(node);
    }else {
        let text = node.text;
        if(! defaultTagRE.test(text)){// If it is plain text
            return `_v(The ${JSON.stringify(text)}) `
        }else {
            // Store each section of code
            let tokens = [];
            let lastIndex = defaultTagRE.lastIndex = 0; // If the re is in global mode, set the index to 0 before each use
            let match,index;
            while (match = defaultTagRE.exec(text)) {
                index = match.index;// Save the matched 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('+')}) `; }}}Copy the code
  • Processing properties
function genProps(attrs) {
    console.log(attrs);
    let str = "";
    for (let i = 0; i < attrs.length; i++) {
        let attr = attrs[i];
        if (attr.name === 'style') {
            let obj = {};
            attr.value.split('; ').forEach(item= > {
                let [key,value] = item.split(':');
                obj[key] = value;
            })
            attr.value = obj;
        }
        str += `${attr.name} : The ${JSON.stringify(attr.value)}, `

    }
    console.log(str);
    return ` {${str.slice(0, -1)}} `;
}
Copy the code

New Function lets you convert a string to a Function and with lets you specify a global variable within a Function

compiler/index.js
let code = generate(ast);
let render = new Function(`with(this){return ${code}} `);
Copy the code
The last

Mount render before the user delivers it and reuse the patch logic

At this point, the AST is complete

Finally realize

  • Warehouse address: [email protected]: Sympath/blingSpace. Git
  • Direct access address: github.com/Sympath/bli…

A link to the

  • Vue implemented – ast | | the first edition piece render function
  • Vue | | article implementation ast