Template syntax allows inserting JavaScript variables and expressions into HTML, and when controlling the render in JavaScript, it can automatically calculate and display variables or expressions on the page. Common template syntax includes mustache-style {{}} and DSL-style dsl-html.
AST, short for Abstract Syntax Tree, is a tree-like representation of the abstract syntax structure of source code. Every type of source code can be abstracted into an AST. Here, parsing the template into an AST means to parse the HTML structure of the template into a tree with structure, relationships, and properties, which makes it convenient for subsequent template processing, reduces the performance overhead caused by multiple string parsing, and makes it easier to traverse the HTML as a tree data structure. Below is a simple AST for the HTML example.
A simple implementation of mustache-style {{}} is achieved by only implementing the display of its data. Directives such as loops are not implemented. By processing the string and converting it into a function and then executing it with parameters, the data display can be achieved.
<!DOCTYPEhtml><html><head><title>Template Syntax</title></head><body><divid="root"><div>{{show}}</div><div>{{description}}</div></div></body><scripttype="text/javascript">var data ={show:1,description:"A simple template syntax"};functionrender(element, data){var originString = element.innerHTML;var html =String(originString ||'').replace(/"/g,'\\"').replace(/\s+|\r|\t|\n/g,' ').replace(/\{\{(.)*?\}\}/g,function(value){return value.replace("{{",'"+(').replace("}}",')+"');}) html =`var targetHTML = "${html}";return targetHTML;`;var parsedHTML =newFunction(...Object.keys(data), html)(...Object.values(data)); element.innerHTML = parsedHTML;}</script>
<!DOCTYPEhtml><html><head><title>Template Syntax</title></head><body><divid="root"class="root-node"><div>{{show}}</div><div>{{description}}</div></div></body><scripttype="text/javascript"> var data = {
show: 1,
description: "A simple template syntax"
};
function parseAST(root){
var node = {};
node.parent = null;
if(root.nodeName === "#text"){
node.type = "text";
node.tagName = "text";
node.content = root.textContent.replace(/\s+|\r|\t|\n/g, ' ').replace(/"/g,'\\"');
}else{
node.type = "tag";
node.tagName = root.localName;
node.children = [];
node.attr = {};
Array.prototype.forEach.call(root.attributes, item => node.attr[item.nodeName] = item.nodeValue );
}
Array.prototype.forEach.call(root.childNodes, element => {
var parsedNode = parseAST(element);
parsedNode.parent = root;
node.children.push(parsedNode);
});
return node;
}
function render(element, template, data) {
html = `var targetHTML = "${template}";return targetHTML;`;
var parsedHTML = new Function(...Object.keys(data), html)(...Object.values(data));
element.innerHTML = parsedHTML;
}
Although it seems like in the end we'll have to use Function to process strings, and the AST also needs to parse HTML and then concatenate strings, increasing the computation time, if it's simply a matter of implementing the template syntax based on string processing, when the data changes, a full render is required, and each time that happens, the entire DOM needs to be re-rendered. Even though in the simple implementation above, the AST also re-renders the entire template, modern Js frameworks like Vue are based on the AST approach. First, the template is parsed into AST, then the AST is used to mark static nodes for reusing and skipping comparison, thereby optimizing rendering. Then a virtual DOM is generated, and when the data changes, the virtual DOM performs a diff algorithm comparison to find the nodes with data changes, and then minimizes the rendering. This way, the entire template doesn't need to be re-rendered when the data changes, increasing rendering efficiency.