JS Template Engines Performance (v100)

Revision 100 of this benchmark created by Kingkong on


Description

Preparation HTML

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

<script src="https://rawgithub.com/danactive/js-template-engines-performance/master/lib/underscore-1.4.4.min.js"></script>

<script src="https://rawgithub.com/danactive/js-template-engines-performance/master/lib/mustache-0.7.2.js"></script>

<script src="https://rawgithub.com/danactive/js-template-engines-performance/master/lib/handlebars.1.0.0-rc.3.js"></script>

<script src="https://rawgithub.com/danactive/js-template-engines-performance/master/lib/kendo.core.min.2013.1.319.js"></script>

<script src="https://rawgithub.com/danactive/js-template-engines-performance/master/lib/dust-runtime-1.2.3.js"></script>

<script src="https://rawgithub.com/danactive/js-template-engines-performance/master/lib/hogan-2.0.0.js"></script>

<script src="https://jsmart.googlecode.com/files/smart-2.9.min.js"></script>

<script src="http://paularmstrong.github.io/swig/js/swig.min.js"></script>

<script src="http://mozilla.github.io/nunjucks/files/nunjucks.min.js"></script>




<script>            
var aro = {};

aro.string = new function ()
{
    /**
     * @var {Object}
     */
    this.escapes_html = 
    {
        '\\': '&#92;',
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#39;',
        "/": '&#47;'
    };
    
    /**
     * @var {RexExp}
     */
    this.escape_html_reg = /[&<>"'\/\\]/g;
    
    /**
     * @param {String} string
     * @returns {String}
     */
    this.escapeHtml = function(string) 
    {
        return string.replace(
            this.escape_html_reg,
            function (match)
            {
                return aro.string.escapes_html[match];
            }
        );
    };
};


aro.template = new function()
{
    /**
     * @var {String}
     */
    this.opener = '<#',
                
    /**
     * @var {String}
     */
    this.closer = '#>';
    
    /**
     * Retourne le corp d'une fonction correspondante à un template
     * 
     * @param {String} template
     * @param {Object} options
     * @returns {String}
     */
    function transform(template, options)
    {
        var fn_body = 'var r=\'\', t;',
            code = false,
            code_op = 0,//1 == concat, 2 == concat + escape
            is_last_code_block = true,
            is_escape = false,
            char = '',
            from = 0,
            opener_chars = (options.opener || aro.template.opener).split(''),
            closer_chars = (options.closer || aro.template.closer).split(''),
            opener_chars_len = opener_chars.length,
            closer_chars_len = closer_chars.length,
            frag,
            j, to, l, char;

        source : for(to = 0, l = template.length - (closer_chars_len - 1); to < l; to++)
        {
            char = template[to];

            //ouverture block code
            if(!code)
            {
                if(!is_escape && char === opener_chars[0])
                {
                    for(j = 1; j < opener_chars_len; j++)
                    {
                        if(opener_chars[j] !== template[to + j])
                        {
                            continue source;
                        }
                    }
                    
                    code = true;
                    if(from < to)
                    {
                        fn_body += (is_last_code_block ? 'r+=' : '+') + JSON.stringify(template.slice(from, to));
                    }
                    
                    to += opener_chars_len - 1;
                    from = to;
                }
            }
            //code concat 
            else if(!code_op && from === to - 1)
            {//alert(char);
                if(char === '=')
                {
                    from++;
                    code_op = 1;
                    is_last_code_block = false;
                }
                else if(char === '-')
                {
                    from++;
                    code_op = 2;
                    is_last_code_block = false;
                }
                else
                {
                    code_op = 0;
                    is_last_code_block = true;
                    from && (fn_body += ';');
                }
            }   
            //fermeture block code
            else if(!is_escape && char === closer_chars[0])
            {
                for(j = 1; j < closer_chars_len; j++)
                {
                    if(closer_chars[j] !== template[to + j])
                    {
                        continue source;
                    }
                }
                
                if(from < to)
                {
                    frag = template.slice(from + opener_chars_len, to);//alert(frag);
                    fn_body += !code_op  ? frag : (is_last_code_block ? 'r+=' : '+') +
                           (code_op === 2  ? 'aro.string.escapeHtml((t=(' + frag + '))!= null?t:\'\')'  : '((t=(' + frag + '))!= null?t:\'\')');
                }
                
                code = false;
                code_op = 0;
                
                from = to + closer_chars_len;
                to = from;
            }

            is_escape = char === '\\';
        }

        if(from < to - 1)
        {
            fn_body += (is_last_code_block ? 'r+=' : '+') + JSON.stringify(template.slice(from, to)) + ';';
        }
        
        return fn_body += 'return r;';
    }
    
    /**
     * Retourne la fonction correspondante au template sous forme de chaine
     *  
     * @param {String} template
     * @param {Object} options
     * @param {String} name
     * @returns {String}
     */
    this.getSource = function(template, options, name)
    {
        return 'function ' + (name || '') + '(data){' + transform(template, options || {}) + '}';
    };
    
    /**
     * Retourne la fonction correspondante au template
     *  
     * @param {String} template
     * @param {Object} options
     * @returns {Function}
     */
    this.compile = function(template, options)
    {
        return Function(
            'data',
            transform(template, options || {})
        );
    };
};

</script>
<!--Extern
al 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 id="test_tpl" type="text/x-jsmart-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'>{foreach from=$list item=list_item}<li class='item'>{$list_item}</li>{/foreach}</ul></div>
</script>


        
<script>
  window.sharedVariables = {
    header: "Header",
    header2: "Header2",
    header3: "Header3",
    header4: "Header4",
    header5: "Header5",
    header6: "Header6",
    list: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
  };


  window.nunjucksTemplate = "<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 item in list %}<li class='item'>{{ item }}</li>{% endfor %}</ul></div>";

  window.nunjucksCompiledTemplate = new nunjucks.Template(nunjucksTemplate, true);


  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.mustacheCompiledTemplate = Mustache.compile(mustacheTemplate);



  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.underscoreTemplateEach = _.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'><% _.each(list, function(item){ %><li class='item'><%= item %></li><% }); %></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.swigTemplate = "<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 item in list %}<li class='item'>{{ item }}</li>{% endfor %}</ul></div>";

  window.swigPrecompiledTemplate = swig.compile(swigTemplate);



  Handlebars.template((function(){var a=Handlebars.template,b=Handlebars.templates=Handlebars.templates||{};b["template-handlebars.html"]=a(function(a,b,c,d,e){function k(a,b){var c="";return c+="<li class='item'>"+i(typeof a===h?a.apply(a):a)+"</li>",c}this.compilerInfo=[2,">= 1.0.0-rc.3"],c=c||a.helpers,e=e||{};var f="",g,h="function",i=this.escapeExpression,j=this;f+="<div><h1 class='header'>",(g=c.header)?g=g.call(b,{hash:{},data:e}):(g=b.header,g=typeof g===h?g.apply(b):g),f+=i(g)+"</h1><h2 class='header2'>",(g=c.header2)?g=g.call(b,{hash:{},data:e}):(g=b.header2,g=typeof g===h?g.apply(b):g),f+=i(g)+"</h2><h3 class='header3'>",(g=c.header3)?g=g.call(b,{hash:{},data:e}):(g=b.header3,g=typeof g===h?g.apply(b):g),f+=i(g)+"</h3><h4 class='header4'>",(g=c.header4)?g=g.call(b,{hash:{},data:e}):(g=b.header4,g=typeof g===h?g.apply(b):g),f+=i(g)+"</h4><h5 class='header5'>",(g=c.header5)?g=g.call(b,{hash:{},data:e}):(g=b.header5,g=typeof g===h?g.apply(b):g),f+=i(g)+"</h5><h6 class='header6'>",(g=c.header6)?g=g.call(b,{hash:{},data:e}):(g=b.header6,g=typeof g===h?g.apply(b):g),f+=i(g)+"</h6><ul class='list'>",g=c.each.call(b,b.list,{hash:{},inverse:j.noop,fn:j.program(1,k,e),data:e});if(g||g===0)f+=g;return f+="</ul></div>",f})})());

  //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 o;" +
                          "o='" +
  
              str.replace(/[\r\t\n]/g, " ")
                 .replace(/'(?=[^#]*#>)/g, "\t")
                 .split("'").join("\\'")
                 .split("\t").join("'")
                 .replace(/<#=(.+?)#>/g, "'+data.$1+'")
                 .split("<#").join("';")
                 .split("#>").join("o+='")
                 + "';return o;";
  
              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.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.kendoUIAlt1 = kendo.template($("#kendoUIextTemplate").html());
  window.kendoUIAlt2 = kendo.template($("#kendoUIextTemplate").html(), {useWithBlock: false});

  window.dustTemplatePrecompiled = (function(){dust.register("dustTemplatePrecompiled",body_0);function body_0(chk,ctx){return chk.write("<div><h1 class='header'>").reference(ctx.get("header"),ctx,"h").write("</h1><h2 class='header2'>").reference(ctx.get("header2"),ctx,"h").write("</h2><h3 class='header3'>").reference(ctx.get("header3"),ctx,"h").write("</h3><h4 class='header4'>").reference(ctx.get("header4"),ctx,"h").write("</h4><h5 class='header5'>").reference(ctx.get("header5"),ctx,"h").write("</h5><h6 class='header6'>").reference(ctx.get("header6"),ctx,"h").write("</h6><ul class='list'>").section(ctx.get("list"),ctx,{"block":body_1},null).write("</ul></div>");}function body_1(chk,ctx){return chk.write("<li class='item'>").reference(ctx.getPath(true,[]),ctx,"h").write("</li>");}return body_0;})();

  window.dustTemplate2 = function(content) {
    dust.loadSource(dustTemplatePrecompiled, "dustTemplatePrecompiled");
    dust.render("dustTemplatePrecompiled", content, function() {});
  };

  window.dustTemplatePrecompiledCached = (function(){dust.register("dustTemplatePrecompiledCached",body_0);function body_0(chk,ctx){return chk.write("<div><h1 class='header'>").reference(ctx.get("header"),ctx,"h").write("</h1><h2 class='header2'>").reference(ctx.get("header2"),ctx,"h").write("</h2><h3 class='header3'>").reference(ctx.get("header3"),ctx,"h").write("</h3><h4 class='header4'>").reference(ctx.get("header4"),ctx,"h").write("</h4><h5 class='header5'>").reference(ctx.get("header5"),ctx,"h").write("</h5><h6 class='header6'>").reference(ctx.get("header6"),ctx,"h").write("</h6><ul class='list'>").section(ctx.get("list"),ctx,{"block":body_1},null).write("</ul></div>");}function body_1(chk,ctx){return chk.write("<li class='item'>").reference(ctx.getPath(true,[]),ctx,"h").write("</li>");}return body_0;})();

    dust.loadSource(dustTemplatePrecompiledCached, "dustTemplatePrecompiledCached");

  window.dustTemplate3 = function(content) {
    dust.render("dustTemplatePrecompiledCached", content, function() {});
  };

  window.hoganCompile = 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.jsmartObj = new jSmart(document.getElementById('test_tpl').innerHTML);
   window.jsmartFetch = function(data) {
     jsmartObj.fetch(data);
   };
  
window.Fmustache= function(data){
var o="<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'>";
if(data.hasOwnProperty('list') && data.list){
  if(typeof data.list === 'object'){
    if(data.list instanceof Array){
      for(var __i=0;__i<data.list.length;__i++){
        o+="<li class='item'>"+data.list[__i]+"</li>";
      }
    }
  }

}
o+="</ul></div>";
return o;
}

window.aroTemplate= aro.template.compile(
        "<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>", 
    {
        opener:'#', 
        closer : '#'
    }
);
</script>

Test runner

Ready to run.

Testing in
TestOps/sec
mustache.js
Mustache.render(mustacheTemplate, sharedVariables);
ready
underscore.js
underscoreTemplate(sharedVariables);
ready
handlebar.js
handlebarsTemplate(sharedVariables);
ready
handlebar.js (precompiled)
Handlebars.templates["template-handlebars.html"](sharedVariables);
ready
dust.js (precompiled)
dustTemplate2(sharedVariables);
ready
dust.js (precompiled, cached)
dustTemplate3(sharedVariables);
ready
Hogan.js
hoganCompile.render(sharedVariables);
ready
Kendo UI Templates (Default)
kendouiTemplate(sharedVariables);
ready
Kendo UI Templates (No "with" block)
kendouiTemplate2(sharedVariables);
ready
Resig Micro Templates (modified)
resig(sharedVariables);
ready
Resig Micro Templates (No "with" block)
resig2(sharedVariables);
ready
jsmart
jsmartFetch(sharedVariables);
ready
Fmustache
Fmustache(sharedVariables);
ready
Swig
swig.render(swigTemplate, sharedVariables);
ready
Swig Precompiled
swigPrecompiledTemplate(sharedVariables);
ready
underscore.js (_.each)
underscoreTemplateEach(sharedVariables);
ready
Mustache (compiled)
mustacheCompiledTemplate(sharedVariables);
ready
Nunjucks (compiled)
nunjucksCompiledTemplate.render(sharedVariables);
ready
Nunjucks
nunjucks.renderString(nunjucksTemplate, sharedVariables);
ready
aro
aroTemplate(sharedVariables);
ready

Revisions

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