JavaScript template language shootoff

JavaScript performance comparison

Revision 535 of this test case created by Victor

Info

A limited comparison of some popular JavaScript templating engines on a short template: 6 header tags, and 10 list items. Compared templating engines:

Preparation code

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

<script src="https://raw.github.com/documentcloud/underscore/master/underscore.js"></script>

<script src="http://ajax.cdnjs.com/ajax/libs/mustache.js/0.3.0/mustache.min.js"></script>

<script src="https://github.com/downloads/wycats/handlebars.js/handlebars.1.0.0.beta.3.js"></script>

<script src="http://cdn.kendostatic.com/2012.1.322/js/kendo.all.min.js"></script>

<script src="http://jashkenas.github.com/coffee-script/extras/coffee-script.js"></script>

<script src="http://borismoore.github.com/jsrender/jsrender.js"></script>

<script src="https://raw.github.com/twitter/hogan.js/master/lib/template.js"></script>
<script src="https://raw.github.com/twitter/hogan.js/master/lib/compiler.js"></script>
<script src="https://github.com/olado/doT/raw/master/doT.js">
</script>

<script type="text/javascript" src="https://raw.github.com/PaulGuo/Juicer/master/src/juicer.js"></script>

<script type="text/javascript" src="https://raw.github.com/kirbysayshi/Vash/master/build/vash.js"></script>

<script type="text/javascript" src="https://raw.github.com/akdubya/dustjs/master/dist/dust-full-0.3.0.js"></script>
<script src="https://raw.github.com/visionmedia/jade/master/jade.min.js"></script>
<script src="http://github.com/creationix/haml-js/raw/master/lib/haml.js"></script>
<!--External Template Definitions-->
<script type="text/x-kendo-template" id="kendoUIextTemplate">
<div>
<h1 class='header'>#= data.header #</h1>
<h2 class='header2'>#= data.header2 #</h2>
<h3 class='header3'>#= data.header3 #</h3>
<h4 class='header4'>#= data.header4 #</h4>
<h5 class='header5'>#= data.header5 #</h5>
<h6 class='header6'>#= data.header6 #</h6>
<ul class='list'>
   # for (var i = 0, l = data.list.length; i < l; i++) { #
   <li class='item'>#= data.list[i] #</li>
   # } #
