Github: https://github.com/zhiyu/jes
和ejs做了简单的性能对比,性能大概提升了30%-50%,欢迎大家试用。
测试代码:
var jes = require('jes');
var ejs = require('ejs');
var template = "<div><%-title%><div><p><%-body%></p>";
var start, end;
//ejs
start = new Date().getTime();
for(var i=0;i<50000;i++){
ejs.render(template, {title:'hello', body:'world'});
}
end = new Date().getTime();
console.log('ejs:'+(end-start));
//jes
start = new Date().getTime();
for(var i=0;i<50000;i++){
jes.render(template, {title:'hello', body:'world'});
}
end = new Date().getTime();
console.log('jes:'+(end-start));
jes源代码:
var path = require('path');
var fs = require('fs');
var jes = module.exports = {
cache : true,
caches : {}
};
jes.compile = function (tpl, options) {
var file = options.jes_file;
var splits = tpl.match(/<%[-*|=*]*|%>/g);
//no need to parse
if(splits == null){
return tpl;
}
//parse template
var lines = [];
lines.push("var s = [];with(options){");
for(var i = 0; i < splits.length; i++){
var line = '';
var split = splits[i];
var splitNext = null;
if(i < splits.length - 1){
splitNext = splits[i+1]
}
var idx = tpl.indexOf(split);
var idxNext = idx;
if(splitNext != null){
idxNext = tpl.indexOf(splitNext);
}
line = JSON.stringify(tpl.slice(0, idx));
lines.push("s.push("+line+");");
if(split == "<%"){
line = tpl.slice(idx + split.length, idxNext);
if(line.indexOf('include') != -1){
jes.renderFile(path.dirname(file)+"/"+line.trim().slice(8), options, function(err, data){
if(err){
lines.push("s.push("+JSON.stringify(err.toString())+");");
}else{
lines.push("s.push("+JSON.stringify(data)+");");
}
});
}else{
lines.push(line);
}
}else if(split == "<%-"){
line = tpl.slice(idx + split.length, idxNext);
lines.push("s.push(("+line+"));");
}else if(split == "<%="){
line = tpl.slice(idx + split.length, idxNext);
lines.push("s.push(escape(("+line+")));");
}
i++;
tpl = tpl.slice(idxNext+splitNext.length, tpl.length);
}
line = JSON.stringify(tpl);
lines.push("s.push("+line+");");
lines.push("return s.join('');}");
return new Function('options, escape', lines.join(''));
}
jes.render = function(str, options){
var obj = jes.compile(str, options);
var file = options.jes_file;
//cache
if(file){
jes.caches[file] = obj;
}
if(typeof obj == 'string'){
return obj;
}else{
return obj(options, jes.escape);
}
}
jes.renderFile = function(file, options, cb){
options.jes_file = file;
try{
//check cache
if(jes.cache && jes.caches[file]){
var obj = jes.caches[file];
if(obj){
if(typeof obj == 'string'){
cb(null, obj);
}else{
cb(null, obj(options, jes.escape));
}
}
}else{
var str = fs.readFileSync(file, 'utf8');
cb(null, jes.render(str, options));
}
}catch(e){ cb(e); }
}
jes.escape = function(str){
return str.replace(/&(?!\w+;)/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
支持。 lz有空研究研究模版中对异步代码的支持呗。 我最近在改造ejs,让它支持异步代码。比如: 下面的模版中有一块代码是异步(setTimeout)的,它会修改a变量的值。如果是同步的模版系统,比如ejs,代码<%=a%>会输出111111。
<div class="container">
<div class="row">
<aside class="span2">
<% include left.html %>
</aside>
<section class="span10">
<% var a = '111111';%>
Welcome!
</section>
<% var b= '333333';
setTimeout(function(){
a = '222222';
},3000);
%>
<%=a%>
<br/>
<%=b%>
</div>
</div> <!-- /container -->
我改造了ejs让其支持异步代码,将上例的代码修改如下:
<div class="container">
<div class="row">
<aside class="span2">
<% include left.html %>
</aside>
<section class="span10">
<% var a = '11111';%>
Welcome!
</section>
<% var b= '333333';
__async__(function(callback){
setTimeout(function(){
a='222222';
callback(null,'');
},3000);
}});
%>
<%=a%>
<br/>
<%=b%>
</div>
</div> <!-- /container -->
相比之前的代码多使用了async函数,来handle异步代码。 现在执行模版,<%=a%>输出就是222222了。 当然目前改造仍在继续中,比如还需要支持<%=…%>异步输出等
<%=user.getGradeAsync()%>
@zhiyu 还是比较常用吧,预先准备好所有数据的方式不够灵活。要想灵活的实现主题、插件功能,就需要这种异步的支持。比如我在模版文件中调用评论插件:
<% callPlugin('newest-comment');%>
- 系统先检查是否开启了最新评论的插件,如果没有开启,那就返回空字符串
- 如果开启了,就返回评论列表,这个步骤才去读数据库,nodejs中就是异步的了
当然对于其他语言来说第2点就没什么问题,因为读数据库是同步的。
@hylin 单纯从代码分层及代码维护来看,取数据的操作最好放到逻辑层来做,但是这不代表说每次都要取出所有数据,完全可以根据设置项来做选择,就像你说,可根据“是否开启评论”来判断是否读取评论数据,当然放到模板里来做也没有任何问题。可以大胆想象两个极端的情况:
- 既然可以把业务逻辑代码放到模板中,那么完全可以把逻辑层的所有代码也放到模板中。
- 同样的,视图的展现也可完全放到逻辑层来实现。
我想大家都没有去践行上面两种方案(这两种情况其实是一样的), 模板很重要的一点价值就介于此中间。
模板的基本功能保证了它的通用性,你所说的需求我更认为是和具体业务相关的,也就是对于模板基本功能的特定化扩展,就像wordpress模板中定义的各种全局函数(标签,评论什么的)。
如果对模板一般性功能有好的想法,希望能分享一下,共同进步^_^
@anuxs 同意。一般情况下(web应用)我们都是对文件做渲染(renderFile),而不是对字符串做渲染,大多数模板引擎都有缓存机制(缓存解析后的代码),性能上不会有太大的差别。如果真到了对模板做性能优化的时候,完全可以通过其他方式来解决了。
@zhiyu 大部分内容都赞同。不过正是因为我觉得现在nodejs由于它异步的特性,使得nodejs一般的模版系统纯粹停留在render数据的层面,和其它语言比如php中模版机制就有差距了。 所以通用性上来说,我觉得支持异步代码就应该是nodejs模版的一个基础功能,能够抹平和其它语言中的模版差异,实现类似其它语言已经非常成熟的模版、插件系统机制。
当然这只是目前我个人的认知,不排除在nodejs这种异步的情况下,在不支持异步代码的模版引擎上也有成熟的模版、插件系统机制。