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