Free email newsletter: “ News


HTML templating with ES6 template strings

Despite their name, template strings in ECMAScript 6 (ES6) are more like a different kind of function call than a mechanism for defining templates. This blog post explains how you can still use them for HTML templating. It is based on an idea by Claus Reinke.

If you are not familiar with ES6 template strings, you may want to consult [2] before reading on.

Defining and using a template

You define a template as follows. It relies on the template handler html (a function that we’ll look at later).

    const tmpl = addrs => html`
        ${ => html`

The trick is that the the inside of each substitution ${} can be an arbitrary expression. We use map() to create an array of strings inside the first substitution (which html() turns into the appropriate string). Thanks to arrow functions [3], the callback of map() is nicely concise.

Inside the callback, we are “invoking” html again. Therefore, calling the function tmpl leads to several calls of the function html.

The double dollar sign in $${addr.last} is not ES6 syntax, it is simply the normal text “$” in front of the substitution ${addr.last}. But the template handler treats a substitution differently if it is preceded by a dollar sign – it HTML-escapes the string returned by it.

The template is used like this:

        { first: '<Jane>', last: 'Bond' },
        { first: 'Lars', last: '<Croft>' },

This code produces the following output:


Note that the angle brackets around Jane and Croft are escaped, whereas <tr> isn’t.

The template handler

The template handler is surprisingly simple:

    function html(literalSections, ...substs) {
        // Use raw literal sections: we don’t want
        // backslashes (\n etc.) to be interpreted
        let raw = literalSections.raw;
        let result = '';
        substs.forEach((subst, i) => {
            // Retrieve the literal section preceding
            // the current substitution
            let lit = raw[i];
            // In the example, map() returns an array:
            // If substitution is an array (and not a string),
            // we turn it into a string
            if (Array.isArray(subst)) {
                subst = subst.join('');
            // If the substitution is preceded by a dollar sign,
            // we escape special characters in it
            if (lit.endsWith('$')) {
                subst = htmlEscape(subst);
                lit = lit.slice(0, -1);
            result += lit;
            result += subst;
        // Take care of last literal section
        // (Never fails, because an empty template string
        // produces one literal section, an empty string)
        result += raw[raw.length-1]; // (A)
        return result;

Each substitution is always surrounded by literal sections. If the template string ends with a substitution, the last literal section is an empty string. Accordingly, the following expression is always true:

    literalSections.length === substs.length + 1

That’s why we need to append the last literal section in line (A).

The following is a simple implementation of htmlEscape().

    function htmlEscape(str) {
        return str.replace(/&/g, '&amp;') // first!
                  .replace(/>/g, '&gt;')
                  .replace(/</g, '&lt;')
                  .replace(/"/g, '&quot;')
                  .replace(/'/g, '&#39;')
                  .replace(/`/g, '&#96;');

More ideas

There are more things you can do with this approach to templating:

  • This approach isn’t limited to HTML, it would work just as well for other kinds of text. Obviously, escaping would have to be adapted.

  • if-then-else inside the template can be done via the ternary operator (cond?then:else) or via the logical Or operator (||):

          $${addr.first ? addr.first : '(No first name)'}
          $${addr.first || '(No first name)'}
  • Some of the leading whitespace in each line can be trimmed if the first non-whitespace character in the first line defines where the first column is.

  • Destructuring [4] can be used:

          ${{first,last}) => html`

Should I use this in production code?

Use this approach if you need something quick and dirty. It is not as readable as the template syntax supported by Handlebars.js and similar templating engines. On the other hand, it is lightweight and control flow mechanisms (loops and if-then-else) are easy to understand, because they are just JavaScript.

Further reading

  1. Using ECMAScript 6 today [includes an overview of ECMAScript 6 and links to in-depth material]
  2. Template strings: embedded DSLs in ECMAScript 6
  3. ECMAScript 6: arrow functions and method definitions
  4. Multiple return values in ECMAScript 6


Axel Rauschmayer said...

Don't you need a `.join('')` after the map to keep from having commas between rows?

Axel Rauschmayer said...

You do, but it’s done inside the function html().

Axel Rauschmayer said...

I meant in your destructuring example. Doesn't return an array?

Axel Rauschmayer said...

It iterates over its parameter substs and does the join if one of them is an array.

Axel Rauschmayer said...

Oh! I see it now. The inner map works because the outer template is an html`` template. Thanks!

Axel Rauschmayer said...

Website Designing are effective and creative for web application. In this blog Adaptive and responsive design elaborates efficiently. So this is very important to know in a start up way for everyone who are taking website designing training.