At Shutterstock we recently went through the process of settling on a preferred templating language. We have lots of projects across different languages and platforms, and it was clear to the front end team that we would gain efficiency by investing in a single templating approach.
There are lots of templating languages out there, but two flavors stood out as obvious prospects: the Mustache family, and the Django family. There are strong pros and cons for each. Mustache has unmatched cross-platform support, while Django-inspired templating languages provide a more robust feature set.
We set out to compare implementations of slightly tricky tasks in both settings. Would we miss the extra features from the Django family? We used Node.js along with Swig (Django family) and Hogan (Mustache family) for our comparison.
Task #1: Position within a list
It happens from time to time that we’d like to know our index within a loop while in the context of templating. Let’s say we’re implementing some sort of thing with drag-and-drop, and we want to add a data attribute with the initial position of the element:
Position in a List with Swig
{% for name in names %}
<li data-position="{{ loop.index }}">{{ name }}</li>
{% endfor %}
Swig makes this easy. We have access to the special loop property and its attributes. This situation was anticipated.
Position in a List with Mustache
With Mustache we have to massage the data beforehand. Somewhere in code before sending to the template, we populate an index
attribute:
var names = [ 'Jacob', 'Sophia', ...];
names.forEach(function(name, index) {
names[index] = { name: name, index: index };
});
Then finally in the template:
{{#names}}
<li data-position="{{ index }}">{{ name }}</li>
{{/names}}
This is a little rough. The template itself looks great, but it’s regrettable to have to write the code that transforms names
to add index
alongside. It’s also a bit of a shame to have to mix formatting code in with business logic or controller logic.
Task #2: Currency Formatting
Often we want to show dollar amounts on the page. Let’s see how we do here with a very basic example where we want to show a list of items and a total price.
Formatting with Swig
Swig comes with some built-in filters, but not exactly any that would get us going here. That’s okay–we can define our own filter and register it with Swig:
var usd = function(value) {
return '$' + value.toFixed(2) + ' USD';
};
swig.init({ filters: { usd: usd } })
Prepare our data and render:
var invoice = {
items: [...],
price: 100
};
res.render("checkout", invoice);
Apply filters by specifying them after a pipe for interpolated values:
{% for item in items %}
<div class="item">{{ item }}</div>
{% endfor %}
<div class="price">{{ price|usd }}</div>
Formatting with Mustache
Mustache requires a similar approach. We define a lambda to pass along with the data to render. Since we want to act on the rendered value here, we need to render the input first and then return a transformation of that.
var usd = function() {
return function(template) {
var value = Hogan.compile(template).render(this);
return '$' + Number(value).toFixed(2) + ' USD';
}
};
var invoice = {
items: [...],
price: { value: 100, usd: usd }
};
res.render("checkout", invoice);
Later in the template:
{{#items}}
<div class="item">{{ item }}</div>
{{/items}}
<div class="price">
{{#price}}{{#usd}}{{ value }}{{/usd}}{{/price}}
</div>
This works, but it’s bulky and verbose having to set the price
context, and then having to wrap the value with the formatter. One way or another you have to do some massaging before you pass the data to the template. We could also have just done the formatting right within the data structure too, but it would seem a shame to have to mix display formatting with data access.
Task #3: Place CSS at the top and JavaScript at the bottom
There’s a fairly common pattern where on a given page we may want to include a page-level CSS stylesheet, generate some markup, and then add event listeners at the end. From within a page template, ideally we’d like to put a <link>
tag in the head
and inject some JavaScript just before the closing </body>
tag.
CSS at the top and JS at the bottom with Swig
Swig supports block-level inheritance. In our layout template we specify the document structure:
<!-- layout.swig -->
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="/global.css">
{% block css %}{% endblock %}
</head>
<body>{% raw %}
{% include 'header.swig' %}
{% block content %}{% endblock %}
{% include 'footer.swig' %}
{% block footer_js %}{% endblock %}
</body>
</html>
Now from within a page template we can override those blocks to get our bits to be where we want them within the document:
<!-- page.swig -->%
{% extends 'layout.swig' %}
{% block css %}
<link rel="stylesheet" type="text/css" href="/page.css">
{% endblock %}
{% block content %}
<div id="main">Wonderful content here...</div>
{% endblock %}
{% block footer_js %}
<script>
var main = document.querySelector('#main');
main.addEventListener('click', function(e) {
e.target.setAttribute('contenteditable', true);
});
</script>
{% endblock %}
CSS at the top and JS at the bottom with Mustache
Mustache doesn’t directly support inheritance so we have to fake it. There are different ways to do it, none of them especially elegant. One way is to just always piece together the whole document using partials.
<!-- css.mustache -->
<link rel="stylesheet" type="text/css" href="/global.css">
<!-- page.mustache -->
<!doctype html>
<html>
<head>
{{>css}}
<link rel="stylesheet" type="text/css" href="/page.css">
</head>
<body>
{{>header}}
<div class="main">Wonderful content here...</div>
{{>footer}}
<script>
var main = document.querySelector('#main');
main.addEventListener('click', function(e) {
e.target.setAttribute('contenteditable', true);
});
</script>
</body>
</html>
This is nicely explicit, but has its obvious drawbacks too. There are other approaches to consider as well. Some implementations support the concept of standard layouts. There’s also a proposal to update the Mustache spec to support inheritance. Hogan actually has unadvertised support for the proposed implementation. Yet another way is to use carefully crafted helpers in Handlebars. But basically when it comes to inheritance, Mustache support is a bit of a mess at the moment.
Other Considerations
Cross-Platform Support
We want to avoid having our templates lock us into a backend language or framework. Ideally if we want to migrate a project from Ruby/Sinatra to Node/Express, the last thing in the way should be the templates.
On a more practical level, we also want the flexibility to be able to share templates between the server and the client. For example, we may want to serve the first page of search results as rendered markup from the server, but then serve subsequent pages through AJAX, sending back JSON for the browser to interpolate into the same template that the server used. Both Mustache and Swig have solid in-browser implementations, so either fits the bill there.
Mustache actually has a formal spec, so you can be fairly sure that mustache templates that render with one implementation will likely render the same way in another. In real life though, since Mustache is very minimal, different implementations often add extra improvised features. Hogan adds nested accessors; Handlebars adds alternative control-flow syntax and helpers.
The Django family has decent cross-platform support, but implementations take wider liberties deviating from the original Django version. There’s Swig for JavaScript, Jinja for Python, Liquid for Ruby, DTL Template::Swig for Perl, and Twig for PHP. These implementations are all very strongly Django inspired, but again they diverge here and there.
Learning Curve
At Shutterstock we like to encourage people to reach outside their core disciplines and think holistically about what we’re all trying to do. For example, front-end developers might weigh in heavily on the direction of the product; back-end developers might suggest design directions; ops folk might suggest new business strategies, etc, etc.
So when it comes to templating, we want anyone to be able to dive in and find their way quickly, whether or not they have a background in front end development. A quick qualitative survey around the office found that relatively technical people had a much easier time picking up the Django style than they did learning the terse Mustache semantics.
Conclusion
In the end we went with the Django family—Swig, Twig, Liquid, etc. We still use Mustache here and there, but for projects of any decent size we find we really enjoy having the extra features and robustness in the Django templating languages.