This is part 2 of a series of technical posts documenting some of the interesting work and technologies we’ve used to power the new interface (see also part 1, Instant notifications of new emails via eventsource/server-sent events). Regular users can skip these posts, but we hope technical users find them interesting.
As dynamic websites constructed entirely on the client side become de rigueur, there are a number of templating languages battling it out to become the One True Wayâ„¢ of rendering your page. All follow essentially the same style: introduce extra control tags to intersperse with HTML. But if we go back to basics, HTML is simply a way of serialising a tree structure into a text format that is relatively easy for humans to edit. Once the browser receives this, it then has to parse it to generate an internal DOM tree representation before it can draw the page.
In an AJAX style application, we don’t transmit HTML directly to the browser. Instead, we generate the HTML on the client side, and often update the HTML in different parts of the page over time as the user interacts with the application. As string manipulation for building HTML from data objects is hard to write and error-prone, we normally use a template language and a library that compiles these snippets into code; this executes with a data context, producing a string of HTML that may be set as an element’s innerHTML property. The browser then builds a DOM tree, which we can query to update nodes and add event listeners.
There is, however, another alternative for building the DOM tree:
directly in JavaScript. Modern browsers are very fast at parsing and
executing JavaScript. What if, with the help of a liberal sprinkling of
syntactic sugar, we were to build the DOM tree in code instead? Start by
considering a simple function el
to declare an element.
el( 'div' )
OK, so far we’ve just renamed the document.createElement
method. What next? Well, we’re going to want to add class names and ids
to elements a lot. Let’s use the CSS syntax which everyone knows and
loves.
el( 'div#id.class1.class2' );
Hmm, that’s quite clean and readable compared to:
<div id="id" class="class1 class2"></div>
What else? Well, there may be other attributes. Let’s pass them as a standard hash:
el( 'div#id', { tabindex: -1, title: 'My div' })
That’s pretty neat. Let’s have a quick look at the html for comparison:
<div id="id" tabindex="-1" title="My div"></div>
A node’s not much use on its own. Let’s define a tree:
var items = [ 1, 2, 3, 4 ]; el( 'div#message', [ el( 'a.biglink', { href: 'http://www.google.com' }, [ 'A link to Google' ]), el( 'ul', [ items.forEach( function( item ) { return el( 'li.item', [ item + '. Item ] ); }) ]), items.length > 1 ? 'There are lots of items'.localise() + '. ' : null, 'This is just plain text. <script>I have no effect</script>' ])
So what have we achieved? We’ve got a different way of writing a
document tree, which is essentially very similar to HTML but changes the
punctuation slightly to make it valid JavaScript syntax instead. So
what? Well, the point is this readable declaration is directly
executable code; we just need to define the el
function: https://gist.github.com/1532562.
As it’s pure JS, we can replace static strings with variables. We can
easily add conditional nodes, as shown in the example above. We can call
other functions to generate a portion of the DOM tree or use array
iterators to cleanly write loops. Wrap it all in a function and we can
pass different data into the function each time to render our DOM nodes…
we have ourselves a template.
Performance
While innerHTML used to be much faster than JS DOM methods, this no longer holds for modern browsers. Let’s have a look at a benchmark: http://jsperf.com/innerhtml-or-dom/4
Here we have four different methods of rendering the same bit of HTML. This is a real-world snippet, taken from a core part of our new webmail application (https://beta.fastmail.fm), with just a few class names changed. Let’s first look at the hand-optimised innerHTML method and hand-optimised DOM method. In Chrome the DOM version is over 50% faster than using innerHTML and in Safari it’s 45% faster. Firefox is just as fast with either, while Opera is marginally faster using innerHTML. IE is still twice as fast using innerHTML rather than DOM methods. Perhaps most interesting though is to look at mobile browser performance. On desktop, computers are fast enough these days that the performance differences are less of an issue. On mobile it’s crucial, and here we find that the DOM method is anywhere from 45% to 100% faster in mobile WebKit browsers, such as Safari on the iPhone and the default Android browser, and level with innerHTML on Opera Mobile.
A few things to note before we look at the real-world tests. Firstly, for maximum speed, the innerHTML method is assuming all text is already escaped; a very dangerous assumption. The DOM method on the other hand needs to make no such assumptions, as text is added to the DOM tree by creating text nodes. Since the text is never parsed as HTML, there is zero chance of accidentally injecting a malicious script tag. Secondly, if you need a reference to any of the DOM nodes you’re creating (for example to save for updating later or to add event listeners), with the innerHTML method you must query the DOM after you’ve constructed it. With direct DOM construction, you already have the node reference; you just save it as you create it.
These hand-optimised functions are fast, but unmaintainable and a pain to write. Let’s move on to something we would use on a real website.
Handlebars is a popular JS templating language, and claims to be one of the fastest around. It produces a string for use with innerHTML to construct the DOM elements. Let’s compare that to the JS declarative approach I outlined above (which I’m going to call Sugared DOM). Compared to the raw methods, the Sugared DOM was more-or-less equal in performance to the hand-optimised innerHTML in Chrome and Safari, even on the iPhone. It’s equal to or faster than Handlebars templates (sometimes by a significant margin) in all browsers other than IE, and crucially on mobile browsers it’s anywhere from 50% to 100% faster. Note too that the initial compilation time for Handlebars templates is not included in these benchmarks.
Conclusion
On almost all modern browsers the Sugared DOM method is faster than normal templates, even when ignoring the compile-time cost the latter have. There are other benefits as well:
- Easy to debug (the template declaration is the code).
- The sugar code is much smaller than any decent templating library.
- No need to query the DOM, as you can just save references to nodes you’ll need later as you create them. This is faster and may remove the need for a whole JS library you currently use (like Sizzle).
- No escaping worries; zero chance of XSS bugs. When you include a
string in the declaration it is explicitly set as a text node, so is
never parsed as HTML.
<script>
tags are harmless! - No extraneous white-space text nodes. White space between block-level nodes in HTML does not affect the rendering, but it does add extra nodes to the DOM. These can be a pain when you’re manipulating it later (the firstChild property may not return what you expect) and increases the memory usage of the page.
- As it’s pure JS, the templates can be easily included inline as part of view classes that also handle the behaviour of the view, or kept in separate files.
- JSHint will validate your syntax; much easier than tracking down syntax errors from a template’s compiler.
- Flexibility to use the full power of JS; easily call other functions to generate parts of your DOM tree, localise a string, or do whatever else you like.
What are the downsides? Well, it’s slightly slower in Internet Explorer (although still plenty fast enough in real world use) and the difference in syntax to HTML may take a little time to become accustomed to, especially if templates are written by designers rather than coders (then again every template introduces its own syntax, so I’m not sure there’s much difference here). And, err, I think that’s about it.
It’s time to ditch HTML based templates. Embrace the DOM, and enjoy your powerful, fast and readable new way to render pages.