Caffeinated Simpleton

Bujagali: Incredibly Fast JavaScript Templating

“Fuck it, let’s do it live”

Today’s complex web apps have a complicated mish-mash of server-side templates combined with client-side JavaScript. Often times there are certain calls to the server that return HTML, and others that return JSON or XML for client-side use. This not only distorts the “view” portion of MVC, but it can also be hard to develop for and, increasingly, slow. At rdio, we thought that we may be able to see substantial performance improvements across the board if we changed our templating engine from Jinja2 on the server-side to a custom, client-side templating engine. We found this to be quite true in the end, and the majority of the pages found on rdio today use Bujagali (the name we gave our system).

Goals

The goals for the project were pretty ambitious.

  • Trivial to convert from Jinja to Bujagali
    • Support template inheritance
    • Support importing shared code
    • Support filters (essentially just functions accessible from the template)
  • Make heavy use of client’s cache
  • Reduce bandwidth usage to being as close as possible to the size of the actual data required to render
  • Perform really, really fast on good browsers
  • Not have terrible performance in IE
  • Optionally render server-side to support googlebot and other limited JS environments

We achieved these goals, and you can learn how to use the engine and play around with the source on github. Be warned, however, that documentation is sparse right now.

If you want to learn how all the insides work, keep reading.

Architecture

The architecture is pretty simple on the surface. It comes down to heavy caching, robust versioning, a laissez-faire syntax, and flexibility in runtime environment.

A quick note about measuring performance: it’s hard. Bujagali is optimized to perform better over time, to get you your data faster by utilizing smaller payloads, and to actually be fast once we were shoving data into strings. To top it off, it performs differently depending on your client. It’s hard to do an apples-to-apples comparison of overall template performance in a case like this, but suffice it to say that we measured performance extensively at rdio and Bujagali is way, way faster in most cases than the previous Jinja2-based system. I hope to write more about performance in the future.

Caching

When it comes to caching, there’s one overriding rule: templates themselves are very cacheable, data is not. I also did not assume that the application JavaScript was very cacheable since in rdio’s case it’s not. After all, we roll out new versions of the site every day and since the application JavaScript is compressed into one file, your browser almost always has to reload the JavaScript whenever we roll out a new version. I wanted to avoid this in the case of templates for a couple of reasons. First of all, most of the templates change quite a bit less frequently than the JavaScript itself, and second, including all markup on the site would increase the size of the rapidly-changing JS file enormously.

To maximize cacheability of the templates, we turn to the browser cache itself. Each template is just a JavaScript file that contains a JavaScript function of the same name. By including the version of the file in the file name and telling the browser to cache it til we’re all long dead, we can assure that the turnaround time for the browser to get the template loaded into the program is really low. In fact in some versions of IE, loading a script that is contained in the cache is a synchronous operation. This is better explained through a bit of code.

As long as we carefully keep track of versions, we can be sure that the function loaded by the above script is the one that we want and that if the template does not change, it will never require another trip to the server to retrieve. In practice, this is a huge win as the full markup of your site really only has to be transferred once.

Versioning

The next overriding principle was strict versioning. This was pretty much necessary as a result of the heavy caching. Every time data is passed into Bujagali to be rendered, it must specify what version of the template it expects to be rendered against. By providing this information every time, we can be sure that the templates always work right in pretty much every possible proxy and caching set-up. A cool side effect of this is hot-swappable templates. If you modify a template server side and an ajax request comes in for data that’s supposed to render against that freshly modified template, Bujagali will reload the template and render it seamlessly, no refresh required.

Laissez-Faire Syntax

The final part of designing Bujagali was figuring out what we would actually allow in the templates. Jinja is very strict about what you can and cannot do. Bujagali is not. You can just write arbitrary JavaScript, and in fact writing a template in Bujagali feels a lot more like writing JS than it does HTML. This isn’t because I think there should be logic in your templates (I don’t), but I think that it’s up to the developer to decide what constitutes “program logic” and what constitutes “view logic”. Having a full-fledged language can be very useful in a template, and so arbitrary JavaScript is perfectly acceptable in Bujagali. It’s a language you already know and it won’t get in your way. An example comes from one of rdio’s own templates:

