What’s wrong with template engines

We encounter this syntax quite often when using template engines:

<div>{{ item }}</div>

Of course, there are several implementations (ERB, EJS, DTL, etc.) but they all work pretty much the same.

The double curly braces hold a context object so if we have to output a list we need a for loop:

<div>
<ul id="foo" class="bar">
{% for item in list %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</div>

But what if we could just write the same list using methods:

<div>
${list.as_ul.with_attr('#foo.bar')}
</div>
Wouldn’t that be cleaner and faster to write?

Template string in Python

Python has a built-in Template called Template string:

'${item}s'

Unlike ES6 Template literals (we’ll see later on) we cannot use methods with Template strings.

Formatted string literals in Python

Python 3.6+ also offers Formatted string literals:

f'{item}s'

The f here stands for format.

If we need to escape curly braces we can use double curly braces:

In  [1]: f'{{item}}'
Out [1]: '{item}'

Even if this syntax looks familiar, here the double curly braces do not hold a context object which is quite a contradiction compared with Djangolike and Jinjalike template languages.

With Formatted string literals we can use methods inside curly braces (e.g. f'{item.upper()}'). This little detail makes Formatted string literals way more powerful than Template string.

Template literals in JavaScript

ES6 has something called Template literals which combines template syntax with literals:

`${item}`

Using the Template literals we can add methods inside curly braces:

`${item.toUpperCase()}`

Now, let’s create the unordered list:

`
<div>
${list.asUl}
</div>
`

Of course, to make this work, we need to create the setter asUl inside a class that inherits from the Array. This is pretty easy to achieve but it’s not part of this article.

Conventions

An example of conventions:

  • .asUl: make an unordered list;
  • .asOl: make an ordered list;
  • .asP: make a paragraph;
  • .withAttr('#foo.bar'): add attributes (id, class);
  • .withAttr('data-color="red"'): add data attribute;
  • include('base.html'): include template;
  • extend('nav.html'): extend template;

Template literals limitations

Template literals, when used as template engine, show their limitations as we start nesting tags:

`
<div>
${list.asUl.withAttr('#foo.bar').withInnerAttr('.baz')}
</div>
`

We need to implement the custom method withInnerAttr() and the markup gets hard to read.

They are also pretty slow!