“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
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.