Definitive Handlebars precompilation benchmark

Benchmark created by srigi on


Description

Small HTML webapp representing non-trivial Handlebars template. Near real world scenario. It is one small widget representing eshop product comparator. It is completly rendered as Handlebars template and include:

  • partial
  • each iterators
  • lots of if confitions
  • custom inline helper

My previous test with Underscore templates show huge performance gain when precompiling templates into JS functions.

Handlebars is not only providing compilation of templates, it also provides some sort of optimization when compiling template function. I want to test this as well, so I prepared compilation results for:

  • bare compilation (templates.js)
  • compilation with optimized accesses to known helpers (templates.opt1.js)
  • compilation with optimized all block helper references (templates.opt2.js)

For more details please visit project demo.

Preparation HTML

<!-- Libraries -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="https://s3.amazonaws.com/github/downloads/wycats/handlebars.js/handlebars-1.0.rc.1.min.js"></script>

<!-- Precompiled templates -->
<script src="http://js.srigi.sk/zandlebars/js/app/templates/templates.js"></script>
<script src="http://js.srigi.sk/zandlebars/js/app/templates/templates.opt1.js"></script>
<script src="http://js.srigi.sk/zandlebars/js/app/templates/templates.opt2.js"></script>

<!-- Render data -->
<script src="http://js.srigi.sk/zandlebars/js/fixtures.js"></script>

<!-- Templates for "noncompiled" test -->
<script type="text/x-handlebars-template" id="comparator-template">
  <h2 class="comparator-headline">Compare products</h2>
  {{#each products}}
    <div class="comparator-item {{comparatorPosition @index}}" comparator-product-id="{{this.id}}">
      {{> comparatorItem}}
    </div>
  {{/each}}
</script>

<script type="text/x-handlebars-template" id="comparator-item-template">
  <div class="comparator-product">
    <img class="comparator-product-image" src="{{imageUrl}}" />
    <h3 class="comparator-product-name">{{name}}</h3>
    <dl>
      <dt>Sensor</dt>
      <dd>{{attributes.sensor}}</dd>

      <dt>Lens</dt>
      <dd>{{attributes.lens}}</dd>
      <dd>Optical&nbsp;zoom:&nbsp;{{attributes.opticalZoom}}×, Digital&nbsp;Zoom:&nbsp;{{attributes.digitalZoom}}×</dd>
      <dd>Luminosity:&nbsp;{{attributes.luminosity}}</dd>
      <dd>Focal length:&nbsp;{{attributes.focalLength}}</dd>

      {{#if attributes.iris}}
        <dd>Iris: {{attributes.iris}}</dd>
      {{/if}}
      {{#if attributes.irisRange}}
        <dd>Iris range: {{attributes.irisRange}}</dd>
      {{/if}}

      <dt>Exposition</dt>
      <dd>{{attributes.exposition}}</dd>
      <dd>{{attributes.expositionRange}}</dd>

      {{#if attributes.whiteBalance}}
        <dt>White balance</dt>
        <dd>{{attributes.whiteBalance}}</dd>
      {{/if}}

      <dt>ISO</dt>
      <dd>{{attributes.iso}}</dd>

      <dt>LCD panel</dt>
      <dd>{{attributes.lcdPanel}}, {{attributes.lcdPanelResolution}}</dd>
      <dd>{{attributes.lcdPanelFeatures}}</dd>

      <dt>Memory cards</dt>
      <dd>{{attributes.memoryCards}}</dd>

      <dt>File formats</dt>
      <dd>{{{explodeText attributes.fileFormats ","}}}</dd>

      <dt>File resolutions</dt>
      {{#each attributes.fileResolutions}}
        <dd>{{@key}}: {{{explodeText this ","}}}</dd>
      {{/each}}

      <dt>Connectivity</dt>
      <dd>{{{explodeText attributes.connectivity ","}}}</dd>

      {{#if attributes.other}}
        <dt>Other</dt>
        <dd>{{attributes.other}}</dd>
      {{/if}}

      <dt>Powering</dt>
      <dd>{{attributes.powering}}</dd>

      <dt>Dimensions</dt>
      <dd>{{attributes.dimensions}}</dd>

      <dt>Weight</dt>
      <dd>{{attributes.weight}}</dd>

      <dt>Accessories</dt>
      <dd>{{{explodeText attributes.accessories ","}}}</dd>
    </dl>
  </div>
</script>


<script>
  var comparatorSource = $('#comparator-template').html();
  var comparatorItemSource = $('#comparator-item-template').html();

  Handlebars.registerHelper("comparatorPosition", function(index) {
    switch (index) {
      case 0: return "comparator-item-left span3";
      case 1: return "comparator-item-middle span4";
      case 2: return "comparator-item-right span3";
    }
  });

  Handlebars.registerHelper("explodeText", function(text, delimiter) {
    if ('string' === typeof text) {
      return text.split(delimiter).join('<br />');
    } else {
      return '';
    }
  });
</script>

Test runner

Ready to run.

Testing in
TestOps/sec
noncompiled
// compile template
var comparatorTemplate = Handlebars.compile(comparatorSource);
var comparatorItemTemplate = Handlebars.compile(comparatorItemSource);

// register partial template
Handlebars.registerPartial('comparatorItem', comparatorItemTemplate);

// render template
var html = comparatorTemplate({ products:fixtures });
 
ready
precompiled
var comparatorTemplate = Handlebars.templates['comparator'];
var comparatorItemTemplate = Handlebars.templates['comparator-item'];

// register partial template
Handlebars.registerPartial('comparatorItem', comparatorItemTemplate);

// render template
var html = comparatorTemplate({ products:fixtures });
 
ready
precompiled.optimized (--known)
var comparatorTemplate = Handlebars.templates['comparator.opt1'];
var comparatorItemTemplate = Handlebars.templates['comparator-item.opt1'];

// register partial template
Handlebars.registerPartial('comparatorItem', comparatorItemTemplate);

// render template
var html = comparatorTemplate({ products:fixtures });
 
ready
precompiled.optimized (--known --knownOnly)
var comparatorTemplate = Handlebars.templates['comparator.opt2'];
var comparatorItemTemplate = Handlebars.templates['comparator-item.opt2'];

// register partial template
Handlebars.registerPartial('comparatorItem', comparatorItemTemplate);

// render template
var html = comparatorTemplate({ products:fixtures });
 
ready

Revisions

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