ES2015 Template Literals

Kerri Shotts
10 min readJun 23, 2017

--

I think one of my most favorite features in ES2015 is the introduction of template literals. The name of the feature may sound a little odd, but in short, it gives us multi-line strings, interpolation, and some other rather interesting features.

A template literal uses a third quote: the backtick (`). Perhaps the addition of a third quote isn’t ideal, but it had to be done to maintain backwards compatibility with ES5 and earlier. So, get used to that backtick key on your keyboard, because template literals are something you’ll probably want to use frequently.

A template literal looks like this:

let str = `Hello, world`;

Well, besides the quotes, that doesn’t look all that different from a string literal, does it? Hiding behind these tick marks are some developer conveniences as well as some very powerful features, so let’s dig in!

Multi-line strings

One welcome difference is that template literals support multi-line strings. Finally!

let str = `This is a string
that is written over several
lines. Isn't that cool?`;

So, what does the string actually look like when written this way? It looks like this (␢ represents spaces; \n represents newlines):

This is a string\n␢␢that is written over several\n␢␢␢␢lines. Isn't that cool?

Notice that whitespace is preserved when using multi-line strings. This wouldn’t be terribly noticeable if you inserted this straight into the DOM, but it would be an issue if you were sending this somewhere else, or if you were displaying text on console, for example.

If you want the whitespace, great! You’re done. If you don’t want the extra whitespace, you can eliminate it any number of ways — though the easiest is probably to use a regular expression:

let str = `This is a string
that is written over several
lines. Isn't that cool?`.replace(/\s+/g, " ");

There are lots of ways that multi-line strings can be useful. For example, if you’ve built HTML templates with strings, you’ve probably written something like this in the past:

var html = "<ul>"
+ " <li>This is list item 1</li>"
+ " <li>This is list item 2</li>"
+ "</ul>";

or something like this:

var html = [
"<ul>",
" <li>This is list item 1</li>",
" <li>This is list item 2</li>",
"</ul>"
].join("");

With multi-line strings, though, it’s much easier:

let html = `
<ul>
<li>This is list item 1</li>
<li>This is list item 2</li>
</ul>`;

Note: the above isn’t exactly identical — to maintain indentation we’ve added a newline at the beginning. You could always remove it if it bugs you, or start the unordered list on the same line as the quote.

String Interpolation

Here’s where template literals really start to shine — string interpolation:

let theAnswer = 42;
let str = `The answer to the ultimate question: ${theAnswer}`;
// The answer to the ultimate question: 42

Notice the syntax for interpolation: ${...}. Anything you put inside the braces will be evaluated, which means that template literals are extremely powerful, since you can evaluate arbitrary expressions, so technically this feature is better called expression interpolation. Here’s a simple example that adds two variables together within a string:

let x = 2, y = 3;
let str = `The answer to ${x} + ${y} is ${x + y}`;
// The answer to 2 + 3 is 5

Because arbitrary expressions are valid, we can reference properties and call methods:

let words = ["fruit", "vegetable", "tree", "seed"];
let lengthOfWordStrs = words.map(word =>
`The length of ${word.toUpperCase()} is ${word.length}`);
// [ "the length of FRUIT is 5", "the length of VEGETABLE is 9",
"the length of TREE is 4", "the length of SEED is 4" ]

It’s even possible to nest template literals:

let word = "world";
let str = `hello, ${word.split("").map(c => `(${c})`).join("")}`;
// hello, (w)(o)(r)(l)(d)

… but this example hints at why it’s probably best to avoid complex expressions — they quickly start to hurt the eyeballs! I don’t know about you, but while I like short code, I like it only when it is readable. If terse code is trying to be too clever by half, then that’s probably not great for long-term maintainability.

Technically expression interpolation is equivalent to concatenation, so you do need to worry about the same security risks as you would when concatenating strings. For example, you shouldn’t ever write a SQL statement using only string interpolation, since it’s subject to the same injection risks as when using concatenation. In the case of SQL, you’d still want to use bindings instead, which don’t have the same risk. Or, if you were inserting the result of a template literal into the DOM, you’re subject to the same risk of a DOM injection attack as you are if you use regular strings and process untrusted data.

Tagged Template Literals

So, as template literals stand, they’re pretty cool. But they can do more… much more. Enter tagged template literals. Now, I’ll warn you up front: the syntax takes a little getting used to — at least it did for me, anyway — but once you do get used to it, it can be extremely useful.

A tagged template literal looks like this:

