前端Javascript模板引擎【简单实现】 进入全屏
line

一、背景

有两个大项目是从RD那边迁移过来,因为项目初期FE无人力跟进,所以都是后端同学直接用Smarty完成的前端部分;所以考虑到迁移的成本和方案,索性升级一下当前的JS模板引擎

二、大致方案

  • 支持extends标签
  • 支持block标签

1、标签语法

为了便于模板词法分析,在模板左定界符后加上@来标识,标签名替换为属性设置方式,如extends标签:

<%@ extends="layout/layout.html" %>

2、extends实现方案

将整个extends标签替换成其指定的父模板的内容

3、block实现方案

首先理解block概念:在父模板layout.html中提前挖好的坑,子模板中对block的实现,就好比萝卜,最后渲染的时候,就是一个萝卜一个坑的填充。

这样一来事情就好办多了,可将block的定义编译成js function的调用,假设父模板中block的定义是:

<%@ block="block_header" %>

最终可将其编译成:

block_header();

4、block进阶:父模板支持默认内容

smarty中的block是支持默认内容的,比如:(为了将smarty和js模板引擎区分开,下面用<&&>分别标识smarty的定界符)

<&block name="block_header" &>
    <div class="x-header">
        ...
    </div>
<&/block&>

那么,我们可以对应的在js模板引擎中支持如下格式:

<% block="block_header" {%>
    <div class="x-header">
        ...
    </div>    
<%}%>

没错,用一个相对投机的{}将block默认内容包围起来,编译时,将block_header编译成一个function即可,为了避免和子模板中的block实现(也是一个function)命名冲突,所以给父模板中的block name定义加上parent标识,如:

function block_header__parent_ (){
    __html += '<div class="x-header">\n';
    __html += '\t...\n';
    __html += '</div\n>';
}

这样,如果子模板中没有对该block的(function)实现,那么这里可以直接执行这个blockx__parent方法即可。最终的样子差不多是这样的:

block_header__parent_(); 
function block_header__parent_ (){
    __html += '<div class="x-header">\n';
    __html += '\t...\n';
    __html += '</div\n>';
}

5、block进阶:子模板有block实现,甚至设置prepend、append模式

如题,假设子模板中有对该block的实现,可以用function来解决,如:

<%function block_header(){%>
    我是header
<%}%>

那么,按照smarty的理解,应该是要用子模板中的block来覆盖父模板的block的,所以父模板block的定义处可编译为:

block_header();
function block_header__parent_ (){
    __html += '<div class="x-header">\n';
    __html += '\t...\n';
    __html += '</div\n>';
}

而对于smarty中定义的prepend模式继承block,如:

<&block name="block_header" prepend &>
    我是header
<&/block&>

我们可以这样来简单模拟:

<%function block_header(prepend){%>
    我是header
<%}%>

对,通过给block_header添加prepend参数的方式来解决。对于这种形式,父母般block的定义处可编译为:

block_header();
block_header__parent_();
function block_header__parent_ (){
    __html += '<div class="x-header">\n';
    __html += '\t...\n';
    __html += '</div\n>';
}

同理,对于append模式,我们可以把它编译成:

block_header__parent_();
block_header();
function block_header__parent_ (){
    __html += '<div class="x-header">\n';
    __html += '\t...\n';
    __html += '</div\n>';
}

6、block进阶:多级继承(layout.html ← middle.html ← child.html)

逐级prepend或者append貌似不太好整,不过可以变换下思路,采用smarty中的block嵌套形式,比如:

<&block name="block_header" &>
    我是header

    <&block name="block_header_inner" &>
        我是inner
    <&/block&>
<&/block&>

所以,在middle.html中,可以先实现block_header,再在其内部继续挖坑定义block_header_inner,比如:

<%function block_header(){%>
      我是header
     <%@ block="block_header_inner" %>
<%}%>

这样,在子模板child.html中只需要实现middle.html中的block_header_inner即可:

<%function block_header_inner(){%>
      我是子模板中的inner
<%}%>

7、如何保证子模板中不会出现父模板未定义的东西?

这个是必须得去保证的,可以这样简单处理:在extends标签被替换的地方,强制加上:

return __html;

三、实践一下

1、layout.html

<!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <body>
        <header>
            <%@ block="block_header" %>
        </header>

        <section>
            <%@ block="block_content" {%>
                <div>
                    这里是父layout中的内容
                </div>
            <%}%>
        </section>

        <footer>
            <%@ block="block_footer" %>
        </footer>
    </body>
</html>

2、child.html

<%@ extends="layout.html" %>

<%function block_content(prepend){%>
    我是子模板中的内容
<%}%>

<%function block_footer(){%>
    我是子模板中的footer
<%}%>

我是子模板中多余的内容

3、编译后的中间文件

/*--/Users/zhaoxianlie/SourceCode/biz/trunk/demo/views/child.html--*/
exports.html = function ($_ROOT) {
    return function ($_DATA) {
        var __html = '';
        __html += '';
        __html += '<!doctype html><html> <head> ' +
            '<meta charset="UTF-8"> <title>Document</title> ' +
            '</head> <body> <header> ';

        __html += ' </header> <section> ';
        block_content();
        block_content__parent_();
        function block_content__parent_() {
            __html += ' <div> 这里是父layout中的内容 </div> ';
        }
        __html += ' </section> <footer> ';
        block_footer();
        __html += ' </footer> </body></html>';
        return __html;

        // 其实故事到这里就结束了。。。
        __html += '';
        function block_content(prepend) {
            __html += '我是子模板中的内容';
        }
        __html += '';
        function block_footer() {
            __html += '我是子模板中多余的内容';
        }
        __html += ' haha';
        return __html;
    };
};

4、运行以后

我是子模板中的内容
这里是父layout中的内容
我是子模板中的footer


差不多也就这点儿东西,就这么个原理,之后的smarty项目就好迁移了。

阿里巴巴-钉钉-开放平台,能力开放&开发者运营岗位招聘中, 期待你的加入!
钉钉开放,让应用开发更简单
充分开放,是钉钉的重要方向!除致力于为开发者打造丰富的开放API, 更易接入的场景化能力包, 完备的应用开发工具之外, 还需要持续构建开放能力的布道、开发者生态运营体系,包括培训、沙龙、大会、社区合作等等。业务在快速发展,我们也还需要更多优秀的小伙伴加入!

评论区域

line
  • Alien 2014-12-27 20:27:39 回复
    回复 谢亮 : 对的
    谢亮 said:
    前端的权重也越来越高了。。。我们现在还是用的php的框架,不过也有layouts之类的功能,目前也还能满足需求吧,你们这是用node处理的模板了?
  • 谢亮 2014-12-27 20:26:40 回复
    前端的权重也越来越高了。。。我们现在还是用的php的框架,不过也有layouts之类的功能,目前也还能满足需求吧,你们这是用node处理的模板了?