Temple takes one approach to rendering templates, but it's not the only (or an all-around best) approach. Just thought I should write down four ways to
a template engine.
I should say that I'm doing this not only to share knowledge about template engine, but to learn more about template engines. I've simply checked out the alternatives, but I wouldn't call myself an expert at all. So if you believe I am wrong: Awesome! I love to be proven wrong :-)
Parse & interpretThis is the technique Mustache started with, and works by parsing and evaluating the template every time it renders. This is often implemented with a while-loop + regexp and can give you quite a lot of freedom (since the evaluation can change the way parsing works later). On the other hand, this is very, very, very slow, and its parser is often written with a non-parser tool (*cough* regexp *cough*). To relate to programming languages, this is how MRI 1.8 works (well, except for the regexp-part).
def render
while parsing
render
end
end
I do not recommend writing such a template engine, mostly because it's *always* going to be slow. If you want to start write some code early on, it's far better to start with some tests and rather implement it properly later.
Parse & compile
This is what Mustache uses now. It works by parsing the template and generate Ruby code as it parses. At render-time it's simply calling eval(generated_ruby_code). This is very fast (or, that of course depends on the generated Ruby code), but it's often difficult to directly compile complex template languages.
def setup
while parsing
code << generate
end
end
def render
eval(code)
end
If you have a simply template syntax, this is a quick and dirty way to implement a fast engine.
Bytecode & interpret
This is how Liquid works. It works by parsing the template completely (into some sort of bytecode/AST/sexp) and at render-time you look through the bytecode and render the template. This allows you to use a proper parser (you hear what I'm saying Liquid?) and acceptable performance. Another interesting aspect is that you can now traverse the bytecode - it's optimizable!
This is also how virtual machines works (if we ignore JIT).
def setup
while parsing
bytecode << bytecompile
end
end
def render
bytecode.each { |b| b.render }
end
Bytecode & compile
This is the approach Temple (maybe Erubis too - I haven't checked out their internals yet) is taking. You parse the template into bytecode/AST/sexp and then you compile it into Ruby code. Then you get excellent performance, possibility for compiling in several steps and you can optimize it.
def setup
while parsing
bytecode << bytecompile
end
code = bytecode.map { |b| b.compile }
end
def render
eval(code)
end
// Magnus Holm