JS Template Engines Performance (v140)

Revision 140 of this benchmark created by gooofly on


Description

Preparation HTML

<!-- Targeted DIV -->
<div id="target" style="display: none;"></div>

<!-- Libraries -->
<script src="https://code.jquery.com/jquery-git2.js"></script>
<script src="https://rawgithub.com/eugenioclrc/handlehash.js/master/HandleHash.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.2/mustache.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.2.1/handlebars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/hogan.js/2.0.0/hogan.min.js"></script>
<script src="https://rawgithub.com/olado/doT/master/doT.js"></script>
<script src="https://gsf.github.io/whiskers.js/whiskers.min.js"></script>
<script src="https://qatrix.com/js/qatrix-1.1.min.js"></script>
<script src="https://rawgithub.com/premasagar/875670/raw/d52752ead19a4eebc7237602438ae08a2541a5b5/tim-lite-cached-min.js"></script>
<script src="http://fb.me/react-0.8.0.min.js"></script>
<script src="https://cdn.rawgit.com/adammark/Markup.js/master/src/markup.min.js"></script>
<script src="https://cdn.rawgit.com/foo123/Contemplate/master/src/js/Contemplate.min.js"></script>



<script>
  /*
      ********** Juicer **********
      ${A Fast template engine}
      Project Home: http://juicer.name

      Author: Guokai
      Gtalk: badkaikai@gmail.com
      Blog: http://benben.cc
      Licence: MIT License
      Version: 0.6.8-stable
  */

  (function() {

      // This is the main function for not only compiling but also rendering.
      // there's at least two parameters need to be provided, one is the tpl, 
      // another is the data, the tpl can either be a string, or an id like #id.
      // if only tpl was given, it'll return the compiled reusable function.
      // if tpl and data were given at the same time, it'll return the rendered 
      // result immediately.

      var juicer = function() {
          var args = [].slice.call(arguments);

          args.push(juicer.options);

          if(args[0].match(/^\s*#([\w:\-\.]+)\s*$/igm)) {
              args[0].replace(/^\s*#([\w:\-\.]+)\s*$/igm, function($, $id) {
                  var _document = document;
                  var elem = _document && _document.getElementById($id);
                  args[0] = elem ? (elem.value || elem.innerHTML) : $;
              });
          }

          if(typeof(document) !== 'undefined' && document.body) {
              juicer.compile.call(juicer, document.body.innerHTML);
          }

          if(arguments.length == 1) {
              return juicer.compile.apply(juicer, args);
          }

          if(arguments.length >= 2) {
              return juicer.to_html.apply(juicer, args);
          }
      };

      var __escapehtml = {
          escapehash: {
              '<': '&lt;',
              '>': '&gt;',
              '&': '&amp;',
              '"': '&quot;',
              "'": '&#x27;',
              '/': '&#x2f;'
          },
          escapereplace: function(k) {
              return __escapehtml.escapehash[k];
          },
          escaping: function(str) {
              return typeof(str) !== 'string' ? str : str.replace(/[&<>"]/igm, this.escapereplace);
          },
          detection: function(data) {
              return typeof(data) === 'undefined' ? '' : data;
          }
      };

      var __throw = function(error) {
          if(typeof(console) !== 'undefined') {
              if(console.warn) {
                  console.warn(error);
                  return;
              }

              if(console.log) {
                  console.log(error);
                  return;
              }
          }

          throw(error);
      };

      var __creator = function(o, proto) {
          o = o !== Object(o) ? {} : o;

          if(o.__proto__) {
              o.__proto__ = proto;
              return o;
          }

          var empty = function() {};
          var n = Object.create ? 
              Object.create(proto) : 
              new(empty.prototype = proto, empty);

          for(var i in o) {
              if(o.hasOwnProperty(i)) {
                  n[i] = o[i];
              }
          }

          return n;
      };

      var annotate = function(fn) {
          var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
          var FN_ARG_SPLIT = /,/;
          var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
          var FN_BODY = /^function[^{]+{([\s\S]*)}/m;
          var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
          var args = [],
              fnText,
              fnBody,
              argDecl;

          if (typeof fn === 'function') {
              if (fn.length) {
                  fnText = fn.toString();
              }
          } else if(typeof fn === 'string') {
              fnText = fn;
          }

          fnText = fnText.replace(STRIP_COMMENTS, '');
          fnText = fnText.trim();
          argDecl = fnText.match(FN_ARGS);
          fnBody = fnText.match(FN_BODY)[1].trim();

          for(var i = 0; i < argDecl[1].split(FN_ARG_SPLIT).length; i++) {
              var arg = argDecl[1].split(FN_ARG_SPLIT)[i];
              arg.replace(FN_ARG, function(all, underscore, name) {
                  args.push(name);
              });
          }

          return [args, fnBody];
      };

      juicer.__cache = {};
      juicer.version = '0.6.8-stable';
      juicer.settings = {};

      juicer.tags = {
          operationOpen: '{@',
          operationClose: '}',
          interpolateOpen: '\\${',
          interpolateClose: '}',
          noneencodeOpen: '\\$\\${',
          noneencodeClose: '}',
          commentOpen: '\\{#',
          commentClose: '\\}'
      };

      juicer.options = {
          cache: true,
          strip: true,
          errorhandling: true,
          detection: true,
          _method: __creator({
              __escapehtml: __escapehtml,
              __throw: __throw,
              __juicer: juicer
          }, {})
      };

      juicer.tagInit = function() {
          var forstart = juicer.tags.operationOpen + 'each\\s*([^}]*?)\\s*as\\s*(\\w*?)\\s*(,\\s*\\w*?)?' + juicer.tags.operationClose;
          var forend = juicer.tags.operationOpen + '\\/each' + juicer.tags.operationClose;
          var ifstart = juicer.tags.operationOpen + 'if\\s*([^}]*?)' + juicer.tags.operationClose;
          var ifend = juicer.tags.operationOpen + '\\/if' + juicer.tags.operationClose;
          var elsestart = juicer.tags.operationOpen + 'else' + juicer.tags.operationClose;
          var elseifstart = juicer.tags.operationOpen + 'else if\\s*([^}]*?)' + juicer.tags.operationClose;
          var interpolate = juicer.tags.interpolateOpen + '([\\s\\S]+?)' + juicer.tags.interpolateClose;
          var noneencode = juicer.tags.noneencodeOpen + '([\\s\\S]+?)' + juicer.tags.noneencodeClose;
          var inlinecomment = juicer.tags.commentOpen + '[^}]*?' + juicer.tags.commentClose;
          var rangestart = juicer.tags.operationOpen + 'each\\s*(\\w*?)\\s*in\\s*range\\(([^}]+?)\\s*,\\s*([^}]+?)\\)' + juicer.tags.operationClose;
          var include = juicer.tags.operationOpen + 'include\\s*([^}]*?)\\s*,\\s*([^}]*?)' + juicer.tags.operationClose;
          var helperRegisterStart = juicer.tags.operationOpen + 'helper\\s*([^}]*?)\\s*' + juicer.tags.operationClose;
          var helperRegisterBody = '([\\s\\S]*?)';
          var helperRegisterEnd = juicer.tags.operationOpen + '\\/helper' + juicer.tags.operationClose;

          juicer.settings.forstart = new RegExp(forstart, 'igm');
          juicer.settings.forend = new RegExp(forend, 'igm');
          juicer.settings.ifstart = new RegExp(ifstart, 'igm');
          juicer.settings.ifend = new RegExp(ifend, 'igm');
          juicer.settings.elsestart = new RegExp(elsestart, 'igm');
          juicer.settings.elseifstart = new RegExp(elseifstart, 'igm');
          juicer.settings.interpolate = new RegExp(interpolate, 'igm');
          juicer.settings.noneencode = new RegExp(noneencode, 'igm');
          juicer.settings.inlinecomment = new RegExp(inlinecomment, 'igm');
          juicer.settings.rangestart = new RegExp(rangestart, 'igm');
          juicer.settings.include = new RegExp(include, 'igm');
          juicer.settings.helperRegister = new RegExp(helperRegisterStart + helperRegisterBody + helperRegisterEnd, 'igm');
      };

      juicer.tagInit();

      // Using this method to set the options by given conf-name and conf-value,
      // you can also provide more than one key-value pair wrapped by an object.
      // this interface also used to custom the template tag delimater, for this
      // situation, the conf-name must begin with tag::, for example: juicer.set
      // ('tag::operationOpen', '{@').

      juicer.set = function(conf, value) {
          var that = this;

          var escapePattern = function(v) {
              return v.replace(/[\$\(\)\[\]\+\^\{\}\?\*\|\.]/igm, function($) {
                  return '\\' + $;
              });
          };

          var set = function(conf, value) {
              var tag = conf.match(/^tag::(.*)$/i);

              if(tag) {
                  that.tags[tag[1]] = escapePattern(value);
                  that.tagInit();
                  return;
              }

              that.options[conf] = value;
          };

          if(arguments.length === 2) {
              set(conf, value);
              return;
          }

          if(conf === Object(conf)) {
              for(var i in conf) {
                  if(conf.hasOwnProperty(i)) {
                      set(i, conf[i]);
                  }
              }
          }
      };

      // Before you're using custom functions in your template like ${name | fnName},
      // you need to register this fn by juicer.register('fnName', fn).

      juicer.register = function(fname, fn) {
          var _method = this.options._method;

          if(_method.hasOwnProperty(fname)) {
              return false;
          }

          return _method[fname] = fn;
      };

      // remove the registered function in the memory by the provided function name.
      // for example: juicer.unregister('fnName').

      juicer.unregister = function(fname) {
          var _method = this.options._method;

          if(_method.hasOwnProperty(fname)) {
              return delete _method[fname];
          }
      };

      juicer.template = function(options) {
          var that = this;

          this.options = options;

          this.__interpolate = function(_name, _escape, options) {
              var _define = _name.split('|'), _fn = _define[0] || '', _cluster;

              if(_define.length > 1) {
                  _name = _define.shift();
                  _cluster = _define.shift().split(',');
                  _fn = '_method.' + _cluster.shift() + '.call({}, ' + [_name].concat(_cluster) + ')';
              }

              return '<%= ' + (_escape ? '_method.__escapehtml.escaping' : '') + '(' +
                          (!options || options.detection !== false ? '_method.__escapehtml.detection' : '') + '(' +
                              _fn +
                          ')' +
                      ')' +
                  ' %>';
          };

          this.__removeShell = function(tpl, options) {
              var _counter = 0;

              tpl = tpl
                  // inline helper register
                  .replace(juicer.settings.helperRegister, function($, helperName, fnText) {
                      var anno = annotate(fnText);
                      var fnArgs = anno[0];
                      var fnBody = anno[1];
                      var fn = new Function(fnArgs.join(','), fnBody);

                      juicer.register(helperName, fn);
                      return $;
                  })

                  // for expression
                  .replace(juicer.settings.forstart, function($, _name, alias, key) {
                      var alias = alias || 'value', key = key && key.substr(1);
                      var _iterate = 'i' + _counter++;
                      return '<% ~function() {' +
                                  'for(var ' + _iterate + ' in ' + _name + ') {' +
                                      'if(' + _name + '.hasOwnProperty(' + _iterate + ')) {' +
                                          'var ' + alias + '=' + _name + '[' + _iterate + '];' +
                                          (key ? ('var ' + key + '=' + _iterate + ';') : '') +
                              ' %>';
                  })
                  .replace(juicer.settings.forend, '<% }}}(); %>')

                  // if expression
                  .replace(juicer.settings.ifstart, function($, condition) {
                      return '<% if(' + condition + ') { %>';
                  })
                  .replace(juicer.settings.ifend, '<% } %>')

                  // else expression
                  .replace(juicer.settings.elsestart, function($) {
                      return '<% } else { %>';
                  })

                  // else if expression
                  .replace(juicer.settings.elseifstart, function($, condition) {
                      return '<% } else if(' + condition + ') { %>';
                  })

                  // interpolate without escape
                  .replace(juicer.settings.noneencode, function($, _name) {
                      return that.__interpolate(_name, false, options);
                  })

                  // interpolate with escape
                  .replace(juicer.settings.interpolate, function($, _name) {
                      return that.__interpolate(_name, true, options);
                  })

                  // clean up comments
                  .replace(juicer.settings.inlinecomment, '')

                  // range expression
                  .replace(juicer.settings.rangestart, function($, _name, start, end) {
                      var _iterate = 'j' + _counter++;
                      return '<% ~function() {' +
                                  'for(var ' + _iterate + '=' + start + ';' + _iterate + '<' + end + ';' + _iterate + '++) {{' +
                                      'var ' + _name + '=' + _iterate + ';' +
                              ' %>';
                  })

                  // include sub-template
                  .replace(juicer.settings.include, function($, tpl, data) {
                      // compatible for node.js
                      if(tpl.match(/^file\:\/\//igm)) return $;
                      return '<%= _method.__juicer(' + tpl + ', ' + data + '); %>';
                  });

              // exception handling
              if(!options || options.errorhandling !== false) {
                  tpl = '<% try { %>' + tpl;
                  tpl += '<% } catch(e) {_method.__throw("Juicer Render Exception: "+e.message);} %>';
              }

              return tpl;
          };

          this.__toNative = function(tpl, options) {
              return this.__convert(tpl, !options || options.strip);
          };

          this.__lexicalAnalyze = function(tpl) {
              var buffer = [];
              var method = [];
              var prefix = '';
              var reserved = [
                  'if', 'each', '_', '_method', 'console', 
                  'break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', 'do', 
                  'finally', 'for', 'function', 'in', 'instanceof', 'new', 'return', 'switch', 
                  'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with', 'null', 'typeof', 
                  'class', 'enum', 'export', 'extends', 'import', 'super', 'implements', 'interface', 
                  'let', 'package', 'private', 'protected', 'public', 'static', 'yield', 'const', 'arguments', 
                  'true', 'false', 'undefined', 'NaN'
              ];

              var indexOf = function(array, item) {
                  if (Array.prototype.indexOf && array.indexOf === Array.prototype.indexOf) {
                      return array.indexOf(item);
                  }

                  for(var i=0; i < array.length; i++) {
                      if(array[i] === item) return i;
                  }

                  return -1;
              };

              var variableAnalyze = function($, statement) {
                  statement = statement.match(/\w+/igm)[0];

                  if(indexOf(buffer, statement) === -1 && indexOf(reserved, statement) === -1 && indexOf(method, statement) === -1) {

                      // avoid re-declare native function, if not do this, template 
                      // `{@if encodeURIComponent(name)}` could be throw undefined.

                      if(typeof(window) !== 'undefined' && typeof(window[statement]) === 'function' && window[statement].toString().match(/^\s*?function \w+\(\) \{\s*?\[native code\]\s*?\}\s*?$/i)) {
                          return $;
                      }

                      // compatible for node.js
                      if(typeof(global) !== 'undefined' && typeof(global[statement]) === 'function' && global[statement].toString().match(/^\s*?function \w+\(\) \{\s*?\[native code\]\s*?\}\s*?$/i)) {
                          return $;
                      }

                      // avoid re-declare registered function, if not do this, template 
                      // `{@if registered_func(name)}` could be throw undefined.

                      if(typeof(juicer.options._method[statement]) === 'function' || juicer.options._method.hasOwnProperty(statement)) {
                          method.push(statement);
                          return $;
                      }

                      buffer.push(statement); // fuck ie
                  }

                  return $;
              };

              tpl.replace(juicer.settings.forstart, variableAnalyze).
                  replace(juicer.settings.interpolate, variableAnalyze).
                  replace(juicer.settings.ifstart, variableAnalyze).
                  replace(juicer.settings.elseifstart, variableAnalyze).
                  replace(juicer.settings.include, variableAnalyze).
                  replace(/[\+\-\*\/%!\?\|\^&~<>=,\(\)\[\]]\s*([A-Za-z_]+)/igm, variableAnalyze);

              for(var i = 0;i < buffer.length; i++) {
                  prefix += 'var ' + buffer[i] + '=_.' + buffer[i] + ';';
              }

              for(var i = 0;i < method.length; i++) {
                  prefix += 'var ' + method[i] + '=_method.' + method[i] + ';';
              }

              return '<% ' + prefix + ' %>';
          };

          this.__convert=function(tpl, strip) {
              var buffer = [].join('');

              buffer += "'use strict';"; // use strict mode
              buffer += "var _=_||{};";
              buffer += "var _out='';_out+='";

              if(strip !== false) {
                  buffer += tpl
                      .replace(/\\/g, "\\\\")
                      .replace(/[\r\t\n]/g, " ")
                      .replace(/'(?=[^%]*%>)/g, "\t")
                      .split("'").join("\\'")
                      .split("\t").join("'")
                      .replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='")
                      .split("<%").join("';")
                      .split("%>").join("_out+='")+
                      "';return _out;";

                  return buffer;
              }

              buffer += tpl
                      .replace(/\\/g, "\\\\")
                      .replace(/[\r]/g, "\\r")
                      .replace(/[\t]/g, "\\t")
                      .replace(/[\n]/g, "\\n")
                      .replace(/'(?=[^%]*%>)/g, "\t")
                      .split("'").join("\\'")
                      .split("\t").join("'")
                      .replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='")
                      .split("<%").join("';")
                      .split("%>").join("_out+='")+
                      "';return _out.replace(/[\\r\\n]\\s+[\\r\\n]/g, '\\r\\n');";

              return buffer;
          };

          this.parse = function(tpl, options) {
              var _that = this;

              if(!options || options.loose !== false) {
                  tpl = this.__lexicalAnalyze(tpl) + tpl;
              }

              tpl = this.__removeShell(tpl, options);
              tpl = this.__toNative(tpl, options);

              this._render = new Function('_, _method', tpl);

              this.render = function(_, _method) {
                  if(!_method || _method !== that.options._method) {
                      _method = __creator(_method, that.options._method);
                  }

                  return _that._render.call(this, _, _method);
              };

              return this;
          };
      };

      juicer.compile = function(tpl, options) {
          if(!options || options !== this.options) {
              options = __creator(options, this.options);
          }

          try {
              var engine = this.__cache[tpl] ? 
                  this.__cache[tpl] : 
                  new this.template(this.options).parse(tpl, options);

              if(!options || options.cache !== false) {
                  this.__cache[tpl] = engine;
              }

              return engine;

          } catch(e) {
              __throw('Juicer Compile Exception: ' + e.message);

              return {
                  render: function() {} // noop
              };
          }
      };

      juicer.to_html = function(tpl, data, options) {
          if(!options || options !== this.options) {
              options = __creator(options, this.options);
          }

          return this.compile(tpl, options).render(data, options._method);
      };

      typeof(module) !== 'undefined' && module.exports ? module.exports = juicer : this.juicer = juicer;

  })();

</script>



    





    








    


    

   

    <script type="text/x-contemplate" id="contemplateextTemplate">
<# #>
<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 as $item) #><li class='item'><# $item #></li><# %endfor() #></ul></div></script>

Setup

sharedVariables = {
            header: "Header",
            header2: "Header 2",
            header3: "Header 3",
            header4: "Header 4",
            header5: "Header 5",
            header6: "Header 6",
            list: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
        };
    
    var element = document.getElementById('target');

Test runner

Ready to run.

Testing in
TestOps/sec
mustache.js
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>";

element.innerHTML = Mustache.render(mustacheTemplate, sharedVariables);
ready
underscore.js
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>");
element.innerHTML = underscoreTemplate(sharedVariables);
ready
handlebar.js
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>");
element.innerHTML = handlebarsTemplate(sharedVariables);
ready
Hogan.js
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>");
element.innerHTML = hoganCompile.render(sharedVariables);
ready
Resig Micro Templates (modified)
    //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);
    }

    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>");

element.innerHTML = resig(sharedVariables);
ready
Resig Micro Templates (No "with" block)
    //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, "'+($1)+'")
            .split("<#").join("';")
            .split("#>").join("o+='") + "';return o;";

        return new Function("data", strFunc);
    }

    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>");

element.innerHTML = resig2(sharedVariables);
ready
underscore.js (_.each)
    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>");
element.innerHTML = underscoreTemplateEach(sharedVariables);
ready
Mustache (compiled)
 mustacheCompiledTemplate = Mustache.compile(mustacheTemplate);
element.innerHTML = mustacheCompiledTemplate(sharedVariables);
ready
HandleHash
handleHashTemplate = HandleHash.build("<div><h1>{#=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(data.list,item) #}<li class='item'>{#= item #}</li>{#endfor #</ul></div>");
element.innerHTML = handleHashTemplate(sharedVariables);
ready
underscore each cached
    underscoreTemplateEachCached = _.memoize(_.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>"), function (v) {
        return v
    });

element.innerHTML = underscoreTemplateEachCached(sharedVariables);
ready
underscore for cached
    underscoreTemplateCached = _.memoize(_.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>"), function (v) {
        return v
    });


element.innerHTML = underscoreTemplateCached(sharedVariables);
ready
doT.js
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>");
element.innerHTML = doTtemplate(sharedVariables);
ready
doT.js 2
 doTtemplate2 = doT.compile("<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'>{{~it.list :value:index}}<li class='item'>{{=value}}</li>{{~}}</ul></div>", {
        append: true
    });
element.innerHTML = doTtemplate2(sharedVariables);
ready
Whiskers.js
whiskersRender = whiskers.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'>{for item in list}<li class='item'>{item}</li>{/for}</ul></div>");
element.innerHTML = whiskersRender(sharedVariables);
ready
React
var App = App || {};
    
    App.template = '<div>' +
                     '<h1 class="header"><%this.header%></h1>' +
                     '<h2 class="header2"><%this.header2%></h2>' +
                     '<h3 class="header3"><%this.header3%></h3>' +
                     '<h4 class="header4"><%this.header4%></h4>' +
                     '<h5 class="header5"><%this.header5%></h5>' +
                     '<h6 class="header6"><%this.header6%></h6>' +
                     '<ul class="list">' +
                       '<%for(var i in this.list) {%>' +
                         '<li class="item"><%this.list[i]%></li>' +
                       '<%}%>' +
                     '</ul>' +
                   +'</div>'
    App.render = function(html, options) {
        var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0;
        var add = function(line, js) {
            js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
                (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
            return add;
        }
        while(match = re.exec(html)) {
            add(html.slice(cursor, match.index))(match[1], true);
            cursor = match.index + match[0].length;
        }
        add(html.substr(cursor, html.length - cursor));
        code += 'return r.join("");';
        return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
    }

var Hello = React.createClass({
  render: function() {
    return React.DOM.div({}, '<h1 class="header">' + this.props.header + '</h1>');
  }
});
React.renderComponent(Hello(sharedVariables), target);
ready
Markup.js
var MarkupjsTemplate = "<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>";
element.innerHTML = Mark.up(MarkupjsTemplate, sharedVariables);
ready
juicer(compiled)
compiled_tpl = juicer("<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 as item}<li class='item'>${item}</li>{@/each}</ul></div>");
element.innerHTML = compiled_tpl.render(sharedVariables);
ready
juicer
element.innerHTML = juicer("<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 as item}<li class='item'>${item}</li>{@/each}</ul></div>", sharedVariables);
ready
contemplate
//Use external template definition
  Contemplate.add({
    'test': "#contemplateextTemplate"
  });
  window.contemplateTpl = Contemplate.tpl( 'test' );
contemplateTpl.render(sharedVariables);
ready

Revisions

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