README
dom.tpl
DOM模版通用翻译器,用于将DOM模版翻译成其他模版语言,接口类似Q.js。
模版简史
jQuery作者John Resig的著名文章JavaScript Micro-Templating,开启了前端模版引擎的序章。
之后,模版引擎走向了两个方向:
- 强大的自定义语法,代表有著名的handlebars
- 性能优先,追求极速体验,支持Javascript语法,代表有doT
但随着MVVM
和Web Component
的崛起,模版引擎又有了一些有趣的新成员:
- DOM Base Template
- Markup Template,代表有plates
为了解决什么需求?
DSLs
(Domain Specific Languages) ,例如<%=foo%>
和{{foo}}
的可移植性较差- 逻辑和模版真正分离,模版本身就是一个标准
HTML
片段 DOM
结构不依赖于数据,使得DOM
在没有数据时是可分析的
当然DOM Base Template
也有一些很难逾越的问题,比如性能,由于基于DOM树
的建立以及对DOM树
的遍历,所以对性能并不友好,这也正是Markup Template
出现的原因。
dom.tpl
实际上是一个DOM Base Template
翻译器,我们的思想是既然DOM Base Template
有性能问题,那么我们通过一次编译将其翻译成性能更好的模版,例如:
<p q-text="message"></p>
翻译成:
<p q-text="message"><%=it.message%></p>
例子可见Qtpl.js
。
使用
语法上和Q.js,在元素上通过自定义属性来映射指令。
directive
告知翻译器如何对节点进行操作,遵循Vuejs写法:
<element
prefix-directiveId="[argument:] value [| filters...]">
</element>
例如模版:
<p q-text="message"></p>
引擎会找到对应的text
指令来对该p元素进行操作,例如velocity.js
:
var domTpl = new DOMTpl({
directives: {
// text指令是发现q-text后在其内插入${value}
'text': function (value) {
value = postFix(value);
var dom = htmlparser.parseDOM('${' + value + '}');
DomUtils.appendChild(this.el, dom[0]);
}
}
});
再例如Qtpl.js
的例子:
var domTpl = new DOMTpl({
// 所有value都会经过get方法预处理,例如message预处理后变成it.message
get: function (value) {
return 'it.' + value;
},
// 所有filter的组装方法
// filters是字符串数组,value是上面prefix的结果
applyFilters: function (filters, value) {
var args, foo;
if (filters.length) {
filters.forEach(function (filter) {
args = filter.split(/ +/);
foo = args.shift()
value = [
value
];
args = args.map(function (arg) {
return "\'" + arg + "\'";
});
args.unshift(1, 0);
value.splice.apply(value, args);
// 我们可以看到这个方法输入如果是filters = ['filter1 arg', 'filter2'], value = message,则输出为:opt.filters.filter2(opt.filters.filter1(value, 'arg'))
value = 'opt.filters.' + foo + '(' + value.join(', ') + ')';
})
}
return value;
},
directives: {
// 最后到directive,输出结构
'text': function (value) {
var dom = htmlparser.parseDOM('{{=' + value + '}}');
DomUtils.appendChild(this.el, dom[0]);
}
}
});
默认方法
- get的默认为:
// 即直接返回值,不进行prefix
get: function (value) {
return value;
}
- applyFilters默认为:
// 如果输入为filters = ['filter1 arg', 'filter2'], value = message,则输出为:filter2(filter1(message, 'arg'))
applyFilters: function (filters, value, options) {
var args, foo;
if (filters.length) {
filters.forEach(function (filter) {
args = filter.split(/ +/);
foo = args.shift();
if (options.filters[foo]) {
value = [value]
value.push.apply(value, args);
value = options.filters[foo].apply(this, value);
} else {
value = [
value
];
args = args.map(function (arg) {
return "\'" + arg + "\'";
});
args.unshift(1, 0);
value.splice.apply(value, args);
value = foo + '(' + value.join(', ') + ')';
}
})
}
return value;
}