JavaScript Performance: Pre-Compiling And Caching HTML Templates

If you’re using HTML templates with a JavaScript application, you need to be caching the template’s raw text and/or pre-compiled version of the template. All of this spawned from a fun thread of discussion on a StackOverflow question.

DOM Selection Is Expensive

If you didn’t know this already, DOM selection is expensive. So expensive, in fact, that it should be avoided whenever possible. Of course avoiding it entirely is not likely as we need DOM selection to handle events, update the UI, and more. There’s a huge performance impact on having to constantly select a <script> tag from the DOM when you need a template, though. See this JSPerf that I wrote which illustrates the difference.

Screen Shot 2012 03 28 at 9 35 57 PM

If you’re storing templates in a script tag, don’t access that DOM element more than once. After all, you’re not expecting the contents of the template to change. Only the data that is rendered in to the template changes.

Simple Template Caching

The easiest way of storing a template is to use the DOM selector as a key, and the template contents as a value. Check for the existence of that key on an object, and if it exists use that. If it doesn’t exist, load it:

TemplateCache = {
  get: function(selector){
    if (!this.templates){ this.templates = {}; }

    var template = this.templates[selector];
    if (!template){
      var template = $(selector).html();
      this.templates[selector] = template;
    }

    return template;
  }
}

Pre-Compile Templates

One of the commenters in the thread of discussion liked the test I put together but thought it could be done better, still. He updated it to show cached vs non-cached and pre-compiled vs non-pre-compiled templates.

Screen Shot 2012 03 28 at 10 02 37 PM

Clearly, pre-compiling your templates is important. So let’s update our TemplateCache to pre-compile the templates for us (assuming we’re using underscore.js):

TemplateCache = {
  get: function(selector){
    if (!this.templates){ this.templates = {}; }

    var template = this.templates[selector];
    if (!template){
      template = $(selector).html();

      // precompile the template, for underscore.js templates
      template = _.template(template);

      this.templates[selector] = template;
    }

    return template;
  }
}

Template Cache Built In To Backbone.Marionette

Of course you saw this part coming, right? I’ve got a much more robust version of the above template caching already available in Backbone.Marionette. It defaults to using underscore.js as it’s template engine, but that’s really easy to change. See the documentation for some examples on how to do that.

But even if you’re not using Backbone or Backbone.Marionette, you need to take advantage of the performance improvements that your application will see, by only selecting something from the DOM once, and by pre-compiling and caching any templates that you’re using.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, DOM, Javascript, JQuery, Marionette, Performance. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://blog.prabir.me/ Prabir

    Another alternative solution would be to precompile templates in server side.
    Using Cassette in asp.net and hogan.js you can precompile the mustache templates. http://getcassette.net/documentation/html-templates/mustache-hoganjs
    http://twitter.github.com/hogan.js/

    • Anonymous

      I am using Cassette and Knockout on the client. Cassette uses KO 1.6, not 2.0+. I’ve been trying to figure out if I can update it because if I can, that would be awesome. I’ve opened an issue on it. 
      https://github.com/andrewdavey/cassette/issues/219

      If someone better at figuring this out would like to help… that’d be great.

  • http://twitter.com/ryanimated Ryan Smith

    Even better would be using the r.js optimizer from require.js and the tpl plugin to pre-compile and concatenate and minify them at build time.

    I can see some benefit of lazy-loading templates to speed initial page load, but you could also build several template bundles for different parts of the application.

    I’ve been using Marionette the last month or so, and extended the template support to handle r.js optimized templates even before you added the enhancements to your TemplateCache. Your enhancements are closer, but still not quite the optimal solution I’d like. I’ll submit a pull request soon to see what you think.

    • http://mutedsolutions.com Derick Bailey

      i’d love to see that pull request! i know it could be done a lot better, but i haven’t seen a pull request to do it yet :)

      • Anonymous

        Yesterday I updated my code from 0.6.1 to 0.7.4 with the intent to merge in the changes I made to support pre-compiled templates, and then create that pull request. 

        What I had done (bit of a hack) was to set a templateFn attribute on my ItemViews and then override ItemView.prototype.render to check for that attribute and if found, bypass the normal template loading/compiling. The trouble with creating a general solution was that you already allow the template attribute to be either a jQuery selector string, or a function that returns a selector string, so I couldn’t just set the actual template there.

        Anyway, after looking in more detail at 0.7.4 and thinking through several alternatives for how to cleanly support this, it dawned on me. The signature of ItemView.renderHtml is the same as a compiled underscore template. So, in my AMD modules where I define my views, I can just override renderHtml with my pre-compiled template function.

        This lets me cleanly bypass your templateSelector/Renderer/TemplateCache without clobbering it, so if I have some views which I want to load lazily, I still can.

        Pretty sweet!

        Here’s a gist 

        https://gist.github.com/2576421

        that shows you what I’m doing. I’m using require.js and the tpl plugin. The tpl plugin extends the text plugin by returning you the compiled template function instead of just the text string. At developlement time it loads the template asynchronously, but with the require.js build optimizer, it minifies and inlines the template functions in your main application js, which I prefer over lazy loading, particularly for the mobile web where the number of requests matter, and I can use the html5 app cache to store everything. Makes for a very responsive app!

        • http://mutedsolutions.com Derick Bailey

          very nice! :)

  • petehawkins

    If you are using handlebars and requirejs for your client side rendering, check out this module for require, it loads in your templates already pre-compiled, so if build your requireJS for production with the r.js optimiser the templates are served precompiled from the start. – https://github.com/SlexAxton/require-handlebars-plugin