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.
<div class="root" name="root">
<p>1</p>
<div>11</div>
</div>
{
type: "tag",
tagName: "div",
attr: {
className: "root"
name: "root"
},
parent: null,
children: [{
type: "tag",
tagName: "p",
attr: {},
parent: {} /* Reference to parent node */,
children: [{
type: "text",
tagName: "text",
parent: {} /* Reference to parent node */,
content: "1"
}]
},{
type: "tag",
tagName: "div",
attr: {},
parent: {} /* Reference to parent node */,
children: [{
type: "text",
tagName: "text",
parent: {} /* Reference to parent node */,
content: "11"
}]
}]
}
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.
<!DOCTYPE html>
<html>
<head>
<title>Template Syntax</title>
</head>
<body>
<div id="root">
<div>{{show}}</div>
<div>{{description}}</div>
</div>
</body>
<script type="text/javascript">
var data = {
show: 1,
description: "A simple template syntax"
};
function render(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 = new Function(...Object.keys(data), html)(...Object.values(data));
element.innerHTML = parsedHTML;
}
</script>
<!DOCTYPE html>
<html>
<head>
<title>Template Syntax</title>
</head>
<body>
<div id="root" class="root-node">
<div>{{show}}</div>
<div>{{description}}</div>
</div>
</body>
<script type="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;
}
function generateHTMLTemplate(AST) {
var template = "";
AST.forEach(node => {
if (node.type === "tag") {
template += `<${node.tagName}>`;
template += generateHTMLTemplate(node.children);
template += `</${node.tagName}>`;
} else {
if (node.content.match(/\{\{(.)*?\}\}/)) {
var expression = node.content.replace(/\{\{(.)*?\}\}/g, function(value) {
return value.replace("{{", '"+(').replace("}}", ')+"');
})
template += expression;
} else {
template += node.content;
}
}
})
return template;
}
var root = document.getElementById("root");
var AST = parseAST(root);
var template = generateHTMLTemplate([AST]);
render(root, template, data);
</script>
</html>
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.
https://github.com/WindrunnerMax/EveryDay
https://www.cnblogs.com/libin-1/p/6544519.html
https://www.cnblogs.com/10manongit/p/12869775.html
https://blog.csdn.net/weixin_34321977/article/details/91419022