写了一个模板引擎,分享一下代码。
发布于 2年前 作者 zhiyu 1122 次浏览

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, '"');
}
11 回复

支持。 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()%>

晕 文中的 async 是 __ async __ ,代码中的是正确的。被markdown替换了

@hylin 期待一楼的支持异步的模板

我觉得这种模板渲染引擎最好用c或者c++这类更底层的语言来写,效率上肯定会更好吧。不太懂。

你在业务上遇到这样的需求了?感觉这样的需求应该不多。只是个人看法,不太赞同在模板中使用异步代码,模板本身的意义在于渲染数据,异步的代码完全可以在其他地方实现。

@zhiyu 还是比较常用吧,预先准备好所有数据的方式不够灵活。要想灵活的实现主题、插件功能,就需要这种异步的支持。比如我在模版文件中调用评论插件:

<% callPlugin('newest-comment');%>
  1. 系统先检查是否开启了最新评论的插件,如果没有开启,那就返回空字符串
  2. 如果开启了,就返回评论列表,这个步骤才去读数据库,nodejs中就是异步的了

当然对于其他语言来说第2点就没什么问题,因为读数据库是同步的。

node.js 就是编译js为本地二进制代码的运行机制,得益于V8引擎。 再说,模板效率不是关键问题。

@hylin 单纯从代码分层及代码维护来看,取数据的操作最好放到逻辑层来做,但是这不代表说每次都要取出所有数据,完全可以根据设置项来做选择,就像你说,可根据“是否开启评论”来判断是否读取评论数据,当然放到模板里来做也没有任何问题。可以大胆想象两个极端的情况:

  1. 既然可以把业务逻辑代码放到模板中,那么完全可以把逻辑层的所有代码也放到模板中。
  2. 同样的,视图的展现也可完全放到逻辑层来实现。

我想大家都没有去践行上面两种方案(这两种情况其实是一样的), 模板很重要的一点价值就介于此中间。

模板的基本功能保证了它的通用性,你所说的需求我更认为是和具体业务相关的,也就是对于模板基本功能的特定化扩展,就像wordpress模板中定义的各种全局函数(标签,评论什么的)。

如果对模板一般性功能有好的想法,希望能分享一下,共同进步^_^

@anuxs 同意。一般情况下(web应用)我们都是对文件做渲染(renderFile),而不是对字符串做渲染,大多数模板引擎都有缓存机制(缓存解析后的代码),性能上不会有太大的差别。如果真到了对模板做性能优化的时候,完全可以通过其他方式来解决了。

@zhiyu 大部分内容都赞同。不过正是因为我觉得现在nodejs由于它异步的特性,使得nodejs一般的模版系统纯粹停留在render数据的层面,和其它语言比如php中模版机制就有差距了。 所以通用性上来说,我觉得支持异步代码就应该是nodejs模版的一个基础功能,能够抹平和其它语言中的模版差异,实现类似其它语言已经非常成熟的模版、插件系统机制。

当然这只是目前我个人的认知,不排除在nodejs这种异步的情况下,在不支持异步代码的模版引擎上也有成熟的模版、插件系统机制。

楼主好帅!万分敬仰崇拜

回到顶部