</ul>
</div>
</script>
<script>
  window.sharedVariables = {
   header: "Header",
   header2: "Header2",
   header3: "Header3",
   header4: "Header4",
   header5: "Header5",
   header6: "Header6",
   list: ['1000000000', '2', '3', '4', '5', '6', '7', '8', '9', '10']
  };
 

  //JsRender compiled template (no encoding)
  window.jsRenderTemplate = $.templates("<div><h1 class='header'>{{:header}}</h1><h2 class='header2'>{{:header2}}</h2><h3 class='header3'>{{:header3}}</h3><h4 class='header4'>{{:header4}}</h4><h5 class='header5'>{{:header5}}</h5><h6 class='header6'>{{:header6}}</h6><ul class='list'>{{for list}}<li class='item'>{{:#data}}</li>{{/for}}</ul></div>");
 
  window.mustacheTemplate = "<div><h1 class='header'>{{{header}}}</h1><h2 class='header2'>{{{header2}}}</h2><h3 class='header3'>{{{header3}}}</h3><h4 class='header4'>{{{header4}}}</h4><h5 class='header5'>{{{header5}}}</h5><h6 class='header6'>{{{header6}}}</h6><ul class='list'>{{#list}}<li class='item'>{{{.}}}</li>{{/list}}</ul></div>";
 
  window.handlebarsTemplate = Handlebars.compile("<div><h1 class='header'>{{header}}</h1><h2 class='header2'>{{header2}}</h2><h3 class='header3'>{{header3}}</h3><h4 class='header4'>{{header4}}</h4><h5 class='header5'>{{header5}}</h5><h6 class='header6'>{{header6}}</h6><ul class='list'>{{#each list}}<li class='item'>{{this}}</li>{{/each}}</ul></div>");
 
  window.kendouiTemplate = kendo.template("<div><h1 class='header'>#= data.header #</h1><h2 class='header2'>#= data.header2 #</h2><h3 class='header3'>#= data.header3 #</h3><h4 class='header4'>#= data.header4 #</h4><h5 class='header5'>#= data.header5 #</h5><h6 class='header6'>#= data.header6 #</h6><ul class='list'># for (var i = 0, l = data.list.length; i < l; i++) { #<li class='item'>#= data.list[i] #</li># } #</ul></div>", {useWithBlock:true});
 
  window.kendouiTemplate2 = kendo.template("<div><h1 class='header'>#= data.header #</h1><h2 class='header2'>#= data.header2 #</h2><h3 class='header3'>#= data.header3 #</h3><h4 class='header4'>#= data.header4 #</h4><h5 class='header5'>#= data.header5 #</h5><h6 class='header6'>#= data.header6 #</h6><ul class='list'># for (var i = 0, l = data.list.length; i < l; i++) { #<li class='item'>#= data.list[i] #</li># } #</ul></div>", {useWithBlock:false});
 
  //Use external template definition
  window.kendoUIAlt = kendo.template($("#kendoUIextTemplate").html());
  window.kendoUIAlt2 = kendo.template($("#kendoUIextTemplate").html(), {useWithBlock:false});
 
  window.underscoreTemplate = _.template("<div><h1 class='header'><%= header %></h1><h2 class='header2'><%= header2 %></h2><h3 class='header3'><%= header3 %></h3><h4 class='header4'><%= header4 %></h4><h5 class='header5'><%= header5 %></h5><h6 class='header6'><%= header6 %></h6><ul class='list'><% for (var i = 0, l = list.length; i < l; i++) { %><li class='item'><%= list[i] %></li><% } %></ul></div>");
 
  window.underscoreTemplateNoWith = _.template("<div><h1 class='header'><%= data.header %></h1><h2 class='header2'><%= data.header2 %></h2><h3 class='header3'><%= data.header3 %></h3><h4 class='header4'><%= data.header4 %></h4><h5 class='header5'><%= data.header5 %></h5><h6 class='header6'><%= data.header6 %></h6><ul class='list'><% for (var i = 0, l = data.list.length; i < l; i++) { %><li class='item'><%= data.list[i] %></li><% } %></ul></div>", null, {variable: 'data'});

  window.baseHtml = "<div><h1 class='header'></h1><h2 class='header2'></h2><h3 class='header3'></h3><h4 class='header4'></h4><h5 class='header5'></h5><h6 class='header6'></h6><ul class='list'><li class='item'></li></ul></div>";
 
