April 16, 2012

How to load your Mustache templates on the client side?

Mustache.js is really awesome. It's fast, the syntax is succint (and pretty), and all but the most basic logic is hidden from you to lead you away from the path of Satan. It's been ported to umpteen platforms, so unlike things like Jinja, ERB, HAML, Smarty, etc., you can actually reuse the same templates from different languages -- importantly, javascript and whatever else. Client-side template rendering makes for a way faster experience for the user, what with the general snappiness of javascript these days. 

That said, there is still the problem of how to get the templates to your client-side code. There are a few schools of thought on this.

One school is the one taught by backbone.js's canonical todo example: embed the template in a <script> tag and use jQuery (or whatever) to extract the text for rendering.

I think this is popular mostly because it's in the canonical todo list example from backbone.js. It certainly is convenient, and I go this route when I'm not using Mustache (underscore's templates come with backbone, so you may as well use them).

Problem is, I like Mustache on the server-side too, and if not that than Jinja or Django's templates -- all of which share the {{}} syntax, and so will render parts they ought not if they see your Mustache bits just sitting there in your files.

As a developer, when you come across this, your first thought is probably to serve the template via ajax so that the js can fetch and render it on demand. You will only have this thought for a second, because it reveals itself as a stupid one pretty fast. Why send an uncompiled template from server to client this way? It defeats the point of moving application code to the client side to begin with.

Finally, you might settle on serving a js file with the templates stringified and ready to render. In my opinion, this is the correct answer; once loaded, templates are available immediately to the client-side scripts with little overhead.

Of course, if you have a very large application with a non-trivial line count of templates to serve up, you might do well do break them into chunks. Otherwise, you can do what I came up with to mash all your templates together.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
#!/usr/bin/env python
"""
The filename is grandiose. All this does is find the files in the
web/templates/mustache directory, and dump them all into web/static/js/templates.js as
members of the window.templates object.
"""
 
import os
import re
import json
 
TEMPLATE_DIR = os.path.abspath("web/templates/mustache")
files = os.listdir(TEMPLATE_DIR)
 
first = True
templates = {}
 
 
 
for fn in files:
m = re.match(r"^(?P<varname>.*)\.mustache$", fn)
if m:
with open(os.path.join(TEMPLATE_DIR, fn), "rb") as f:
tmpl_str = f.read()
templates[m.group('varname')] = tmpl_str.replace("\n", " ")
 
with open("web/static/js/templates.js", "wb") as outfile:
outfile.write("window.templates = ")
outfile.write(json.dumps(templates))
outfile.write(";")

Genius, I know. Actually, what would be really nice is to include some sort of watch function, so that you can reload a la coffeescript. Perhaps using http://packages.python.org/fs/watch.html?