let s = reverseWords`The quick brown fox jumped over the lazy dog`;

That reverseWords represents the template literal’s tag. On its own, trying to execute this will actually return an error (“reverseWords is not defined”). Why? Because the tags are functions. This means we can actually do some pretty powerful stuff!

Let’s define the function to make the above statement work:

function reverseWords(strings) {
return strings.join(" ")
.split(/\s+/)
.map((_, idx, arr) => arr[(arr.length - idx) - 1])
.join(" ");
}

Now when we execute the previous let, s will contain “dog lazy the over jumped fox brown quick The”.

Okay, so that’s interesting, right? But if we try to add some interpolation into the mix, it falls apart:

let foxColor = "brown";
let dogEnergy = "lazy";
let s = reverseWords`The quick ${foxColor} fox jumped over the
${dogEnergy} dog`);
// "dog the over jumped fox quick The"

Hmm — “brown” and “lazy” have gone missing! That’s… not right — the interpolants have been completely ignored.

It turns out that tagged template literal functions take additional parameters which actually contain the interpolations. Which means we have to do a little more work to make something that looks sensible. First off, let’s examine the arguments and their types:

function tagFn(strings:Array[String], parm1:any, parm2:any, ...) {}

Hmm — that’s interesting. strings is passed to us as an array of strings, but any interpolations are passed as additional arguments. Well, that’s easy to rectify using ES2015’s rest operator (which we haven’t covered yet in this series, but is really simple: take the remaining items and collect into an array):

function reverseWords(strings, ...args) {}

Now that we have two arrays, all we need to do is zip them together so that we have an array that looks something like this:

[
["The quick ", "brown"],
[" fox jumped over the ", "lazy"],
[" dog.", undefined]
]

Let’s write a small utility method to do just that, as shown below.

function zip(a, b) {
let acc = [];
for (let i = 0, l = Math.max(a.length, b.length); i < l; i++) {
acc.push([a[i], b[i]]);
}
return acc;
}

Great, that will let us combine both the string literal and the interpolation together in a single array. (The point of that is so it is easy to map over or reduce.) There’s only one catch here — it’s possible for us to have a mismatched number of string literals and interpolants, so we’ll have to worry about that in our reverseWords method.

Here’s what that might look like:

function reverseWords(strings, ...args) {
let numArgs = args.length;
return zip(strings, args)
.map(([literal, val], idx) =>
`${literal}${idx < numArgs ? val : ""}`)
.join(" ")
.split(/\s+/)
.map((_, idx, arr) => arr[(arr.length - idx) - 1])
.join(" ");
}

And now it works properly, as you can see in the result below.

let s = reverseWords`The quick ${foxColor} fox jumped over the 
${dogEnergy} dog`);
// "dog lazy the over jumped fox brown quick The"

JSBin Link: https://jsbin.com/pacofuhunu/2/edit?js,console

A tagged template literal doesn’t have to return a string, though. It can return anything a function can return. For example:

function p(strings, ...args) {
let el = document.createElement("p"),
items = zip(strings, args),
numArgs = args.length;
el.textContent = items
.map(([str, val], idx) =>
`${str}${idx < numArgs ? val : ""}`)
.join("");
return el;
}
let el = p`This is a paragraph!`;

el is now a HTMLParagraphElement that contains the text “This is a paragraph”. Because it’s a DOM Node, we can add it to the DOM tree:

document.body.appendChild(el);

JSBin link: https://jsbin.com/xikocipaca/edit?js,output

Another useful example is related to SQL statements. SQL injection is a common problem — there are lots of bad examples out there that concatenate variables instead of binding them, and we’ve already warned against using pure expression interpolation alone, since that opens you up to the same risk.

// BAD, BAD, VERY BAD, DON'T EVER DO THIS, EVER, EVER, EVER!
// PLEASE, I'M BEGGING YOU: DO NOT USE!
let lastName = "Smith";
let sql = `SELECT * FROM PEOPLE WHERE LAST_NAME = ${lastName}`;

The correct pattern is to use bindings, where a question mark indicates where an item will be substituted.

let sql = `SELECT * FROM PEOPLE WHERE LAST_NAME = ?`;
let binds = [lastName];

There is a downside, though, in that the bind variable is no longer in the position where it’ll be used, and you can have multiple bindings (so multiple question marks), and those bindings have no obvious relation to their index in the binding array, which can make a SQL statement with multiple bindings difficult to read. What if we could have the variables right where they would be used, but still avoid SQL injection issues?

Tagged template literals to the rescue:

