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/…