Background on CodeGen

In vUE compiler, the VUE syntax we wrote was first converted into ast syntax tree.

Suppose we write a piece of code like this:

<div id="app" a="111" style="color: red; background: green;">
    hello{{arr}}word
</div>
Copy the code

Compiler is parseHTML parsed into the following AST syntax tree:

{
	"tag": "div"."type": 1."children": [{
		"type": 3."text": "hello{{arr}}word"}]."parent": null."attrs": [{
			"name": "id"."value": "app"
		},
		{
			"name": "a"."value": "111"
		},
		{
			"name": "style"."value": "color: red; background: green;"}}]Copy the code

What CodeGen does is convert such an AST syntax tree into a Render string. That is, strings starting with _c, _S, and _v. If you cast the ast above, the result would be the following string:

_c('div', {id:"app",a:"111",style:{"color":" red","background":" green"}}),_v("hello"+_s(arr)+"word")
Copy the code

The AST describes only the syntax, whereas the Render string is used to describe the Dom, which extends properties.

Dinner: CodeGen code writing and analysis

One sentence sums up how CodeGen is implemented: you process the AST syntax, loop through it, and concatenate strings over and over until you get the desired result.

Before we can formally analyze the code, we need to know what _c, _S, and _v stand for:

_c: createElement, create the virtual Dom.

_v: create string

_s: handles variables in template syntax {{}}

When CodeGen receives json from ast, the code looks like this:

function generate(el) {
    console.log('-- -- -- -- -- -- -- -- -- -- -- -- -- -- --', el)
    let children = genChildren(el)
    // Iterate over 🌲, concatenating 🌲 into a string
    let code = `_c('${el.tag}', ${el.attrs.length ? genProps(el.attrs) : 'undefined'
        })${children ? `,${children}` : ' '}`
    return code
}

Copy the code

The above code actually does one thing:

Attributes are processed

// "attrs": [{
// "name": "id",
// "value": "app"
/ /},
/ / {
// "name": "a",
// "value": "111"
/ /},
/ / {
// "name": "style",
// "value": "color: red; background: green;"
/ /}
// ]

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 () {
                console.log(arguments[1].arguments[2])
                styleObj[arguments[1]] = arguments[2]
            })
            attr.value = styleObj
        }
        str += `${attr.name}:The ${JSON.stringify(attr.value)}, `
    }
    return ` {${str.slice(0, -1)}} `
}
Copy the code

As you can see, the incoming parameter is an array of attR, and the operation on the array is very simple, just loop through and concatenate. You get the following string.

{id:"app",a:"111",style:"color: red; background: green;" }Copy the code

Color: red; color: red; background: green;” “, so when we process it, we want it to look like this:

style:{"color":" red","background":" green"}
Copy the code
if (attr.name === 'style') {
    let styleObj = {}
    attr.value.replace(/ / ^ :; : (+) / ^ :; +)/g.function () {
        console.log(arguments[1].arguments[2])
        styleObj[arguments[1]] = arguments[2]
    })
    attr.value = styleObj
}
Copy the code

Extra meal: regular small class

/ / ^ :; : (+) / ^ :; The () in the +)/g re represents grouping ([^:;] ) : [[^ :;] Grouping) represented by: [] represents a + represents any number of invert/g ^ representative on behalf of the whole match So analysis is the mean (any number of [not: or; character]) : (any number of characters] [not: or;)"color: red; background: green;"After being matched by the re, we get the following result: color red background green and then replace with an object by replace to get the final resultCopy the code

Treat children

function genChildren(el) {
    let children = el.children
    if (children) {
        return children.map(c= > gen(c)).join(', ')}}function gen(el) {
    let text = el.text
    if (el.type === 1) { // element: 1 text: 3
        return generate(el)
    } else {
        if(! defaultTagRE.test(text)) {return `_v('${text}') `
        } else {
            / / split
            let tokens = []
            let match;
            let lastIndex = defaultTagRE.lastIndex = 0;
            while (match = defaultTagRE.exec(text)) {
                // See if there is a match
                let index = match.index // start indexing
                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)))
            }

            console.log('tokens', tokens)

            return `_v(${tokens.join('+')}) `}}}Copy the code

{
	"children": [{
		"type": 3."text": "hello{{arr}}word"}}]Copy the code

The logic here is:

1. If children’s type is 1, it means that this is an element

2. If the type of children is 3, only _v is required for the children

3. If text contains a string wrapped in {{}}, _s is required

DefaultTagRE is a re used to configure {{beginning and}} ending strings

const defaultTagRE = /\{\{((? :.|\r? \n)+?) \}\}/g;Copy the code

The following code loops through the text of text to match and replace it with a re

let tokens = []
    let match;
    let lastIndex = defaultTagRE.lastIndex = 0;
    while (match = defaultTagRE.exec(text)) {
        // See if there is a match
        let index = match.index // start indexing
        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)))
    }

    console.log('tokens', tokens)

    return `_v(${tokens.join('+')}) `
Copy the code

The complete code

const defaultTagRE = / \ {\ {((? :.|\r? \n)+?) \}\}/g; // {{aaaaa}}

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 () {
                console.log(arguments[1].arguments[2])
                styleObj[arguments[1]] = arguments[2]
            })
            attr.value = styleObj
        }
        str += `${attr.name}:The ${JSON.stringify(attr.value)}, `
    }
    return ` {${str.slice(0, -1)}} `
}

function gen(el) {
    let text = el.text
    if (el.type === 1) { // element: 1 text: 3
        return generate(el)
    } else {
        if(! defaultTagRE.test(text)) {return `_v('${text}') `
        } else {
            / / split
            let tokens = []
            let match;
            let lastIndex = defaultTagRE.lastIndex = 0;
            while (match = defaultTagRE.exec(text)) {
                // See if there is a match
                let index = match.index // start indexing
                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)))
            }

            console.log('tokens', tokens)

            return `_v(${tokens.join('+')}) `}}}function genChildren(el) {
    let children = el.children
    if (children) {
        return children.map(c= > gen(c)).join(', ')}}function generate(el) {
    console.log('-- -- -- -- -- -- -- -- -- -- -- -- -- -- --', el)
    let children = genChildren(el)
    // Iterate over 🌲, concatenating 🌲 into a string
    let code = `_c('${el.tag}', ${el.attrs.length ? genProps(el.attrs) : 'undefined'
        })${children ? `,${children}` : ' '}`
    return code
}

export { generate };
Copy the code

The processing results

Git address

Github.com/haimingyue/…