function sql(strings, ...args) {
let numArgs = args.length;
return zip(strings, args)
.reduce((acc, [literal, arg], idx) => {
acc.sql += literal;
if (idx < numArgs) {
acc.sql += "?";
acc.binds.push(arg);
}
return acc;
}, {sql: "", binds: []});
}
let salary = 60000;
let companyId = 1234;
let preparedSql = sql`
SELECT employeeId
FROM employees
WHERE companyId = ${companyId}
AND annualSalary > ${salary}
`;
// preparedSql.sql: "
// SELECT employeeId
// FROM employees
// WHERE companyId = ?
// AND annualSalary > ?
// "
// preparedSql.binds: [1234, 60000]

JSBin Link: https://jsbin.com/zeludedode/2/edit?js,console

And guess what! We get the benefits of using SQL bindings alongside the benefits of having those variables immediately at their place of use.

Now, obligatory warning here: you might be tempted to think that any variable in a template literal is safe from injection. That couldn’t be further from the truth! You can only ensure that you are safe from injection if you build a correct tagged template literal function, and then always use it. (And “correct” will mean different things depending upon the various security scenarios.)

Performance

As with all things ES2015, we need to think about performance, relative to what we used to do in ES5. So, let’s take a look, shall we? In the below table, the second and third columns are for Chrome, followed by Firefox, and then the last two columns are for Safari and WebKit, respectively. This data is from http://incaseofstairs.com/six-speed/ from January 4th, 2017.

Source: http://incaseofstairs.com/six-speed/, as of January 4th, 2017. The second and third columns are for Chrome, the next two are for Firefox, and the last two columns are for Safari and Webkit respectively. Note that the first case tests interpolation, and is generally not far off from the ES5 equivalent, except in Safari and WebKit (which is slower). Tagged template literals, however, are slower in every engine.

As with all microbenchmarks, it’s important to recognize that these results may not translate to the real world. And engines have progressed since the beginning of the year, and will continue to do so, so I expect the performance deltas to improve. Given this, I don’t avoid using template literals for performance reasons in my code unless there’s a very big gain to be had.

When I Avoid Template Literals

You’ll surely have noticed that I haven’t made every string in these examples a template literal. As such, my code is a mix between regular strings (in my case, the heretical double quote), and template literals. Why don’t I use template literals exclusively?

Well, you’re free to do so, of course, but currently I only use template literals if I’m going to benefit from the feature itself. That is, if my string isn’t going to contain an interpolation, break across lines of code, or use a tag function, I’ll still use a regular string instead. If I think that the string may eventually take an interpolation, I may plan ahead and use a template literal, but that’s about the only exception.

I’ll also avoid template literals for very complex expression interpolation — not because template literals can’t support complex interpolations, but because I think it’s easier to read and understand. A template literal full of complex expressions (especially if ternary operators are involved) can quickly devolve into an unintelligible mess… which I try to avoid. I don’t mind short expressions in a template literal, but one that involves a lot of steps and decisions…, well, I keep that separate.

For example:

// I don't use a template literal with a character definition
const L_PAREN = "(";
// or with a short string that will never need interpolations
const GT_ENTITY = "&gt;";
// or inside an interpolated expression if not othwerwise necessary
let arr = [1, 2, 3, 4];
let strs = arr.map(item =>
`Is ${item} even? ${item % 2 === 0 ? "yes" : "no"}`);

I should note that if you’re using ESLint (you should be!), you can configure ESLint to prefer using template literals for concatenation (http://eslint.org/docs/rules/prefer-template) if you want, as well as catch potential errors should you try using an interpolation in a regular string (http://eslint.org/docs/rules/no-template-curly-in-string).

Wrapping Up

There’s all sorts of interesting uses for template literals, and especially tagged template literals. You should peruse the sources and additional links at the bottom of this post as there are some very interesting examples that might spark your imagination.

Next up: Destructuring. It sounds like a scary word, but you’ve already seen examples of it, and it’s not nearly as scary as it sounds, and can increase clarity and reduce boilerplate code. It also lets us explore some interesting coding patterns.

Like what I’m doing? Consider becoming a patron!

Creating articles, technical documentation, and contributing to open source software and the like takes a lot of time and effort! If you want to support my efforts would you consider becoming my patron on Patreon?

--

--

Kerri Shotts

JavaScript fangirl, Technical writer, Mobile app developer, Musician, Photographer, Transwoman (she/her), Atheist, Humanist. All opinions are my own. Hi there!