Template Literals

At the very outset of this section, I’m going to have to call out the name of this ES6 feature as being awfully… misleading, depending on your experiences with what the word template means.

Many developers think of templates as being reusable renderable pieces of text, such as the capability provided by most template engines (Mustache, Handlebars, etc.). ES6’s use of the word template would imply something similar, like a way to declare inline template literals that can be re-rendered. However, that’s not at all the right way to think about this feature.

So, before we go on, I’m renaming to what it should have been called: interpolated string literals (or interpoliterals for short).

You’re already well aware of declaring string literals with " or ' delimiters, and you also know that these are not smart strings (as some languages have), where the contents would be parsed for interpolation expressions.

However, ES6 introduces a new type of string literal, using the ` backtick as the delimiter. These string literals allow basic string interpolation expressions to be embedded, which are then automatically parsed and evaluated.

Here’s the old pre-ES6 way:

  1. var name = "Kyle";
  2. var greeting = "Hello " + name + "!";
  3. console.log( greeting ); // "Hello Kyle!"
  4. console.log( typeof greeting ); // "string"

Now, consider the new ES6 way:

  1. var name = "Kyle";
  2. var greeting = `Hello ${name}!`;
  3. console.log( greeting ); // "Hello Kyle!"
  4. console.log( typeof greeting ); // "string"

As you can see, we used the `..` around a series of characters, which are interpreted as a string literal, but any expressions of the form ${..} are parsed and evaluated inline immediately. The fancy term for such parsing and evaluating is interpolation (much more accurate than templating).

The result of the interpolated string literal expression is just a plain old normal string, assigned to the greeting variable.

Warning: typeof greeting == "string" illustrates why it’s important not to think of these entities as special template values, as you cannot assign the unevaluated form of the literal to something and reuse it. The `..` string literal is more like an IIFE in the sense that it’s automatically evaluated inline. The result of a `..` string literal is, simply, just a string.

One really nice benefit of interpolated string literals is they are allowed to split across multiple lines:

  1. var text =
  2. `Now is the time for all good men
  3. to come to the aid of their
  4. country!`;
  5. console.log( text );
  6. // Now is the time for all good men
  7. // to come to the aid of their
  8. // country!

The line breaks (newlines) in the interpolated string literal were preserved in the string value.

Unless appearing as explicit escape sequences in the literal value, the value of the \r carriage return character (code point U+000D) or the value of the \r\n carriage return + line feed sequence (code points U+000D and U+000A) are both normalized to a \n line feed character (code point U+000A). Don’t worry though; this normalization is rare and would likely only happen if copy-pasting text into your JS file.

Interpolated Expressions

Any valid expression is allowed to appear inside ${..} in an interpolated string literal, including function calls, inline function expression calls, and even other interpolated string literals!

Consider:

  1. function upper(s) {
  2. return s.toUpperCase();
  3. }
  4. var who = "reader";
  5. var text =
  6. `A very ${upper( "warm" )} welcome
  7. to all of you ${upper( `${who}s` )}!`;
  8. console.log( text );
  9. // A very WARM welcome
  10. // to all of you READERS!

Here, the inner `${who}s` interpolated string literal was a little bit nicer convenience for us when combining the who variable with the "s" string, as opposed to who + "s". There will be cases that nesting interpolated string literals is helpful, but be wary if you find yourself doing that kind of thing often, or if you find yourself nesting several levels deep.

If that’s the case, the odds are good that your string value production could benefit from some abstractions.

Warning: As a word of caution, be very careful about the readability of your code with such new found power. Just like with default value expressions and destructuring assignment expressions, just because you can do something doesn’t mean you should do it. Never go so overboard with new ES6 tricks that your code becomes more clever than you or your other team members.

Expression Scope

One quick note about the scope that is used to resolve variables in expressions. I mentioned earlier that an interpolated string literal is kind of like an IIFE, and it turns out thinking about it like that explains the scoping behavior as well.

Consider:

  1. function foo(str) {
  2. var name = "foo";
  3. console.log( str );
  4. }
  5. function bar() {
  6. var name = "bar";
  7. foo( `Hello from ${name}!` );
  8. }
  9. var name = "global";
  10. bar(); // "Hello from bar!"

At the moment the `..` string literal is expressed, inside the bar() function, the scope available to it finds bar()‘s name variable with value "bar". Neither the global name nor foo(..)‘s name matter. In other words, an interpolated string literal is just lexically scoped where it appears, not dynamically scoped in any way.

Tagged Template Literals

Again, renaming the feature for sanity sake: tagged string literals.

To be honest, this is one of the cooler tricks that ES6 offers. It may seem a little strange, and perhaps not all that generally practical at first. But once you’ve spent some time with it, tagged string literals may just surprise you in their usefulness.

For example:

  1. function foo(strings, ...values) {
  2. console.log( strings );
  3. console.log( values );
  4. }
  5. var desc = "awesome";
  6. foo`Everything is ${desc}!`;
  7. // [ "Everything is ", "!"]
  8. // [ "awesome" ]

Let’s take a moment to consider what’s happening in the previous snippet. First, the most jarring thing that jumps out is foo`Everything...`;. That doesn’t look like anything we’ve seen before. What is it?

It’s essentially a special kind of function call that doesn’t need the ( .. ). The tag — the foo part before the `..` string literal — is a function value that should be called. Actually, it can be any expression that results in a function, even a function call that returns another function, like:

  1. function bar() {
  2. return function foo(strings, ...values) {
  3. console.log( strings );
  4. console.log( values );
  5. }
  6. }
  7. var desc = "awesome";
  8. bar()`Everything is ${desc}!`;
  9. // [ "Everything is ", "!"]
  10. // [ "awesome" ]

But what gets passed to the foo(..) function when invoked as a tag for a string literal?

The first argument — we called it strings — is an array of all the plain strings (the stuff between any interpolated expressions). We get two values in the strings array: "Everything is " and "!".

For convenience sake in our example, we then gather up all subsequent arguments into an array called values using the ... gather/rest operator (see the “Spread/Rest” section earlier in this chapter), though you could of course have left them as individual named parameters following the strings parameter.

The argument(s) gathered into our values array are the results of the already-evaluated interpolation expressions found in the string literal. So obviously the only element in values in our example is "awesome".

You can think of these two arrays as: the values in values are the separators if you were to splice them in between the values in strings, and then if you joined everything together, you’d get the complete interpolated string value.

A tagged string literal is like a processing step after the interpolation expressions are evaluated but before the final string value is compiled, allowing you more control over generating the string from the literal.

Typically, the string literal tag function (foo(..) in the previous snippets) should compute an appropriate string value and return it, so that you can use the tagged string literal as a value just like untagged string literals:

  1. function tag(strings, ...values) {
  2. return strings.reduce( function(s,v,idx){
  3. return s + (idx > 0 ? values[idx-1] : "") + v;
  4. }, "" );
  5. }
  6. var desc = "awesome";
  7. var text = tag`Everything is ${desc}!`;
  8. console.log( text ); // Everything is awesome!

In this snippet, tag(..) is a pass-through operation, in that it doesn’t perform any special modifications, but just uses reduce(..) to loop over and splice/interleave strings and values together the same way an untagged string literal would have done.

So what are some practical uses? There are many advanced ones that are beyond our scope to discuss here. But here’s a simple idea that formats numbers as U.S. dollars (sort of like basic localization):

  1. function dollabillsyall(strings, ...values) {
  2. return strings.reduce( function(s,v,idx){
  3. if (idx > 0) {
  4. if (typeof values[idx-1] == "number") {
  5. // look, also using interpolated
  6. // string literals!
  7. s += `$${values[idx-1].toFixed( 2 )}`;
  8. }
  9. else {
  10. s += values[idx-1];
  11. }
  12. }
  13. return s + v;
  14. }, "" );
  15. }
  16. var amt1 = 11.99,
  17. amt2 = amt1 * 1.08,
  18. name = "Kyle";
  19. var text = dollabillsyall
  20. `Thanks for your purchase, ${name}! Your
  21. product cost was ${amt1}, which with tax
  22. comes out to ${amt2}.`
  23. console.log( text );
  24. // Thanks for your purchase, Kyle! Your
  25. // product cost was $11.99, which with tax
  26. // comes out to $12.95.

If a number value is encountered in the values array, we put "$" in front of it and format it to two decimal places with toFixed(2). Otherwise, we let the value pass-through untouched.

Raw Strings

In the previous snippets, our tag functions receive the first argument we called strings, which is an array. But there’s an additional bit of data included: the raw unprocessed versions of all the strings. You can access those raw string values using the .raw property, like this:

  1. function showraw(strings, ...values) {
  2. console.log( strings );
  3. console.log( strings.raw );
  4. }
  5. showraw`Hello\nWorld`;
  6. // [ "Hello
  7. // World" ]
  8. // [ "Hello\nWorld" ]

The raw version of the value preserves the raw escaped \n sequence (the \ and the n are separate characters), while the processed version considers it a single newline character. However, the earlier mentioned line-ending normalization is applied to both values.

ES6 comes with a built-in function that can be used as a string literal tag: String.raw(..). It simply passes through the raw versions of the strings values:

  1. console.log( `Hello\nWorld` );
  2. // Hello
  3. // World
  4. console.log( String.raw`Hello\nWorld` );
  5. // Hello\nWorld
  6. String.raw`Hello\nWorld`.length;
  7. // 12

Other uses for string literal tags included special processing for internationalization, localization, and more!