Nodejs模板引擎代码分享

Nodejs模板引擎代码分享

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

8 回复

Nodejs模板引擎代码分享

GitHub: https://github.com/zhiyu/jes

本文介绍了一个名为 jes 的 Node.js 模板引擎。通过与流行的模板引擎 ejs 进行简单对比,发现 jes 在性能上大约提升了 30%-50%。欢迎大家试用并提供反馈。

性能测试代码

以下是简单的性能测试代码,用于比较 ejsjes 渲染速度的差异:

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 源代码

下面是 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);

    // 如果没有需要解析的部分,则直接返回模板字符串
    if(splits == null){
        return tpl;
    }

    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;
    // 缓存处理
    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{
        // 检查缓存
        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, '"');
}

解释

  1. 编译模板

    • jes.compile 函数负责解析模板字符串,生成一个 JavaScript 函数。该函数接受一个对象作为参数,并将模板中的占位符替换为实际值。
    • 使用正则表达式匹配模板中的标签 <%, <%-, <%= 等,并根据不同的标签类型生成相应的 JavaScript 代码。
  2. 渲染模板

    • jes.render 函数用于执行编译后的 JavaScript 函数,生成最终的 HTML 字符串。
    • 支持文件缓存功能,如果启用了缓存 (jes.cache),则会将编译后的模板函数存储在内存中,以提高后续渲染的速度。
  3. 包含外部文件

    • jes.renderFile 函数允许模板中包含其他文件。例如,模板中可以使用 <% include filename %> 语法来引入其他模板文件。
  4. 转义输出

    • jes.escape 函数用于转义模板中的特殊字符,防止 XSS 攻击。

希望这篇分享对大家有所帮助!欢迎提出任何问题或建议。


支持。 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替换了

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

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

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

楼主好帅!万分敬仰崇拜

这段代码展示了一个自定义的Node.js模板引擎jes。该引擎通过替换EJS来提供更好的性能。下面是jes引擎的主要功能和实现:

主要功能

  1. 模板编译:将模板字符串转换为可执行的JavaScript函数。
  2. 渲染:使用传入的数据对象来填充模板。
  3. 缓存机制:为了提高性能,引擎支持缓存已编译的模板。
  4. 包含文件:允许模板中包含其他文件。

示例代码

const jes = require('jes');
const template = `<div><%-title%><div><p><%-body%></p>`;

// 渲染模板
const result = jes.render(template, { title: 'Hello', body: 'World' });
console.log(result);

源代码解析

  1. 编译模板

    jes.compile = function (tpl, options) {
        var splits = tpl.match(/<%[-*|=*]*|%>/g);
        // 简化处理,直接返回原始模板字符串
        if (!splits) return tpl;
    
        var lines = ["var s = []; with(options) {"];
        for (var i = 0; i < splits.length; i++) {
            var split = splits[i], line = '', splitNext = null;
            if (i < splits.length - 1) {
                splitNext = splits[i + 1];
            }
            var idx = tpl.indexOf(split), 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);
                // 处理 include 指令
                if (line.includes('include')) {
                    jes.renderFile(path.dirname(options.jes_file) + "/" + line.trim().slice(8), options, (err, data) => {
                        lines.push(err ? `s.push(${JSON.stringify(err.toString())});` : `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(''));
    }
    
  2. 渲染模板

    jes.render = function(str, options) {
        var obj = jes.compile(str, options);
        var file = options.jes_file;
        if (file) {
            jes.caches[file] = obj;
        }
        if (typeof obj === 'string') {
            return obj;
        } else {
            return obj(options, jes.escape);
        }
    }
    
  3. 读取文件并渲染

    jes.renderFile = function(file, options, cb) {
        options.jes_file = file;
        try {
            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); }
    }
    
  4. 转义HTML

    jes.escape = function(str) {
        return str.replace(/&(?!\\w+;)/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
    }
    

通过这些代码,你可以看到jes模板引擎是如何工作的,并且可以通过比较其与EJS的性能来决定是否采用它。

回到顶部