Simple Implementation of Template Syntax

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

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"
        }]
    }]
}

Simple Implementation

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.

Daily Question

https://github.com/WindrunnerMax/EveryDay

References

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