By Rich Harris
If you’re been programming for any length of time, you’ve probably read your fair share of ‘Considered Harmful‘ essays, and thus already know that what follows the headline is usually a stream of biased remarks and cherry-picked evidence.
I can’t promise this will be any different. But the concepts we’re going to cover are things that I think the front end community should be talking about.
The tl;dr is this: generating HTML from string templates is an inefficient process that imposes unnecessary constraints on how we conceptualize web applications. You can take advantage of templates without the drawbacks if you use a library like Ractive.js or beta versions of frameworks such as Meteor and Ember. If you don’t need to use templating, React is also an excellent option.
Templating 101
There are a huge number of templating libraries available to JavaScript developers – see Garann Means’ template engine chooser for a few examples. The vast majority of these are string templating engines, which is to say that they take a string template as an input (which typically resembles HTML, except with placeholder tags) alongside some data (which fills the placeholder tags), and outputs another string:
var template = '<h1>Hello {{name}}!</h1>';
var data = { name: 'world' };
Mustache.render( template, data );
// -> '<h1>Hello world!</h1>'
In the example above we’re using mustache.js, an implementation of the excellent mustache templating language, variants of which can be found in many places. It’s justifiably popular – you can learn it in seconds, and it is (I think) attractive, readable and concise. The existence of a formal specification has enabled alternative implementations such as Hogan.js to compete on code size and performance.
And developers love templates with good reason. Not all of them – Templating Languages Considered Harmful by Pete Hunt of the React core team makes a thoughtful case against templates – but enough of us that they’re not going away any time soon.
So why is string templating inefficient?
Generating HTML from a template and some data isn’t a particularly onerous task. The inefficient part is what you have to do with that HTML.
Suppose you have a piece of UI that represents changing data:
<div class='score'>
<p>Current score: <strong>{{currentScore}}</strong></p>
<p>High score: <strong>{{highScore}}</strong></p>
</div>
In our hypothetical shoot-em-up, the currentScore
property is likely to change frequently. The highScore
property might change with it if we’re doing well, but during many games it won’t change at all. Nevertheless, when we re-render the template and insert it into the document with something like score.innerHTML = rendered
or $score.html(rendered)
, whatever is currently in the DOM will get discarded every time the score changes! In this example that includes a <div>
, two <p>
elements and two <strong>
s, four text nodes and an attribute node (the class
).
And that’s a fairly trivial example. As templates become larger, the amount of stuff that needs to be destroyed with each re-render grows – and that means extra work for the garbage collector. It’s slow and wasteful, and we haven’t even talked about what happens when you need to listen to DOM events on those nodes.
Updating views by inserting freshly rendered HTML into the DOM is like knocking your house down and rebuilding it when all you really needed to do was clean your windows.
Show me the evidence
Perhaps you’ve read that innerHTML
is faster than DOM manipulation (in old versions of IE it is, but in modern browsers DOM manipulation is much faster), or that we’re not talking about a significant difference. Let’s look at some numbers.
Some time ago, Jeremy Ashkenas – the creator of Backbone, and all-round JavaScript hero – created a JSFiddle benchmark showing that Backbone’s data-binding performance was significantly better than that of Ember at the time.
The benchmark was forked to include React, which easily beat both competitors. Later, Erik Bryn updated the benchmark to include a development version of Ember he was working on (more on this below) that comfortably bested React.
You shouldn’t read too much into these benchmarks – it’s testing something which React, for example, is not optimized for. Other benchmarks would tell a different story. But there is one benchmark that we should find interesting – Backbone versus Backbone.
Open this JSFiddle. It’s a fork of the fork of the fork of Jeremy’s original. (I don’t doubt there are others). Try the implementations to see the magnitude of the difference. The ‘Backbone (fast)’ example is identical to the ‘Backbone’ example, except that instead of a render()
method that uses string templating, it has an update()
method that uses DOM manipulation. It’s much faster than continually reinserting big globs of HTML.
That’s a straw man – I don’t write my views like that!
Developers are intuitively aware of this problem. We don’t write big templates that cover an entire app – instead we write templates that describe much smaller parts, and combine them in code. Instead of writing a template for a list of todo items, we write a template for a single todo item, and create a view object for each one.
And this is exactly the problem. We end up thinking about our user interfaces atomically rather than holistically – the parts of our app we need to reason about most clearly are split into many different files. Maybe we’ve grown out of ‘jQuery spaghetti’, but in many cases it’s been replaced by MVC duct tape.
Perhaps you think it’s a good thing to write small, tightly focused templates – and you’d be right, some of the time. The point is that these architectural decisions shouldn’t be imposed on us by the limitations of the tools and techniques we’re working with.
What about DOM templating?
There is an alternative to string templating. DOM templating systems, such as those found in Knockout and Angular, put much of their data-binding in element attributes – Knockout’s data-bind
or Angular’s ng-repeat
, for example. These systems avoid the problem of having to continually trash the DOM.
But it comes at a price:
- Because the data-binding instructions are read from the DOM, the template has to be renderable HTML, restricting the expressiveness of the templating language. The syntax is (opinion alert!) typically much less readable than that of string templating languages
- You may have to work to prevent a FOUC – a ‘Flash Of Unbound Content’ – with hacks like
ng-cloak
- Server-side rendering is impossible without Rube Goldberg-esque systems involving JSDOM or PhantomJS
- If you inspect the DOM, you’ll often find ‘data-binding cruft’ – invalid attributes like
ng-model
.
So what’s the answer?
We can have our cake and eat it. We can use string templates, with all their declarative convenience and expressive power, without any of the drawbacks we’ve discussed. How? By using a template parser that is aware of HTML as well as our templating language of choice.
Ractive.js, a UI library originally developed at theguardian.com, was a pioneer of this technique. Ractive uses a backwards-compatible variant of mustache (other languages may be supported in future), but rather than generating strings, its parser generates an abstract syntax tree.
This AST is later combined with data (typically plain old JavaScript objects, or POJOs) to construct a lightweight data-bound ‘parallel DOM’, which can run in the browser or on the server. Through this, it is able to make smart decisions about how to update the real DOM in the most conservative manner possible.
Because it only touches the parts of the DOM it needs to, you can write your templates in the way that makes the most sense for your app, without having to worry about the performance headaches of constantly trashing the DOM or the code headache of wiring up a plethora of view objects.
I won’t go into more detail about the project – you can learn it in 60 seconds here – other than to note that we also pioneered the idea of markup-driven data visualization, which was previously impossible since there’s no SVG equivalent of innerHTML
.
But it’s a technique that’s about to become a lot more popular. Meteor, the full-stack JavaScript framework, has developed a library called Blaze which uses similar techniques, parsing string templates into a domain-specific language. Meanwhile Ember will shortly announce availability of HTMLBars (probably not its final name), an HTML-aware version of Handlebars. Apart from being largely responsible for the drastic performance improvements seen in the benchmarks above, it means Ember users now have a much nicer syntax to work with.
The future
I believe that variations on this technique represent the future of building user interfaces on the web. It’s proven to be a robust, performant, and infinitely flexible approach – and one that doesn’t demand that developers learn strange new concepts in order to become productive.
If you want to be part of that future and help build it, come on over to GitHub and join our (brand new) mailing list. You can follow @RactiveJS on Twitter.