Benchmark test for some template engines, and no-template (only JavaScript). (v19)

Revision 19 of this benchmark created by Makoto on


Description

Benchmark test for...

  • Mustache.js
  • Hogan.js
  • Handlebars.js
  • Underscore.js(_.template)
  • EJS
  • jQuery.EJS
  • PURE
  • Hogan.js (precompiled)
  • Handlebars.js (precompiled)
  • Underscore.js (precompiled)
  • EJS (precompiled)
  • jQuery.EJS (precompiled)
  • Knockout
  • PURE (precompiled)
  • No Template (only JavaScript)

Preparation HTML

<div id="template-mu">
<!--
<table border="1" style="width: 700px;font-size: 15px;text-align: center;border-collapse: collapse;">
  <caption>{{title}}</caption>
  <thead>
    <tr>
      <td style="width: 90px;"></td>
      {{#table.heads}}
      <th>{{.}}</th>
      {{/table.heads}}
    </tr>
  </thead>
  <tbody>
  {{#table.sections}}
  <tr>
    <td class="name" style="text-align: left;padding: 4px 0;">{{name}}</td>
    {{#section}}
    <td class="item" style="text-align: center;padding: 4px 0;">{{.}}</td>
    {{/section}}
  </tr>
  {{/table.sections}}
  </tbody>
</table>
-->
</div>
<div id="template-us">
<!--
<table border="1" style="width: 700px;font-size: 15px;text-align: center;border-collapse: collapse;">
  <caption><%-title%></caption>
  <thead>
    <tr>
      <td style="width: 90px;"></td>
      <%_.each(table.heads,function(head){%>
      <th><%-head%></th>
      <%})%>
    </tr>
  </thead>
  <tbody>
    <%_.each(table.sections,function(s){%>
    <tr>
      <td class="name" style="text-align: left;padding: 4px 0;"><%-s.name%></td>
      <%_.each(s.section,function(section){%>
      <td class="item" style="text-align: center;padding: 4px 0;"><%-section%></td>
      <%})%>
    </tr>
    <%})%>
  </tbody>
</table>
-->
</div>
<div id="template-ejs">
<!--
<table border="1" style="width: 700px;font-size: 15px;text-align: center;border-collapse: collapse;">
  <caption><%=title%></caption>
  <thead>
    <tr>
      <td style="width: 90px;"></td>
      <%table.heads.forEach(function(head){%>
      <th><%=head%></th>
      <%})%>
    </tr>
  </thead>
  <tbody>
    <%table.sections.forEach(function(s){%>
    <tr>
      <td class="name" style="text-align: left;padding: 4px 0;"><%=s.name%></td>
      <%s.section.forEach(function(section){%>
      <td class="item" style="text-align: center;padding: 4px 0;"><%=section%></td>
      <%})%>
    </tr>
    <%})%>
  </tbody>
</table>
-->
</div>
<div id="template-pure" style="display: none;">
<table border="1" style="width: 700px;font-size: 15px;text-align: center;border-collapse: collapse;">
  <caption></caption>
  <thead>
    <tr>
      <td style="width: 90px;"></td>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="name" style="text-align: left;padding: 4px 0;"></td>
      <td class="item" style="text-align: center;padding: 4px 0;"></td>
    </tr>
  </tbody>
</table>
</div>
<div id="template-ko">
<table border="1" style="width: 700px;font-size: 15px;text-align: center;border-collapse: collapse;">
  <caption data-bind="text:title"></caption>
  <thead>
    <tr>
      <td style="width: 90px;"></td>
<!-- ko foreach: table.heads-->
      <th data-bind="text:$data"></th>
<!-- /ko --> 
   </tr>
  </thead>
  <tbody data-bind="foreach: { data: sections, as: 's' }">
    <tr>
      <td class="name" style="text-align: left;padding: 4px 0;" data-bind="text:s.name"></td>
<!-- ko foreach: s.section-->
      <td class="item" style="text-align: center;padding: 4px 0;" data-bind="text:$data"></td>
<!-- /ko -->
    </tr>
  </tbody>
</table>
</div>
<div id='no-template'>
<!--
<table id='tblNoTemplate' border='1' style='width: 700px;font-size: 15px;text-align: center;border-collapse: collapse;'>
  <caption id='captionNoTemplate'>{0}</caption>
  <thead id='theadNoTemplate'>
    <tr>{1}</tr>
  </thead>
  <tbody id='tbodyNoTemplate'>{2}</tbody>
</table>
-->
</div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.0/mustache.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/hogan.js/2.0.0/hogan.js"></script>
<script type="text/javascript" src="https://rawgithub.com/wycats/handlebars.js/1.0.0/dist/handlebars.runtime.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.3/underscore-min.js"></script>
<script type="text/javascript">
// visionmedia EJS
ejs=function(){function require(p){if("fs"==p)return{};var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');return mod.exports||(mod.exports={},mod.call(mod.exports,mod,mod.exports,require.relative(path))),mod.exports}return require.modules={},require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&&reg||require.modules[index]&&index||orig},require.register=function(path,fn){require.modules[path]=fn},require.relative=function(parent){return function(p){if("."!=p.substr(0,1))return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i<segs.length;i++){var seg=segs[i];".."==seg?path.pop():"."!=seg&&path.push(seg)}return require(path.join("/"))}},require.register("ejs.js",function(module,exports,require){var utils=require("./utils"),fs=require("fs");exports.version="0.7.2";var filters=exports.filters=require("./filters"),cache={};exports.clearCache=function(){cache={}};function filtered(js){return js.substr(1).split("|").reduce(function(js,filter){var parts=filter.split(":"),name=parts.shift(),args=parts.shift()||"";return args&&(args=", "+args),"filters."+name+"("+js+args+")"})}function rethrow(err,str,filename,lineno){var lines=str.split("\n"),start=Math.max(lineno-3,0),end=Math.min(lines.length,lineno+3),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" >> ":"    ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message,err}var parse=exports.parse=function(str,options){var options=options||{},open=options.open||exports.open||"<%",close=options.close||exports.close||"%>",buf=["var buf = [];","\nwith (locals) {","\n  buf.push('"],lineno=1,consumeEOL=!1;for(var i=0,len=str.length;i<len;++i)if(str.slice(i,open.length+i)==open){i+=open.length;var prefix,postfix,line="__stack.lineno="+lineno;switch(str.substr(i,1)){case"=":prefix="', escape(("+line+", ",postfix=")), '",++i;break;case"-":prefix="', ("+line+", ",postfix="), '",++i;break;default:prefix="');"+line+";",postfix="; buf.push('"}var end=str.indexOf(close,i),js=str.substring(i,end),start=i,n=0;"-"==js[js.length-1]&&(js=js.substring(0,js.length-2),consumeEOL=!0);while(~(n=js.indexOf("\n",n)))n++,lineno++;js.substr(0,1)==":"&&(js=filtered(js)),buf.push(prefix,js,postfix),i+=end-start+close.length-1}else str.substr(i,1)=="\\"?buf.push("\\\\"):str.substr(i,1)=="'"?buf.push("\\'"):str.substr(i,1)=="\r"?buf.push(" "):str.substr(i,1)=="\n"?consumeEOL?consumeEOL=!1:(buf.push("\\n"),lineno++):buf.push(str.substr(i,1));return buf.push("');\n}\nreturn buf.join('');"),buf.join("")},compile=exports.compile=function(str,options){options=options||{};var input=JSON.stringify(str),filename=options.filename?JSON.stringify(options.filename):"undefined";str=["var __stack = { lineno: 1, input: "+input+", filename: "+filename+" };",rethrow.toString(),"try {",exports.parse(str,options),"} catch (err) {","  rethrow(err, __stack.input, __stack.filename, __stack.lineno);","}"].join("\n"),options.debug&&console.log(str);var fn=new Function("locals, filters, escape",str);return function(locals){return fn.call(this,locals,filters,utils.escape)}};exports.render=function(str,options){var fn,options=options||{};if(options.cache){if(!options.filename)throw new Error('"cache" option requires "filename".');fn=cache[options.filename]||(cache[options.filename]=compile(str,options))}else fn=compile(str,options);return options.__proto__=options.locals,fn.call(options.scope,options)},exports.renderFile=function(path,options,fn){var key=path+":string";"function"==typeof options&&(fn=options,options={}),options.filename=path;try{var str=options.cache?cache[key]||(cache[key]=fs.readFileSync(path,"utf8")):fs.readFileSync(path,"utf8");fn(null,exports.render(str,options))}catch(err){fn(err)}},exports.__express=exports.renderFile,require.extensions?require.extensions[".ejs"]=function(module,filename){source=require("fs").readFileSync(filename,"utf-8"),module._compile(compile(source,{}),filename)}:require.registerExtension&&require.registerExtension(".ejs",function(src){return compile(src,{})})}),require.register("filters.js",function(module,exports,require){exports.first=function(obj){return obj[0]},exports.last=function(obj){return obj[obj.length-1]},exports.capitalize=function(str){return str=String(str),str[0].toUpperCase()+str.substr(1,str.length)},exports.downcase=function(str){return String(str).toLowerCase()},exports.upcase=function(str){return String(str).toUpperCase()},exports.sort=function(obj){return Object.create(obj).sort()},exports.sort_by=function(obj,prop){return Object.create(obj).sort(function(a,b){return a=a[prop],b=b[prop],a>b?1:a<b?-1:0})},exports.size=exports.length=function(obj){return obj.length},exports.plus=function(a,b){return Number(a)+Number(b)},exports.minus=function(a,b){return Number(a)-Number(b)},exports.times=function(a,b){return Number(a)*Number(b)},exports.divided_by=function(a,b){return Number(a)/Number(b)},exports.join=function(obj,str){return obj.join(str||", ")},exports.truncate=function(str,len){return str=String(str),str.substr(0,len)},exports.truncate_words=function(str,n){var str=String(str),words=str.split(/ +/);return words.slice(0,n).join(" ")},exports.replace=function(str,pattern,substitution){return String(str).replace(pattern,substitution||"")},exports.prepend=function(obj,val){return Array.isArray(obj)?[val].concat(obj):val+obj},exports.append=function(obj,val){return Array.isArray(obj)?obj.concat(val):obj+val},exports.map=function(arr,prop){return arr.map(function(obj){return obj[prop]})},exports.reverse=function(obj){return Array.isArray(obj)?obj.reverse():String(obj).split("").reverse().join("")},exports.get=function(obj,prop){return obj[prop]},exports.json=function(obj){return JSON.stringify(obj)}}),require.register("utils.js",function(module,exports,require){exports.escape=function(html){return String(html).replace(/&(?!\w+;)/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}}),require("ejs")}();
</script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<script type="text/javascript">
// JavaScriptMVC
(function(h){var p=function(a){return a.replace(/^\/\//,"").replace(/[\/\.]/g,"_")},s=h.makeArray,B=1,e=h.View=function(a,b,c,d){if(typeof c==="function"){d=c;c=undefined}var f=C(b);if(f.length){var g=h.Deferred();f.push(t(a,true));h.when.apply(h,f).then(function(j){var k=s(arguments),l=k.pop()[0];if(n(b))b=u(j);else for(var m in b)if(n(b[m]))b[m]=u(k.shift());k=l(b,c);g.resolve(k);d&&d(k)});return g.promise()}else{var i;f=typeof d==="function";g=t(a,f);if(f){i=g;g.done(function(j){d(j(b,c))})}else g.done(function(j){i=
j(b,c)});return i}},v=function(a,b){if(!a.match(/[^\s]/))throw"$.View ERROR: There is no template or an empty template at "+b;},t=function(a,b){return h.ajax({url:a,dataType:"view",async:b})},n=function(a){return a&&h.isFunction(a.always)},C=function(a){var b=[];if(n(a))return[a];else for(var c in a)n(a[c])&&b.push(a[c]);return b},u=function(a){return h.isArray(a)&&a.length===3&&a[1]==="success"?a[0]:a};h.ajaxTransport("view",function(a,b){var c=b.url;a=c.match(/\.[\w\d]+$/);var d,f,g,i,j=function(l){l=
d.renderer(g,l);if(e.cache)e.cached[g]=l;return{view:l}};if(f=document.getElementById(c))a="."+f.type.match(/\/(x\-)?(.+)/)[2];if(!a){a=e.ext;c+=e.ext}g=p(c);if(c.match(/^\/\//)){var k=c.substr(2);c=typeof steal==="undefined"?(c="/"+k):steal.root.mapJoin(k)+""}d=e.types[a];return{send:function(l,m){if(e.cached[g])return m(200,"success",{view:e.cached[g]});else if(f)m(200,"success",j(f.innerHTML));else i=h.ajax({async:b.async,url:c,dataType:"text",error:function(){v("",c);m(404)},success:function(w){v(w,
c);m(200,"success",j(w))}})},abort:function(){i&&i.abort()}}});h.extend(e,{hookups:{},hookup:function(a){var b=++B;e.hookups[b]=a;return b},cached:{},cache:true,register:function(a){this.types["."+a.suffix]=a;window.steal&&steal.type(a.suffix+" view js",function(b,c){var d=e.types["."+b.type],f=p(b.rootSrc+"");b.text=d.script(f,b.text);c()})},types:{},ext:".ejs",registerScript:function(a,b,c){return"$.View.preload('"+b+"',"+e.types["."+a].script(b,c)+");"},preload:function(a,b){e.cached[a]=function(c,
d){return b.call(c,c,d)}}});window.steal&&steal.type("view js",function(a,b){var c=e.types["."+a.type],d=p(a.rootSrc+"");a.text="steal('"+(c.plugin||"jquery/view/"+a.type)+"').then(function($){$.View.preload('"+d+"',"+a.text+");\n})";b()});var x,o,y,z,q,A,r,D={val:true,text:true};x=function(a){var b=h.fn[a];h.fn[a]=function(){var c=s(arguments),d,f,g=this;if(n(c[0])){c[0].done(function(i){o.call(g,[i],b)});return this}else if(y(c)){if(d=A(c)){f=c[d];c[d]=function(i){o.call(g,[i],b);f.call(g,i)};e.apply(e,
c);return this}c=e.apply(e,c);if(n(c)){c.done(function(i){o.call(g,[i],b)});return this}else c=[c]}return D[a]?b.apply(this,c):o.call(this,c,b)}};o=function(a,b){var c;for(var d in e.hookups)break;if(d&&a[0]&&z(a[0])){c=e.hookups;e.hookups={};a[0]=h(a[0])}b=b.apply(this,a);c&&r(a[0],c);return b};y=function(a){var b=typeof a[1];return typeof a[0]=="string"&&(b=="object"||b=="function")&&!q(a[1])};q=function(a){return a.nodeType||a.jquery};z=function(a){if(q(a))return true;else if(typeof a==="string"){a=
h.trim(a);return a.substr(0,1)==="<"&&a.substr(a.length-1,1)===">"&&a.length>=3}else return false};A=function(a){return typeof a[3]==="function"?3:typeof a[2]==="function"&&2};r=function(a,b){var c,d=0,f,g;a=a.filter(function(){return this.nodeType!=3});a=a.add("[data-view-id]",a);for(c=a.length;d<c;d++)if(a[d].getAttribute&&(f=a[d].getAttribute("data-view-id"))&&(g=b[f])){g(a[d],f);delete b[f];a[d].removeAttribute("data-view-id")}h.extend(e.hookups,b)};h.fn.hookup=function(){var a=e.hookups;e.hookups=
{};r(this,a);return this};h.each(["prepend","append","after","before","text","html","replaceWith","val"],function(a,b){x(b)})})(jQuery);
(function(i){var e={undHash:/_|-/,colons:/::/,words:/([A-Z]+)([A-Z][a-z])/g,lowUp:/([a-z\d])([A-Z])/g,dash:/([a-z\d])([A-Z])/g,replacer:/\{([^\}]+)\}/g,dot:/\./},l=function(a,b,c){return a[b]!==undefined?a[b]:c&&(a[b]={})},j=function(a){var b=typeof a;return a&&(b=="function"||b=="object")},m,k=i.String=i.extend(i.String||{},{getObject:m=function(a,b,c){a=a?a.split(e.dot):[];var f=a.length,d,h,g,n=0;b=i.isArray(b)?b:[b||window];if(f==0)return b[0];for(;d=b[n++];){for(g=0;g<f-1&&j(d);g++)d=l(d,a[g],
c);if(j(d)){h=l(d,a[g],c);if(h!==undefined){c===false&&delete d[a[g]];return h}}}},capitalize:function(a){return a.charAt(0).toUpperCase()+a.substr(1)},camelize:function(a){a=k.classize(a);return a.charAt(0).toLowerCase()+a.substr(1)},classize:function(a,b){a=a.split(e.undHash);for(var c=0;c<a.length;c++)a[c]=k.capitalize(a[c]);return a.join(b||"")},niceName:function(a){return k.classize(a," ")},underscore:function(a){return a.replace(e.colons,"/").replace(e.words,"$1_$2").replace(e.lowUp,"$1_$2").replace(e.dash,
"_").toLowerCase()},sub:function(a,b,c){var f=[];c=typeof c=="boolean"?!c:c;f.push(a.replace(e.replacer,function(d,h){d=m(h,b,c);if(j(d)){f.push(d);return""}else return""+d}));return f.length<=1?f[0]:f},_regs:e})})(jQuery);
(function(f){f.String.rsplit=function(a,e){for(var b=e.exec(a),c=[],d;b!==null;){d=b.index;if(d!==0){c.push(a.substring(0,d));a=a.slice(d)}c.push(b[0]);a=a.slice(b[0].length);b=e.exec(a)}a!==""&&c.push(a);return c}})(jQuery);
(function(i){var v=function(a){eval(a)},p=i.String.rsplit,k=i.extend,w=i.isArray,x=/\r\n/g,y=/\r/g,z=/\n/g,A=/\n/,B=/\\/g,q=/"/g,C=/'/g,D=/\t/g,E=/\{/g,F=/\}/g,r=/\s*\(([\$\w]+)\)\s*->([^\n]*)/,s=function(a){return a.replace(B,"\\\\").replace(z,"\\n").replace(q,'\\"').replace(D,"\\t")},G=function(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(q,"&#34;").replace(C,"&#39;")},m=i.View,n=function(a){var b=a.match(E);a=a.match(F);return(b?b.length:0)-(a?a.length:0)},
g=function(a){if(this.constructor!=g){var b=new g(a);return function(c,e){return b.render(c,e)}}if(typeof a=="function")this.template={fn:a};else{k(this,g.options,a);this.template=H(this.text,this.type,this.name)}};window.jQuery&&(jQuery.EJS=g);g.prototype.render=function(a,b){a=a||{};this._extra_helpers=b;b=new g.Helpers(a,b||{});return this.template.fn.call(a,a,b)};k(g,{text:function(a){if(typeof a=="string")return a;if(a===null||a===undefined)return"";var b=a.hookup&&function(c,e){a.hookup.call(a,
c,e)}||typeof a=="function"&&a||w(a)&&function(c,e){for(var d=0;d<a.length;d++)a[d].hookup?a[d].hookup(c,e):a[d](c,e)};if(b)return"data-view-id='"+m.hookup(b)+"'";return a.toString?a.toString():""},clean:function(a){return typeof a=="string"?G(a):typeof a=="number"?a:g.text(a)},options:{type:"<",ext:".ejs"}});var t=function(a,b,c){b=p(b,A);for(var e=0;e<b.length;e++)I(a,b[e],c)},I=function(a,b,c){a.lines++;b=p(b,a.splitter);for(var e,d=0;d<b.length;d++){e=b[d];e!==null&&c(e,a)}},J=function(a,b){var c=
{};k(c,{left:a+"%",right:"%"+b,dLeft:a+"%%",dRight:"%%"+b,eeLeft:a+"%==",eLeft:a+"%=",cmnt:a+"%#",scan:t,lines:0});c.splitter=new RegExp("("+[c.dLeft,c.dRight,c.eeLeft,c.eLeft,c.cmnt,c.left,c.right+"\n",c.right,"\n"].join(")|(").replace(/\[/g,"\\[").replace(/\]/g,"\\]")+")");return c},H=function(a,b,c){a=a.replace(x,"\n").replace(y,"\n");b=b||"<";var e=new g.Buffer(["var ___v1ew = [];"],[]),d="",u=function(h){e.push("___v1ew.push(",'"',s(h),'");')},l=null,o=function(){d=""},j=[];t(J(b,b==="["?"]":
">"),a||"",function(h,f){if(l===null)switch(h){case "\n":d+="\n";u(d);e.cr();o();break;case f.left:case f.eLeft:case f.eeLeft:case f.cmnt:l=h;d.length>0&&u(d);o();break;case f.dLeft:d+=f.left;break;default:d+=h;break}else switch(h){case f.right:switch(l){case f.left:h=n(d);f=j.length&&h==-1?j.pop():";";f==="));"&&e.push("return ___v1ew.join('')");e.push(d,f);h===1&&j.push(";");break;case f.eLeft:(h=n(d))&&j.push("));");if(r.test(d)){f=d.match(r);d="function(__){var "+f[1]+"=$(__);"+f[2]+"}"}e.push("___v1ew.push(",
"jQuery.EJS.clean(",d,h?"var ___v1ew = [];":"));");break;case f.eeLeft:(h=n(d))&&j.push("));");e.push("___v1ew.push(","jQuery.EJS.text(",d,h?"var ___v1ew = [];":"));");break}l=null;o();break;case f.dRight:d+=f.right;break;default:d+=h;break}});d.length>0&&e.push("___v1ew.push(",'"',s(d)+'");');a={out:"try { with(_VIEW) { with (_CONTEXT) {"+e.close()+" return ___v1ew.join('')}}}catch(e){e.lineNumber=null;throw e;}"};v.call(a,"this.fn = (function(_CONTEXT,_VIEW){"+a.out+"});\r\n//@ sourceURL="+c+".js");
return a};g.Buffer=function(a,b){this.line=[];this.script=[];this.post=b;this.push.apply(this,a)};g.Buffer.prototype={push:function(){this.line.push.apply(this.line,arguments)},cr:function(){this.script.push(this.line.join(""),"\n");this.line=[]},close:function(){if(this.line.length>0){this.script.push(this.line.join(""));this.line=[]}this.post.length&&this.push.apply(this,this.post);this.script.push(";");return this.script.join("")}};g.Helpers=function(a,b){this._data=a;this._extras=b;k(this,b)};
g.Helpers.prototype={plugin:function(){var a=i.makeArray(arguments),b=a.shift();return function(c){c=i(c);c[b].apply(c,a)}},view:function(a,b,c){c=c||this._extras;b=b||this._data;return m(a,b,c)}};m.register({suffix:"ejs",script:function(a,b){return"jQuery.EJS(function(_CONTEXT,_VIEW) { "+(new g({text:b,name:a})).template.out+" })"},renderer:function(a,b){return g({text:b,name:a})}})})(jQuery);
</script>
<script type="text/javascript" src="http://beebole.com/pure/wp-content/themes/BeeBole-pure/libs/pure.js"></script>
<script type="text/javascript">
 
var entityMap = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': '&quot;',
    "'": '&#39;',
    "/": '&#x2F;'
  };

