The evolution of view templating in 9 easy steps
If you squint hard enough, Greenspun's tenth rule says:
Any sufficiently complicated templating language contains an ad hoc, informally-specified, bug-ridden, slow implementation of a turing complete programming language.
Hindsight is a wonderful thing. Let's see what it teaches us about the current state of the V in MVC:
1. It all started with the render method.
def greet
name = params[:name]
render text: "Hello #{name}!"
end
2. Then the text grew rich and spouted HTML tags:
def greet
name = params[:name]
render text: "<html><h1>Hello #{name}!</h1></html>"
end
3. It became unwieldy, and multi-line string was born.
def greet
name = params[:name]
render text: %{
<html>
<h1>Hello #{name}!</h1>
</html>
}
end
4. There was more to say and more to do. We had to render a todo list, for which the code acquired loops:
def greet
name = params[:name]
todos = ["wake up", "step into the hamster wheel"]
todosView = todos.map.with_index {|todo, index|
%{
<p>
#{index+1} -
#{todo}
</p>
}
}.join("\n")
render text: %{
<html>
<h1>Hello #{name}!</h1>
#{todosView}
</html>
}
end
5. But this violated certain principles, to rectify which the first templates were born. Instead of muddling the code with string munging, we split the view into a separate template file, and passed just the required data into it:
def greet
name = params[:name]
todos = ["wake up", "step into the hamster wheel"]
render file: "greeting.html.erb", locals: {
name: name,
todos: todos
}
end
6. The template, mostly static, contained a few dynamic portions for which it had to do things like asking questions (conditionals), repetition (loops), and re-use (partials). This meant interspersing the content with code:
<html>
<h1>
Hello <%= "#{name} !" if name %>
</h1>
<% todos.each_with_index do |todo, index| %>
<p>
<%= index+1 %> -
<%= todo %>
</p>
<% end %>
</html>
7. But everyone knew that mixing logic and view was wrong. Logic-less
templates were born to get rid of this uneasy co-existence. They absorbed the imperative ugliness of code into declarative
directives. Purity!
Things started to get weird just about then:
<html>
<h1>
Hello {{name }} {% raw %}
<span ng-if="name">{{name}} !</span> {% endraw %}
</h1>
<p ng-repeat="todo in todos">{% raw %}
{{ $index+1 }} -
{{ todo }} {% endraw %}
</p>
</html>
8. This got the ball rolling. Templates went on an acquisition spree. It gets expressions and filters:
<input ng-model="searchText">
<p ng-repeat="todo in todos | filter:search"> {% raw %}
{{ $index+1 }} -
{{ todo }} {% endraw %}
</p>
9. There is a limit to the expressive powers of a language if it doesn't let the user build their own abstractions. Logic-less templates compensates for this by opting-in the abstractions of its host language:
{% raw %}
{{#list people}}
{{firstName}} {{lastName}}
{{/list}}
{% endraw %}
Handlebars.registerHelper('list', function(items, options) {
var out = "<ul>";
for(var i=0, l=items.length; i<l; i++) {
out = out + "<li>" + options.fn(items[i]) + "</li>";
}
return out + "</ul>";
});
And thus we come full circle. What started out as logic-less templates became template programming languages, replete with expressions, conditionals, loops, scoping rules, interop with a host language, in short, an ad-hoc bug-ridden implementation of a programming language.
View Templates began as a way to separate content from code. This used to work reasonably well in the past when most of the web was just content. The occasional need for programmable expression was easily satisfied by string interpolation. I'm writing this very post in a content-primary format (HTML with a few Jekyll helpers). It is a good compromise.
This paradigm however doesn't suit webapps. A website is a programmatically enhanced view, but a webapp is a programmatically constructed view. Construction calls for abstractions. We can either port back programming abstractions in the form of helpers and directives into templates, giving rise to unwiedly templating languages with custom syntax and innumerable quirks, or we could simply use code, the best tool for the job.
So how did view templates become so pervasive? I can count a few reasons:
- The web was historically content oriented for which view templates were a natural medium.
- It is easier for designers and front-end developers who solely write HTML+CSS to work on a view template than code.
- Code was not suited for view templating because it couldn't differentiate between strings and XML view templates.
The HTML snippet embedded in titleView = "<h1>#{"Hello!".upcase}</h1>" can only be parsed as a string, and not as an XML view. This means we cannot get any tooling support from the editor. Moreover, building large strings using concatenation is unwieldy.
But it is easy to parse view templates. Consider the following ERB template:
<h1><%= "Hello!".upcase %></h1>. The point at which the template
ends and code begins is very clear cut. This enables programming editors to parse and
and provide tooling around it - linting, syntax highlight, auto-indent, autocomplete, jump to etc.
Out of the above three points, both the first and last ones no longer hold. The web has become dynamic and content takes the back-stage. As to tooling, Facebook made code friendly to XML in 2010 with the release of XHP. With XHP, developers could embed XML into PHP with no extra syntax. This idea was brought to front-end development by JSX+React. Building applications with React's component model is very pleasant, the resulting view code is better organized and more maintainable (I talk about how React makes the front-end a fully-programmable system in this post), and the tooling support for JSX on IntelliJ IDEs is excellent.
With JSX, we are able to do templates better than they could. We can mix and match code and views and build well organized front-end code without having to reinvent programming constructs into yet another templating language.