Flexibility in Runtime Environment

Despite the fact that Bujagali allows you to execute arbitrary javascript, it does not make a lot of guarantees about the environment. Bujagali requires the Underscore.js library, so it will always be available to templates, but beyond that it makes no guarantees (although it is extensible, so you can ensure that your own objects and helpers are available). This restriction gives us the flexibility to render templates either on the client side or on the server side using a server-side JavaScript environment like node.js.

This restriction on environment does have some practical benefits that keep the templates “clean”, despite the arbitrary JS that is permitted. Without access to the DOM or the rest of the running program, side effects from the templates themselves are limited. This restriction is not actually enforced by Bujagali itself, however, instead you’ll just get burned if you don’t stick to the restriction and then try to use your template in a different context.

Try it out

I’ll write more about actually using Bujagali in the future. However, the code is available now on github. The documentation is a bit thrown together right now, but it’s hopefully enough to get you started. There’s some infrastructure required since, unlike a normal templating system, this system spans the boundary between client and server. Luckily the code provided should make it fairly easy to get going if you already know what you’re doing.

It should be stated that this isn’t exactly a templating system for the faint of heart. Best of luck to all who venture on to github.

Tags: ,

This entry was posted on Monday, October 18th, 2010 at 11:31 pm and is filed under Development. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

  • JSNoob
    Any pros and cons compared to the increasingly popular Google Closure templates ????http://code.google.com/closure...

    A definite pro for Closure Templates would be Google brand name, I guess.
  • retro
    I really like Bujagali, and I think that you're wrong when you say it is not designer friendly. Every template engine has learning curve and I don't think Bujagali's is any worse than any other's. After all, you can choose how hard and complicated should it be.
  • Thanks! That's good to hear.

    There are some subtle things that a good understanding of JS helps with (like the difference between 'self' and 'this' in templates) and some crazy ways of designating functions to be called after rendering is complete, but you can definitely get going without using any of that craziness.

    On the other hand, a lot of designers are becoming quite fluent in JavaScript, which is awesome. Those guys should (hopefully) have no problem.
  • retro
    I'm considering to use it as a server side template language for our CMSaaS so people that will use it should know _some_ Javascript and HTML. I really like how you combined power of Jinja / Django templates with Javascript syntax. Also, I really like macro functionality :)
  • Matthew
    How does it compare to jQote2 or moustache? How fast is it?
  • Moustache and jQote2 don't provide a built-in way of separating templates from the rest of your markup, meaning they don't cache well. Also, since the templates aren't pre-compiled, there will always be a compilation hit. Bujagali templates can be pre-compiled server side and sent down as regular old JS files. This is what we do at rdio.

    It's kind of an entirely different approach, but I'll come up with some numbers and make a new post soon to illustrate the differences.
  • aefxx
    @justin

    Hi there, I'm the author of jQote2 and I'd like to reply on jQote2 not being able to cache "well". Well, that statement is just plain wrong. jQote2 does have the ability to cache, both client-side and server-side.

    If one feels that the compilation hit is too costly (note that every template is compiled only once, though), one might as well just precompile the templates and save those (lambdas as I call them) on the server. It's mere JavaScript in the end - less developer friendly but feasible.

    You also mentioned that the actual template transformation is irrelevant and rather boring. Well, not quite as the resulting lambdas do differ a lot when it comes to performance. I've benchmarked templating solutions that ran 30x (and more) slower than jQote2 which would have a noticable impact on the end user's perception.

    I will take the time and give bujagali a try and benchmark it against the top performers. Once done I'll let you know. Let's see how "incredibly fast" it realy is.
  • What it comes down to is that all JavaScript templates, when it comes to actually transforming templates into markup, basically do the same thing. So speed tests against moustache or jQote2 are boring, because while they might all be slightly different, the whole ecosystem around those systems hasn't improved, where it has for Bujagali.
blog comments powered by Disqus