window.doTtemplate = doT.template("<div><h1 class='header'>{{=it.header}}</h1><h2 class='header2'>{{=it.header2}}</h2><h3 class='header3'>{{=it.header3}}</h3><h4 class='header4'>{{=it.header4}}</h4><h5 class='header5'>{{=it.header5}}</h5><h6 class='header6'>{{=it.header6}}</h6><ul class='list'>{{for(var i = 0,l=it.list.length;i<l;i++) { }}<li class='item'>{{=it.list[i]}}</li>{{ } }}</ul></div>");

  //Resig Template Function (modified to support ')
  function tmpl(str) {
              var strFunc =
              "var p=[];" +
                          "with(obj){p.push('" +
 
              str.replace(/[\r\t\n]/g, " ")
                 .replace(/'(?=[^#]*#>)/g, "\t")
                 .split("'").join("\\'")
                 .split("\t").join("'")
                 .replace(/<#=(.+?)#>/g, "',$1,'")
                 .split("<#").join("');")
                 .split("#>").join("p.push('")
                 + "');}return p.join('');";
 
              return new Function("obj", strFunc);
          }
 
  window.resig = tmpl("<div><h1 class='header'><#= header #></h1><h2 class='header2'><#= header2 #></h2><h3 class='header3'><#= header3 #></h3><h4 class='header4'><#= header4 #></h4><h5 class='header5'><#= header5 #></h5><h6 class='header6'><#= header6 #></h6><ul class='list'><# for (var i = 0, l = list.length; i < l; i++) { #><li class='item'><#= list[i] #></li><# } #></ul></div>");
 
  //Resig modified template function (no "with" block)
  function tmpl2(str) {
              var strFunc =
              "var p=[];" +
                          "p.push('" +
 
              str.replace(/[\r\t\n]/g, " ")
                 .replace(/'(?=[^#]*#>)/g, "\t")
                 .split("'").join("\\'")
                 .split("\t").join("'")
                 .replace(/<#=(.+?)#>/g, "',$1,'")
                 .split("<#").join("');")
                 .split("#>").join("p.push('")
                 + "');return p.join('');";
 
              return new Function("data", strFunc);
          }
 
  window.resig2 = tmpl2("<div><h1 class='header'><#= data.header #></h1><h2 class='header2'><#= data.header2 #></h2><h3 class='header3'><#= data.header3 #></h3><h4 class='header4'><#= data.header4 #></h4><h5 class='header5'><#= data.header5 #></h5><h6 class='header6'><#= data.header6 #></h6><ul class='list'><# for (var i = 0, l = data.list.length; i < l; i++) { #><li class='item'><#= data.list[i] #></li><# } #></ul></div>");

  window.hoganTemplate = Hogan.compile("<div><h1 class='header'>{{{header}}}</h1><h2 class='header2'>{{{header2}}}</h2><h3 class='header3'>{{{header3}}}</h3><h4 class='header4'>{{{header4}}}</h4><h5 class='header5'>{{{header5}}}</h5><h6 class='header6'>{{{header6}}}</h6><ul class='list'>{{#list}}<li class='item'>{{{.}}}</li>{{/list}}</ul></div>");




window.juicerTemplate = "<div><h1 class='header'>$${data.header}</h1><h2 class='header2'>$${data.header2}</h2><h3 class='header3'>$${data.header3}</h3><h4 class='header4'>$${data.header4}</h4><h5 class='header5'>$${data.header5}</h5><h6 class='header6'>$${data.header6}</h6><ul class='list'>{@each data.list as it}<li class='item'>$${it}</li>{@/each}</ul></div>";

window.juicerTpl=juicer.compile(juicerTemplate,{cache:true,errorhandling:false,detection:false});

window.vashtemplate = vash.compile("<div><h1 class='header'>@model.header</h1><h2 class='header2'>@model.header2</h2><h3 class='header3'>@model.header3</h3><h4 class='header4'>@model.header4</h4><h5 class='header5'>@model.header5</h5><h6 class='header6'>@model.header6</h6><ul class='list'>@model.list.forEach(function(m) {<li class='item'>@m</li> })</ul></div>");

window.dusttemplate = dust.compileFn("<div><h1 class='header'>{header}</h1><h2 class='header2'>{header2}</h2><h3 class='header3'>{header3}</h3><h4 class='header4'>{header4}</h4><h5 class='header5'>{header5}</h5><h6 class='header6'>{header6}</h6><ul class='list'>{#list}<li class='item'>{.}</li>{/list}</ul></div>");

window.jadeTemplate = jade.compile("div\n  h1.header!= self.header\n  h2.header2!= self.header2\n  h3.header3!= self.header3\n  h4.header4!= self.header4\n  h5.header5!= self.header5\n  h6.header6!= self.header6\n  ul.list\n    - each item in self.list\n      li.item!= item", { compileDebug: false, self: true});

window.hamlTemplate = Haml("%div\n  %h1.header= header\n  %h2.header2= header2\n  %h3.header3= header3\n  %h4.header4= header4\n  %h5.header5= header5\n  %h6.header6= header6\n  %ul.list\n    :each item in list\n      %li.item= item");
(function(){
  this.templates || (this.templates = {});
  this.templates["koffee"] = function anonymous(data) {
var a,div,em,h1,h2,h3,h4,h5,h6,head,header,i,li,p,s,th,u,ul;a = function(){return __ck.tag('a', arguments);};div = function(){return __ck.tag('div', arguments);};em = function(){return __ck.tag('em', arguments);};h1 = function(){return __ck.tag('h1', arguments);};h2 = function(){return __ck.tag('h2', arguments);};h3 = function(){return __ck.tag('h3', arguments);};h4 = function(){return __ck.tag('h4', arguments);};h5 = function(){return __ck.tag('h5', arguments);};h6 = function(){return __ck.tag('h6', arguments);};head = function(){return __ck.tag('head', arguments);};header = function(){return __ck.tag('header', arguments);};i = function(){return __ck.tag('i', arguments);};li = function(){return __ck.tag('li', arguments);};p = function(){return __ck.tag('p', arguments);};s = function(){return __ck.tag('s', arguments);};th = function(){return __ck.tag('th', arguments);};u = function(){return __ck.tag('u', arguments);};ul = function(){return __ck.tag('ul', arguments);};var __slice = Array.prototype.slice;var __hasProp = Object.prototype.hasOwnProperty;var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };var __extends = function(child, parent) {  for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }  function ctor() { this.constructor = child; }  ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype;  return child; };var __indexOf = Array.prototype.indexOf || function(item) {  for (var i = 0, l = this.length; i < l; i++) {    if (this[i] === item) return i;  } return -1; };
    var coffeescript, comment, doctype, h, ie, tag, text, yield, __ck, _ref, _ref2;
    if (data == null) {
      data = {};
    }
    if ((_ref = data.format) == null) {
      data.format = false;
    }
    if ((_ref2 = data.autoescape) == null) {
      data.autoescape = false;
    }
    __ck = {
      buffer: [],
      esc: function(txt) {
        if (data.autoescape) {
          return h(txt);
        } else {
          return String(txt);
        }
      },
      tabs: 0,
      repeat: function(string, count) {
        return Array(count + 1).join(string);
      },
      indent: function() {
        if (data.format) {
          return text(this.repeat('  ', this.tabs));
        }
      },
      tag: function(name, args) {
        var combo, i, _i, _len;
        combo = [name];
        for (_i = 0, _len = args.length; _i < _len; _i++) {
          i = args[_i];
          combo.push(i);
        }
        return tag.apply(data, combo);
      },
      render_idclass: function(str) {
        var c, classes, i, id, _i, _j, _len, _len2, _ref3;
        classes = [];
        _ref3 = str.split('.');
        for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
          i = _ref3[_i];
          if (__indexOf.call(i, '#') >= 0) {
            id = i.replace('#', '');
          } else {
            if (i !== '') {
              classes.push(i);
            }
          }
        }
        if (id) {
          text(" id=\"" + id + "\"");
        }
        if (classes.length > 0) {
          text(" class=\"");
          for (_j = 0, _len2 = classes.length; _j < _len2; _j++) {
            c = classes[_j];
            if (c !== classes[0]) {
              text(' ');
            }
            text(c);
          }
          return text('"');
        }
      },
      render_attrs: function(obj, prefix) {
        var k, v, _results;
        if (prefix == null) {
          prefix = '';
        }
        _results = [];
        for (k in obj) {
          v = obj[k];
          if (typeof v === 'boolean' && v) {
            v = k;
          }
          if (typeof v === 'function') {
            v = "(" + v + ").call(this);";
          }
          _results.push(typeof v === 'object' && !(v instanceof Array) ? this.render_attrs(v, prefix + k + '-') : v ? text(" " + (prefix + k) + "=\"" + (this.esc(v)) + "\"") : void 0);
        }
        return _results;
      },
      render_contents: function(contents) {
        var result;
        switch (typeof contents) {
          case 'string':
          case 'number':
          case 'boolean':
            return text(this.esc(contents));
          case 'function':
            if (data.format) {
              text('\n');
            }
            this.tabs++;
            result = contents.call(data);
            if (typeof result === 'string') {
              this.indent();
              text(this.esc(result));
              if (data.format) {
                text('\n');
              }
            }
            this.tabs--;
            return this.indent();
        }
      },
      render_tag: function(name, idclass, attrs, contents) {
        this.indent();
        text("<" + name);
        if (idclass) {
          this.render_idclass(idclass);
        }
        if (attrs) {
          this.render_attrs(attrs);
        }
        if (__indexOf.call(this.self_closing, name) >= 0) {
          text(' />');
          if (data.format) {
            text('\n');
          }
        } else {
          text('>');
          this.render_contents(contents);
          text("</" + name + ">");
          if (data.format) {
            text('\n');
          }
        }
        return null;
      }
    };
    tag = function() {
      var a, args, attrs, contents, idclass, name, _i, _len;
      name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      for (_i = 0, _len = args.length; _i < _len; _i++) {
        a = args[_i];
        switch (typeof a) {
          case 'function':
            contents = a;
            break;
          case 'object':
            attrs = a;
            break;
          case 'number':
          case 'boolean':
            contents = a;
            break;
          case 'string':
            if (args.length === 1) {
              contents = a;
            } else {
              if (a === args[0]) {
                idclass = a;
              } else {
                contents = a;
              }
            }
        }
      }
      return __ck.render_tag(name, idclass, attrs, contents);
    };
    yield = function(f) {
      var old_buffer, temp_buffer;
      temp_buffer = [];
      old_buffer = __ck.buffer;
      __ck.buffer = temp_buffer;
      f();
      __ck.buffer = old_buffer;
      return temp_buffer.join('');
    };
    h = function(txt) {
      return String(txt).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
    };
    doctype = function(type) {
      if (type == null) {
        type = 'default';
      }
      text(__ck.doctypes[type]);
      if (data.format) {
        return text('\n');
      }
    };
    text = function(txt) {
      __ck.buffer.push(String(txt));
      return null;
    };
    comment = function(cmt) {
      text("<!--" + cmt + "-->");
      if (data.format) {
        return text('\n');
      }
    };
    coffeescript = function(param) {
      switch (typeof param) {
        case 'function':
          return script("" + __ck.coffeescript_helpers + "(" + param + ").call(this);");
        case 'string':
          return script({
            type: 'text/coffeescript'
          }, function() {
            return param;
          });
        case 'object':
          param.type = 'text/coffeescript';
          return script(param);
      }
    };
    ie = function(condition, contents) {
      __ck.indent();
      text("<!--[if " + condition + "]>");
      __ck.render_contents(contents);
      text("<![endif]-->");
      if (data.format) {
        return text('\n');
      }
    };
    __ck.doctypes = {"5":"<!DOCTYPE html>","default":"<!DOCTYPE html>","xml":"<?xml version=\"1.0\" encoding=\"utf-8\" ?>","transitional":"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">","strict":"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">","frameset":"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">","1.1":"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">","basic":"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML Basic 1.1//EN\" \"http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd\">","mobile":"<!DOCTYPE html PUBLIC \"-//WAPFORUM//DTD XHTML Mobile 1.2//EN\" \"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd\">","ce":"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"ce-html-1.0-transitional.dtd\">"};__ck.coffeescript_helpers = "var __slice = Array.prototype.slice;var __hasProp = Object.prototype.hasOwnProperty;var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };var __extends = function(child, parent) {  for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }  function ctor() { this.constructor = child; }  ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype;  return child; };var __indexOf = Array.prototype.indexOf || function(item) {  for (var i = 0, l = this.length; i < l; i++) {    if (this[i] === item) return i;  } return -1; };";__ck.self_closing = ["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","basefont","frame"];(function(){
div(function() {
  h1('.header', function() {
    return this.header;
  });
  h2('.header2', function() {
    return this.header2;
  });
  h3('.header3', function() {
    return this.header3;
  });
  h4('.header4', function() {
    return this.header4;
  });
  h5('.header5', function() {
    return this.header5;
  });
  h6('.header6', function() {
    return this.header6;
  });
  return ul('.list', function() {
    var item, _i, _len, _ref, _results;
    _ref = this.list;
    _results = [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      item = _ref[_i];
      _results.push(li('.item', function() {
        return item;
      }));
    }
    return _results;
  });
});
}).call(data);return __ck.buffer.join('');
};
}).call(this);

window.coffeekupTemplate = templates.koffee;

</script>


<div id="template" style="display:none"></div>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
doT.js
$('#template').empty().append(doTtemplate(sharedVariables));
++sharedVariables.list[0];
pending…
Handlebars.js
$('#template').empty().append(handlebarsTemplate(sharedVariables));
++sharedVariables.list[0];
pending…
Underscore.js Template
$('#template').empty().append(underscoreTemplate(sharedVariables));
++sharedVariables.list[0];
pending…
Resig Micro Templates (modified)
$('#template').empty().append(resig(sharedVariables));
++sharedVariables.list[0];
pending…
Resig Micro Templates (No "with" block)
$('#template').empty().append(resig2(sharedVariables));
++sharedVariables.list[0];
pending…
Underscore.js Template (No "with")
$('#template').empty().append(underscoreTemplateNoWith(sharedVariables));
++sharedVariables.list[0];
pending…
Hogan.js
$('#template').empty().append(hoganTemplate.render(sharedVariables));
++sharedVariables.list[0];
pending…
JsRender
$('#template').empty().append(jsRenderTemplate.render(sharedVariables));
++sharedVariables.list[0];
pending…
Mustache.js Template
$('#template').empty().append(Mustache.to_html(mustacheTemplate, sharedVariables));
++sharedVariables.list[0];
pending…
Jade Template (No "with")
$('#template').empty().append(jadeTemplate(sharedVariables));
++sharedVariables.list[0];
pending…
CoffeeKup compiled template
$('#template').empty().append(coffeekupTemplate(sharedVariables));
++sharedVariables.list[0];
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment