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, '"');
}
Nodejs模板引擎代码分享
GitHub: https://github.com/zhiyu/jes
本文介绍了一个名为 jes
的 Node.js 模板引擎。通过与流行的模板引擎 ejs
进行简单对比,发现 jes
在性能上大约提升了 30%-50%。欢迎大家试用并提供反馈。
性能测试代码
以下是简单的性能测试代码,用于比较 ejs
和 jes
渲染速度的差异:
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, '"');
}
解释
-
编译模板:
jes.compile
函数负责解析模板字符串,生成一个 JavaScript 函数。该函数接受一个对象作为参数,并将模板中的占位符替换为实际值。- 使用正则表达式匹配模板中的标签
<%
,<%-
,<%=
等,并根据不同的标签类型生成相应的 JavaScript 代码。
-
渲染模板:
jes.render
函数用于执行编译后的 JavaScript 函数,生成最终的 HTML 字符串。- 支持文件缓存功能,如果启用了缓存 (
jes.cache
),则会将编译后的模板函数存储在内存中,以提高后续渲染的速度。
-
包含外部文件:
jes.renderFile
函数允许模板中包含其他文件。例如,模板中可以使用<% include filename %>
语法来引入其他模板文件。
-
转义输出:
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
引擎的主要功能和实现:
主要功能
- 模板编译:将模板字符串转换为可执行的JavaScript函数。
- 渲染:使用传入的数据对象来填充模板。
- 缓存机制:为了提高性能,引擎支持缓存已编译的模板。
- 包含文件:允许模板中包含其他文件。
示例代码
const jes = require('jes');
const template = `<div><%-title%><div><p><%-body%></p>`;
// 渲染模板
const result = jes.render(template, { title: 'Hello', body: 'World' });
console.log(result);
源代码解析
-
编译模板
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('')); }
-
渲染模板
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); } }
-
转义HTML
jes.escape = function(str) { return str.replace(/&(?!\\w+;)/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
通过这些代码,你可以看到jes
模板引擎是如何工作的,并且可以通过比较其与EJS的性能来决定是否采用它。