function escapeHtml(string) {
  return String(string).replace(/[&<>"'\/]/g, function (s) {
    return entityMap[s];
  });
}
  
function renderNoTemplate(t, d) {

        //caption
        var html = t.replace('{0}', escapeHtml(d.title));

        //header
        var tbl = d.table;
        var ths = '<td style="width: 90px;"></td>';

        for (var i = tbl.heads.length - 1; i >= 0; i--) {
                ths += '<th>' + escapeHtml(tbl.heads[i]) + '</th>';
        }

        html = html.replace('{1}', ths);

        //body
        var trs = '';

        for (var i = tbl.sections.length - 1; i >= 0; i--) {

                trs += '<tr>';

                trs += '<td class="name" style="text-align: left;padding: 4px 0;">' + escapeHtml(tbl.sections[i].name) + '</td>';

                for (var j = tbl.sections[i].section.length - 1; j >= 0; j--) {
                        trs += '<td class="item" style="text-align: center;padding: 4px 0;">' + escapeHtml(tbl.sections[i].section[j]) + '</td>';
                }

                trs += '</tr>';
        }

        html = html.replace('{2}', trs);

        return html;

}
</script>

Setup

// template data
    var templates = {
      mustache: $("#template-mu").html().trim().slice(5, -3),
      underscore: $("#template-us").html().trim().slice(5, -3),
      ejs: $("#template-ejs").html().trim().slice(5, -3),
      pure: $p("#template-pure > table"),
    
      noTemplate: $("#no-template").html().trim().slice(5, -3),
      directive: {
        caption: "title",
        "thead th": {
          "head <- table.heads": {
            ".": "head"
          }
        },
        "tbody tr": {
          "s <- table.sections": {
            "td.name": "s.name",
            "td.item": {
              "section <- s.section": {
                ".": "section"
              }
            }
          }
        }
      }
    },
      // view data
      data = {
        title: "JavaScript Template Comparison",
        table: {
          heads: ["Mustache", "Hogan", "Handlebars", "Underscore", "EJS", "jQuery.EJS", "PURE", "No Template"],
          sections: [{
            name: "delimiter",
            section: ["○", "○", "×", "○", "○", "○", "-", "-"]
          }, {
            name: "logic-less",
            section: ["○", "○", "○", "×", "×", "×", "◎", "◎"]
          }, {
            name: "precompile",
            section: ["×", "○", "○", "×", "○", "×", "×", "×"]
          }, {
            name: "escape",
            section: ["○", "○", "○", "○", "○", "○", "△", "△"]
          }, {
            name: "method",
            section: ["○", "○", "◎", "○", "○", "◎", "○", "○"]
          }, {
            name: "standalone",
            section: ["○", "○", "○", "○", "○", "×", "×", "×"]
          }, {
            name: "partials",
            section: ["○", "○", "○", "×", "△", "×", "×", "×"]
          }]
        }
      };
    
    // Precompile Hogan.js
    var hogan_comp = Hogan.compile(templates.mustache);
    
    // Precompile Handlebars.js
    var handlebars_comp = Handlebars.compile(templates.mustache);
    
    // Precompile Underscore.js
    var underscore_comp = _.template(templates.underscore);
    
    // Precompile EJS
    var ejs_comp = ejs.compile(templates.ejs);
    
    // Precompile jQuery.EJS
    var jqejs_comp = new $.EJS({
      text: templates.ejs
    });
    
    // Precompile PURE
    var pure_comp = templates.pure.compile(templates.directive);

Test runner

Ready to run.

Testing in
TestOps/sec
Mustache
Mustache.render(templates.mustache, data);
ready
Hogan.js
Hogan.compile(templates.mustache).render(data);
ready
Handlebars.js
Handlebars.compile(templates.mustache)(data);
ready
Underscore.js
_.template(templates.underscore)(data);
ready
EJS
ejs.compile(templates.ejs)(data);
ready
jQuery.EJS
new $.EJS({
  text: templates.ejs
}).render(data);
ready
PURE
templates.pure.compile(templates.directive)(data);
ready
Hogan.js (precompiled)
hogan_comp.render(data);
ready
Handlebars.js (precompiled)
handlebars_comp(data);
ready
Underscore.js (precompiled)
underscore_comp(data);
ready
EJS (precompiled)
ejs_comp(data);
ready
jQuery.EJS (precompiled)
jqejs_comp.render(data);
ready
PURE (precompiled)
pure_comp(data);
ready
No Template
renderNoTemplate(templates.noTemplate, data);
ready
Knockout
ko.applyBindings(data, document.getElementById('template-ko');
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.