一、为什么要使用前端模板?
主要为了解决UI层与业务逻辑的纠缠不清。比如我们需要根据后端返回的数据动态生成一个表格或者类似表格的DOM(比如ul > li等)时,按照传统的方式,需要我们先获取数据,然后在页面的js中通过遍历数据、提取相关数值以及动态拼接html字串的方式,生成最终的DOM。这样就产生了UI与业务逻辑的纠缠。如果只是偶尔或者局部使用可能问题不明显,但如果代码量庞大、业务或数据十分复杂的情况下,这种方式产生的代码会十分不利于后期维护,这想必是每一个开发人员都不愿看到的。因此,便产生了前端模板以及对它们进行处理的模板引擎。
二、artTemplate及TmodJS简介
准确的说,artTemplate(后文简称aT)才是模板引擎,而TmodJS(后文简称TJ,曾用名atc)则是依赖于前者的一款模板预编译器。两者都是由腾讯开发。其实aT完全可以独立使用,而TJ存在的意义是提供了一个对模板进行预编译的环境(基于NodeJS和模块化)。有了TJ,aT可以支持按目录的方式存储模板、以include的方式对指定目录中的模板进行调用(注意:这里的include和aT中的include不一样,后者只能引用同一页面中的不同模板标签)、自动监控模板变化与自动编译、依赖管理以及与grunt的结合等,可以说进一步增强了aT的能力。目前,aT与TJ都可以通过npm获取:npm install arttemplate -g/npm install tmodjs -g。
更多详情和DEMO,可以参考:http://aui.github.io/artTemplate/以及https://github.com/aui/tmodjs
三、aT和TJ的工作流程与差异
两者的工作流程大致可以分为两步:编辑模板和渲染模板。编辑模板就是将需要动态生成的DOM抽象成为模板,渲染则是利用渲染函数,把后端返回的数据填入模板中相应的位置上,生成最终的html串并且回填到html页面的指定位置中。因为TJ采用了模块化加载技术,所以其模板的组织、引用以及渲染要比单纯的aT更为复杂,内部进行的封装处理流程也更多,但其本质上并未发生根本的变化。
因为aT没有使用模块化技术,所以页面内只需要引入aT的引擎文件即可使用。TJ由于使用了模块化技术,故需要引入相应标准的模块化加载器。因为TJ已经将引擎合并在编译后的文件中,故不需要再单独引入引擎文件。注意,若使用TJ,所有模板必须经过tmod命令进行编译后才能正常使用。
也正是因为模块化否的原因,两者的差异还体现在模板的组织形式与编写方式上。aT中,模板都要写在同一个html页面中,但不同模板可以分别写在不同的<script id="xxx" type="text/html">标签中,每个模板用id作为命名以进行区分。之前提到过,虽然aT也支持include语句,但其只能包含同一页面内的模板。如果模板存放在其它目录中则include无效。
TJ中,模板则是以html文件的形式存放。include语句可以包含不同目录下的模板文件。通过tmod进行编译时,其会在指定的模板目录中按照引用关系逐个读取模板文件,并将模板名称和模板内容封装在函数a()中备用。当所有模板均依此处理完成之后,其会将上述封装的函数以及引擎代码一并封装并产生编译后的js文件。该文件今后会通过require方式载入,抛出渲染函数template()并用后端返回的数据执行渲染并生成DOM的工作。
另外,两者在调用渲染的方式上也有所不同。aT由于是独立存在的,故直接通过调用template('ID',data)即可。而TJ由于模块化的原因,调用则相对复杂一些:
require(['编译后产生的js文件(不包含后缀名)'], function (template) {
document.getElementById('id').innerHTML = template('模板入口', data);
});
四、aT的简明语法
aT分为两种语法模式:简单和原生。所谓原生就是其语法表现形式与JS语法更为相似。不过个人更加喜欢简单模式,理由是简单。原生这种方式也只是类似而已,与其似像似不像不如来个更简单直接的。这里也只介绍简明语法模式。
4.1 if判断
{{if var}}
// some code...
{{/if}}
4.2 遍历对象
{{each obj}}
// some code...
{{/each}}
或者
{{each obj as value i}}
// some code...
{{/each}}
4.3 调用页面内其它模板
{{include '模板ID'}}
4.4 渲染模板
var rendered = template('ID',data); //ID即模板ID,data即从后端返回的数据,此种调用返回渲染后的html串
var renderFunc = template('ID'); //如果仅指定了模板ID,则返回一个渲染函数
var renderFunc = template(模板变量); //如果将模板存储在变量中,且仅指定了该变量,则返回一个渲染函数
4.5 其它功能
{{print}} //输出变量值
template.helper(name,callback); //在模板中使用自定义的函数。相应的,模板中应该这样写:{{arg1 | dateFormat:arg2}}
template.config(name,value); //修改引擎的默认配置。各项设置如图:
五、TJ的使用
TJ在安装之后,便可通过执行tmod命令来执行相关的预编译工作。这个预编译也是以相关的配置作为编译准则的,这个配置则存放在模板目录下的package.json中。该文件的结构如下:
{
"name": "template",
"version": "1.0.1",
"dependencies": {
"tmodjs": "1.0.2"
},
"tmodjs-config": {
"output": "./new-build",
"charset": "utf-8",
"syntax": "simple",
"helpers": null,
"escape": true,
"compress": true,
"type": "default",
"runtime": "main.js",
"combo": true,
"minify": true,
"cache": false
}
}
其中主要用到的是"tmodjs-config"及其下相关各项:
1.output:编译结果的输出目录
2.charset:指定编译所用的字符编码
3.syntax:指明引擎所用的模式(简单/原生)
4.helpers:辅助函数
5.escape:bool,是否对字符进行转义(防止XSS攻击)
6.compress:bool,是否压缩html多余的空白字符
7.type:模板将采用哪种标准进行模块化处理,可为cmd、amd、commonjs
8.runtime:编译后产生的js文件名。所谓编译指的就是将模板转化成模块化的js代码,并且将引擎(种子)与转化后的模板合并在一个js文件中。可以将其理解为“主入口”
9.combo:bool,是否合并模板(type值为default时可用)
10.minify:bool,是否压缩
11.cache:bool,是否生成编译缓存目录(1.0.2版本貌似此选项无效)
上面是写在package.json中的配置。在调用tmod命令时,其后也需(可)跟随参数:
tmod [模板目录] [配置参数]
1.--debug 输出调试版本
2.--charset value 定义模板编码,默认utf-8
3.--output value 定义输出目录,默认./build
4.--type value 定义输出模块格式,默认default,可选cmd、amd、commonjs
5.--no-watch 关闭模板目录监控
6.--version 显示版本号
7.--help 显示帮助信息
tmod命令后跟随的参数会自动覆盖package.json中相应属性的值。
编译后,我们会在编译结果的输出目录中看到一个名为“.cache”目录,这个就是编译过程中产生的缓存文件。该目录可以被删除,不影响最终的使用,因为其内容已被整合到“runtime”指定的js文件中了。