Knockout Template Engines (v4)

Revision 4 of this benchmark created on


Preparation HTML

<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0/handlebars.min.js"></script>  
<script src='http://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js'></script>

<script>
ko.underscoreTemplateEngine = function () {
    var engine = this;
    
    this.getTemplateNode = function (template) {
        return document.getElementById(template);
    };
    this.renderTemplate = function (templateId, data, options) {
        var tmpl = engine.getTemplateNode(templateId).text;
        var html = _.template(tmpl)(data);
        return ko.utils.parseHtmlFragment(html);
    };
    this.rewriteTemplate = function (template, rewriterCallback) {
        var templateNode = engine.getTemplateNode(template);
        
        // default rewrite
        var rewritten = rewriterCallback(templateNode.text);
        
        // custom rewrite to set context ie. insert with(data) after _.each
        var each = /(_\s*\.\s*each\s*\(\s*[^,]+\s*,\s*function\s*\(\s*)([^)]+)(\s*\)\s*{)/gi;
        
        rewritten = rewritten.replace(each, '$1$2$3 with($2)');

        templateNode.text = rewritten;
        templateNode.isRewritten = true;
    };
    this.isTemplateRewritten = function (templateId) {
        return engine.getTemplateNode(templateId).isRewritten === true;
    };
    this.createJavaScriptEvaluatorBlock = function (script) {
        return '<%= ' + script + ' %>';
    };
};
ko.underscoreTemplateEngine.prototype = new ko.templateEngine();
</script>

<script>
ko.handlebarsTemplateEngine = function () { }
ko.handlebarsTemplateEngine.prototype = ko.utils.extend(new ko.templateEngine(), {
    templates: {},
    renderTemplateSource: function (templateSource, bindingContext, options) {
        var data = bindingContext.$data,
            templateId = templateSource.i.id,
            templateText = templateSource.text(),
            compiledTemplate = this.templates[templateId];
 
        // only compile the template once on the client
        if (compiledTemplate == null) {
            compiledTemplate = Handlebars.compile(templateText);
            this.templates[templateId] = compiledTemplate;
        }
 
        return ko.utils.parseHtmlFragment(compiledTemplate(data));
    },
    allowTemplateRewriting: false
});
</script>


<!-- View Model Regions -->
<div id="native-test-virtual">
  <h2>Native (virtual foreach)</h2>
  <div class="messages" data-bind="template:'ko-native'"></div>
</div>
<div id="handlebars-test">
  <h2>Handlebars</h2>
  <div class="messages" data-bind="template:'ko-handlebars'"></div>
</div>
<div id="underscore-test">
  <h2>Underscore</h2>
  <div class="messages" data-bind="template:'ko-underscore'"></div>
</div>


<!-- Templates -->
<script id="ko-native" type="text/html">
<!-- ko foreach: items -->
  <div>
    <span data-bind="text: $data.name"></span>, 
    <span data-bind="text:$data.age"></span><br>
    <ul data-bind="foreach: $data.likes">
      <li data-bind="text: $data"></li>
    </ul>
  </div>
<!-- /ko -->
</script>
<script id="ko-underscore" type="text/html">
<% _.each($root.items(), function(item) { %>
  <div>
    <span data-bind="text: item.name"></span>, 
    <span data-bind="text:item.age"></span><br>
    <ul>
      <% _.each(item.likes(), function (like) { %>
        <li data-bind="text: like"></li>
      <% }); %>
    </ul>
  </div>
  <% }); %>
</script>
<script id="ko-handlebars" type="text/x-handlebars-template">
{{# each items }}
  <div>
    <span data-bind="text: '{{name}}'"></span>, 
    <span data-bind="text: '{{age}}'"></span><br>
    <ul>
      {{# each likes }}
        <li data-bind="text: '{{this}}'"></li>
      {{/ each }}
    </ul>
  </div>
{{/ each }}
</script>

Setup

var nativeNodeVirtual = document.getElementById('native-test-virtual');
    var handlebarsNode = document.getElementById('handlebars-test');
    var underscoreNode = document.getElementById('underscore-test');
    
    var nativeTemplateEngine = new ko.nativeTemplateEngine();
    var handlebarsTemplateEngine = new ko.handlebarsTemplateEngine();
    var underscoreTemplateEngine = new ko.underscoreTemplateEngine();
    
    var data = (function (n) {
      var data = [], i;
      for (i = 0; i < n; i++) {
        data.push({
          name: ko.observable('Greg'),
          age: ko.observable(i),
          likes: ko.observableArray(['movies','reading']) 
        });
      }
      return data;
    }(10));
    
    var viewmodel = { items: ko.observableArray( data ) };

Test runner

Ready to run.

Testing in
TestOps/sec
Native Templates (virtual foreach)
ko.setTemplateEngine(nativeTemplateEngine);
ko.applyBindings(viewmodel, nativeNodeVirtual);  
ko.cleanNode(nativeNodeVirtual);
ready
Handlebars Templates
ko.setTemplateEngine(handlebarsTemplateEngine);
ko.applyBindings(viewmodel, handlebarsNode);  
ko.cleanNode(handlebarsNode);
ready
Underscore Templates
ko.setTemplateEngine(underscoreTemplateEngine);
ko.applyBindings(viewmodel, underscoreNode);  
ko.cleanNode(underscoreNode);
ready

Revisions

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