一、背景
有两个大项目是从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项目就好迁移了。