Please support this book: buy it (PDF, EPUB, MOBI) or donate

8. Template literals

8.1 Overview

ES6 has two new kinds of literals: template literals and tagged template literals. These two literals have similar names and look similar, but they are quite different. It is therefore important to distinguish:

  • Template literals (code): multi-line string literals that support interpolation
  • Tagged template literals (code): function calls
  • Web templates (data): HTML with blanks to be filled in Template literals are string literals that can stretch across multiple lines and include interpolated expressions (inserted via ${···}):
  1. const firstName = 'Jane';
  2. console.log(`Hello ${firstName}!
  3. How are you
  4. today?`);
  5.  
  6. // Output:
  7. // Hello Jane!
  8. // How are you
  9. // today?

Tagged template literals (short: tagged templates) are created by mentioning a function before a template literal:

  1. > String.raw`A \tagged\ template`
  2. 'A \\tagged\\ template'

Tagged templates are function calls. In the previous example, the method String.raw is called to produce the result of the tagged template.

8.2 Introduction

Literals are syntactic constructs that produce values. Examples include string literals (which produce strings) and regular expression literals (which produce regular expression objects). ECMAScript 6 has two new literals:

  • Template literals are string literals with support for interpolation and multiple lines.
  • Tagged template literals (short: tagged templates): are function calls whose parameters are provided via template literals. It is important to keep in mind that the names of template literals and tagged templates are slightly misleading. They have nothing to do with templates, as often used in web development: text files with blanks that can be filled in via (e.g.) JSON data.

8.2.1 Template literals

A template literal is a new kind of string literal that can span multiple lines and interpolate expressions (include their results). For example:

  1. const firstName = 'Jane';
  2. console.log(`Hello ${firstName}!
  3. How are you
  4. today?`);
  5.  
  6. // Output:
  7. // Hello Jane!
  8. // How are you
  9. // today?

The literal itself is delimited by backticks (`), the interpolated expressions inside the literal are delimited by ${ and }. Template literals always produce strings.

8.2.2 Escaping in template literals

The backslash is used for escaping inside template literals.

It enables you to mention backticks and ${ inside template literals:

  1. > `\``
  2. '`'
  3. > `$` // OK
  4. '$'
  5. > `${`
  6. SyntaxError
  7. > `\${`
  8. '${'
  9. > `\${}`
  10. '${}'

Other than that, the backslash works like in string literals:

  1. > `\\`
  2. '\\'
  3. > `\n`
  4. '\n'
  5. > `\u{58}`
  6. 'X'

8.2.3 Line terminators in template literals are always LF (\n)

Common ways of terminating lines are:

  • Line feed (LF, \n, U+000A): used by Unix (incl. current macOS)
  • Carriage return (CR, \r, U+000D): used by the old Mac OS.
  • CRLF (\r\n): used by Windows. All of these line terminators are normalized to LF in template literals. That is, the following code logs true on all platforms:
  1. const str = `BEFORE
  2. AFTER`;
  3. console.log(str === 'BEFORE\nAFTER'); // true

8.2.4 Tagged template literals

The following is a tagged template literal (short: tagged template):

  1. tagFunction`Hello ${firstName} ${lastName}!`

Putting a template literal after an expression triggers a function call, similar to how a parameter list (comma-separated values in parentheses) triggers a function call. The previous code is equivalent to the following function call (in reality, first parameter is more than just an Array, but that is explained later).

  1. tagFunction(['Hello ', ' ', '!'], firstName, lastName)

Thus, the name before the content in backticks is the name of a function to call, the tag function. The tag function receives two different kinds of data:

  • Template strings such as 'Hello '.
  • Substitutions such as firstName (delimited by ${}). A substitution can be any expression. Template strings are known statically (at compile time), substitutions are only known at runtime. The tag function can do with its parameters as it pleases: It can completely ignore the template strings, return values of any type, etc.

Additionally, tag functions get two versions of each template string:

  • A “raw” version in which backslashes are not interpreted (\n becomes '\n', a string of length 2)
  • A “cooked” version in which backslashes are special (\n becomes a string with just a newline in it). That allows String.raw (which is explained later) to do its work:
  1. > String.raw`\n` === '\\n'
  2. true

8.3 Examples of using tagged template literals

Tagged template literals allow you to implement custom embedded sub-languages (which are sometimes called domain-specific languages) with little effort, because JavaScript does much of the parsing for you. You only have to write a function that receives the results.

Let’s look at examples. Some of them are inspired by the original proposal for template literals, which refers to them via their old name, quasi-literals.

8.3.1 Raw strings

ES6 includes the tag function String.raw for raw strings, where backslashes have no special meaning:

  1. const str = String.raw`This is a text
  2. with multiple lines.
  3. Escapes are not interpreted,
  4. \n is not a newline.`;

This is useful whenever you need to create strings that have backslashes in them. For example:

  1. function createNumberRegExp(english) {
  2. const PERIOD = english ? String.raw`\.` : ','; // (A)
  3. return new RegExp(`[0-9]+(${PERIOD}[0-9]+)?`);
  4. }

In line A, String.raw enables us to write the backslash as we would in a regular expression literal. With normal string literals, we have to escape twice: First, we need to escape the dot for the regular expression. Second, we need to escape the backslash for the string literal.

8.3.2 Shell commands

  1. const proc = sh`ps ax | grep ${pid}`;

(Source: David Herman)

8.3.3 Byte strings

  1. const buffer = bytes`455336465457210a`;

(Source: David Herman)

8.3.4 HTTP requests

  1. POST`http://foo.org/bar?a=${a}&b=${b}
  2. Content-Type: application/json
  3. X-Credentials: ${credentials}
  4.  
  5. { "foo": ${foo},
  6. "bar": ${bar}}
  7. `
  8. (myOnReadyStateChangeHandler);

(Source: Luke Hoban)

8.3.5 More powerful regular expressions

Steven Levithan has given an example of how tagged template literals could be used for his regular expression library XRegExp.

Without tagged templates, you write code such as the following:
  1. var parts = '/2015/10/Page.html'.match(XRegExp(
  2. '^ # match at start of string only \n' +
  3. '/ (?<year> [^/]+ ) # capture top dir name as year \n' +
  4. '/ (?<month> [^/]+ ) # capture subdir name as month \n' +
  5. '/ (?<title> [^/]+ ) # capture base name as title \n' +
  6. '\.html? $ # .htm or .html file ext at end of path ', 'x'
  7. ));
  8.  
  9. console.log(parts.year); // 2015
We can see that XRegExp gives us named groups (year, month, title) and the x flag. With that flag, most whitespace is ignored and comments can be inserted. There are two reasons that string literals don’t work well here. First, we have to type every regular expression backslash twice, to escape it for the string literal. Second, it is cumbersome to enter multiple lines. Instead of adding strings, you can also continue a string literal in the next line if you end the current line with a backslash. But that still involves much visual clutter, especially because you still need the explicit newline via \n at the end of each line.
  1. var parts = '/2015/10/Page.html'.match(XRegExp(
  2. '^ # match at start of string only \n\
  3. / (?<year> [^/]+ ) # capture top dir name as year \n\
  4. / (?<month> [^/]+ ) # capture subdir name as month \n\
  5. / (?<title> [^/]+ ) # capture base name as title \n\
  6. \.html? $ # .htm or .html file ext at end of path ', 'x'
  7. ));
Problems with backslashes and multiple lines go away with tagged templates:
  1. var parts = '/2015/10/Page.html'.match(XRegExp.rx</code>
  2. <code> ^ # match at start of string only</code>
  3. <code> / (?&lt;year&gt; [^/]+ ) # capture top dir name as year</code>
  4. <code> / (?&lt;month&gt; [^/]+ ) # capture subdir name as month</code>
  5. <code> / (?&lt;title&gt; [^/]+ ) # capture base name as title</code>
  6. <code> </code><code>\</code><code>.html? $ # .htm or .html file ext at end of path</code>
  7. <code>);
Additionally, tagged templates let you insert values v via ${v}. I’d expect a regular expression library to escape strings and to insert regular expressions verbatim. For example:
  1. var str = 'really?';
  2. var regex = XRegExp.rx(</code><code>${</code><code>str</code><code>}</code><code>)*;
This would be equivalent to
  1. var regex = XRegExp.rx(really</code><code>\</code><code>?)*;
#### 8.3.6 Query languages # Example:
  1. $a.</code><code>${</code><code>className</code><code>}</code><code>[href*=&#39;//</code><code>${</code><code>domain</code><code>}</code><code>/&#39;]
This is a DOM query that looks for all <a> tags whose CSS class is className and whose target is a URL with the given domain. The tag function $ ensures that the arguments are correctly escaped, making this approach safer than manual string concatenation. #### 8.3.7 React JSX via tagged templates # Facebook React is “a JavaScript library for building user interfaces”. It has the optional language extension JSX that enables you to build virtual DOM trees for user interfaces. This extension makes your code more concise, but it is also non-standard and breaks compatibility with the rest of the JavaScript ecosystem. The library t7.js provides an alternative to JSX and uses templates tagged with t7:
  1. t7.module(function(t7) {
  2. function MyWidget(props) {
  3. return t7</code>
  4. <code> &lt;div&gt;</code>
  5. <code> &lt;span&gt;I&#39;m a widget </code><code>${</code> <code>props</code><code>.</code><code>welcome</code> <code>}</code><code>&lt;/span&gt;</code>
  6. <code> &lt;/div&gt;</code>
  7. <code>;
  8. }
  9.  
  10. t7.assign('Widget', MyWidget);
  11.  
  12. t7</code>
  13. <code> &lt;div&gt;</code>
  14. <code> &lt;header&gt;</code>
  15. <code> &lt;Widget welcome=&#34;Hello world&#34; /&gt;</code>
  16. <code> &lt;/header&gt;</code>
  17. <code> &lt;/div&gt;</code>
  18. <code>;
  19. });
In “Why not Template Literals?”, the React team explains why they opted not to use template literals. One challenge is accessing components inside tagged templates. For example, MyWidget is accessed from the second tagged template in the previous example. One verbose way of doing so would be:
  1. <${MyWidget} welcome="Hello world" />
Instead, t7.js uses a registry which is filled via t7.assign(). That requires extra configuration, but the template literals look nicer; especially if there is both an opening and a closing tag. #### 8.3.8 Facebook GraphQL # Facebook Relay is a “JavaScript framework for building data-driven React applications”. One of its parts is the query language GraphQL whose queries can be created via templates tagged with Relay.QL. For example (borrowed from the Relay homepage):
  1. class Tea extends React.Component {
  2. render() {
  3. var {name, steepingTime} = this.props.tea;
  4. return (
  5. <li key={name}>
  6. {name} (<em>{steepingTime} min</em>)
  7. </li>
  8. );
  9. }
  10. }
  11. Tea = Relay.createContainer(Tea, {
  12. fragments: { // (A)
  13. tea: () => Relay.QL</code>
  14. <code> fragment on Tea {</code>
  15. <code> name,</code>
  16. <code> steepingTime,</code>
  17. <code> }</code>
  18. <code>,
  19. },
  20. });
  21.  
  22. class TeaStore extends React.Component {
  23. render() {
  24. return <ul>
  25. {this.props.store.teas.map(
  26. tea => <Tea tea={tea} />
  27. )}
  28. </ul>;
  29. }
  30. }
  31. TeaStore = Relay.createContainer(TeaStore, {
  32. fragments: { // (B)
  33. store: () => Relay.QL</code>
  34. <code> fragment on Store {</code>
  35. <code> teas { </code><code>${</code><code>Tea</code><code>.</code><code>getFragment</code><code>(</code><code>&#39;tea&#39;</code><code>)</code><code>}</code><code> },</code>
  36. <code> }</code>
  37. <code>,
  38. },
  39. });
The objects starting in line A and line B define fragments, which are defined via callbacks that return queries. The result of fragment tea is put into this.props.tea. The result of fragment store is put into this.props.store. This is the data that the queries operates on:
  1. const STORE = {
  2. teas: [
  3. {name: 'Earl Grey Blue Star', steepingTime: 5},
  4. ···
  5. ],
  6. };
This data is wrapped in an instance of GraphQLSchema, where it gets the name Store (as mentioned in fragment on Store). #### 8.3.9 Text localization (L10N) # This section describes a simple approach to text localization that supports different languages and different locales (how to format numbers, time, etc.). Given the following message.
  1. alert(msgWelcome to </code><code>${</code><code>siteName</code><code>}</code><code>, you are visitor</code>
  2. <code> number </code><code>${</code><code>visitorNumber</code><code>}</code><code>:d!);
The tag function msg would work as follows. First, The literal parts are concatenated to form a string that can be used to look up a translation in a table. The lookup string for the previous example is:
  1. 'Welcome to {0}, you are visitor number {1}!'
This lookup string could, for example, be mapped to a German translation::
  1. 'Besucher Nr. {1}, willkommen bei {0}!'
The English “translation” would be the same as the lookup string. Second, the result from the lookup is used to display the substitutions. Because a lookup result includes indices, it can rearrange the order of the substitutions. That has been done in German, where the visitor number comes before the site name. How the substitutions are formatted can be influenced via annotations such as :d. This annotation means that a locale-specific decimal separator should be used for visitorNumber. Thus, a possible English result is:
  1. Welcome to ACME Corp., you are visitor number 1,300!
In German, we have results such as:
  1. Besucher Nr. 1.300, willkommen bei ACME Corp.!
#### 8.3.10 Text templating via untagged template literals # Let’s say we want to create HTML that displays the following data in a table:
  1. const data = [
  2. { first: '<Jane>', last: 'Bond' },
  3. { first: 'Lars', last: '<Croft>' },
  4. ];
As explained previously, template literals are not templates: - A template literal is code that is executed immediately. - A template is text with holes that you can fill with data. A template is basically a function: data in, text out. And that description gives us a clue how we can turn a template literal into an actual template. Let’s implement a template tmpl as a function that maps an Array addrs to a string:
  1. const tmpl = addrs => </code>
  2. <code> &lt;table&gt;</code>
  3. <code> </code><code>${</code><code>addrs</code><code>.</code><code>map</code><code>(</code><code>addr</code> <code>=&gt;</code> <code>
  4. <tr><td>${addr.first}</td></tr>
  5. <tr><td>${addr.last}</td></tr>
  6. </code><code>).</code><code>join</code><code>(</code><code>&#39;&#39;</code><code>)</code><code>}</code><code></code>
  7. <code> &lt;/table&gt;</code>
  8. <code>;
  9. console.log(tmpl(data));
  10. // Output:
  11. // <table>
  12. //
  13. // <tr><td><Jane></td></tr>
  14. // <tr><td>Bond</td></tr>
  15. //
  16. // <tr><td>Lars</td></tr>
  17. // <tr><td><Croft></td></tr>
  18. //
  19. // </table>
The outer template literal provides the bracketing <table> and </table>. Inside, we are embedding JavaScript code that produces a string by joining an Array of strings. The Array is created by mapping each address to two table rows. Note that the plain text pieces <Jane> and <Croft> are not properly escaped. How to do that via a tagged template is explained in the next section. ##### 8.3.10.1 Should I use this technique in production code? # This is a useful quick solution for smaller templating tasks. For larger tasks, you may want more powerful solutions such as the templating engine Handlebars.js or the JSX syntax used in React. Acknowledgement: This approach to text templating is based on an idea by Claus Reinke. #### 8.3.11 A tag function for HTML templating # Compared to using untagged templates for HTML templating, like we did in the previous section, tagged templates bring two advantages: - They can escape characters for us if we prefix ${} with an exclamation mark. That is needed for the names, which contain characters that need to be escaped (<Jane>). - They can automatically join() Arrays for us, so that we don’t have to call that method ourselves. Then the code for the template looks as follows. The name of the tag function is html:
  1. const tmpl = addrs => html</code>
  2. <code> &lt;table&gt;</code>
  3. <code> </code><code>${</code><code>addrs</code><code>.</code><code>map</code><code>(</code><code>addr</code> <code>=&gt;</code> <code>html</code><code>
  4. <tr><td>!${addr.first}</td></tr>
  5. <tr><td>!${addr.last}</td></tr>
  6. </code><code>)</code><code>}</code><code></code>
  7. <code> &lt;/table&gt;</code>
  8. <code>;
  9. const data = [
  10. { first: '<Jane>', last: 'Bond' },
  11. { first: 'Lars', last: '<Croft>' },
  12. ];
  13. console.log(tmpl(data));
  14. // Output:
  15. // <table>
  16. //
  17. // <tr><td>&lt;Jane&gt;</td></tr>
  18. // <tr><td>Bond</td></tr>
  19. //
  20. // <tr><td>Lars</td></tr>
  21. // <tr><td>&lt;Croft&gt;</td></tr>
  22. //
  23. // </table>
Note that the angle brackets around Jane and Croft are escaped, whereas those around tr and td aren’t. If you prefix a substitution with an exclamation mark (!${addr.first}) then it will be HTML-escaped. The tag function checks the text preceding a substitution in order to determine whether to escape or not. An implementation of html is shown later. ### 8.4 Implementing tag functions # The following is a tagged template literal:
  1. tagFunctionlit1</code><code>\</code><code>n</code><code>${</code><code>subst1</code><code>}</code><code> lit2 </code><code>${</code><code>subst2</code><code>}</code><code>
This literal triggers (roughly) the following function call:
  1. tagFunction(['lit1\n', ' lit2 ', ''], subst1, subst2)
The exact function call looks more like this:
  1. // Globally: add template object to per-realm template map
  2. {
  3. // “Cooked” template strings: backslash is interpreted
  4. const templateObject = ['lit1\n', ' lit2 ', ''];
  5. // “Raw” template strings: backslash is verbatim
  6. templateObject.raw = ['lit1\n', ' lit2 ', ''];
  7.  
  8. // The Arrays with template strings are frozen
  9. Object.freeze(templateObject.raw);
  10. Object.freeze(templateObject);
  11.  
  12. templateMap[716] = templateObject;
  13. }
  14.  
  15. // In-place: invocation of tag function
  16. tagFunction(templateMap[716], subst1, subst2)
There are two kinds of input that the tag function receives: - Template strings (first parameter): the static parts of tagged templates that don’t change (e.g. ' lit2 '). A template object stores two versions of the template strings: - Cooked: with escapes such as \n interpreted. Stored in templateObject[0] etc. - Raw: with uninterpreted escapes. Stored in templateObject.raw[0] etc. - Substitutions (remaining parameters): the values that are embedded inside template literals via ${} (e.g. subst1). Substitutions are dynamic, they can change with each invocation. The idea behind a global template object is that the same tagged template might be executed multiple times (e.g. in a loop or a function). The template object enables the tag function to cache data from previous invocations: It can put data it derived from input kind #1 (template strings) into the object, to avoid recomputing it. Caching happens per realm (think frame in a browser). That is, there is one template object per call site and realm.

8.4.1 Number of template strings versus number of substitutions

Let’s use the following tag function to explore how many template strings there are compared to substitutions.

  1. function tagFunc(templateObject, ...substs) {
  2. return { templateObject, substs };
  3. }

The number of template strings is always one plus the number of substitutions. In other words: every substitution is always surrounded by two template strings.

  1. templateObject.length === substs.length + 1

If a substitution is first in a literal, it is prefixed by an empty template string:

  1. > tagFunc`${'subst'}xyz`
  2. { templateObject: [ '', 'xyz' ], substs: [ 'subst' ] }

If a substitution is last in a literal, it is suffixed by an empty template string:

  1. > tagFunc`abc${'subst'}`
  2. { templateObject: [ 'abc', '' ], substs: [ 'subst' ] }

An empty template literal produces one template string and no substitutions:

  1. > tagFunc``
  2. { templateObject: [ '' ], substs: [] }

8.4.2 Escaping in tagged template literals: cooked versus raw

Template strings are available in two interpretations – cooked and raw. These interpretations influence escaping:

  • In both cooked and raw interpretation, a backslash (\) in front of ${ prevents it from being interpreted as starting a substitution.
  • In both cooked and raw interpretation, backticks are also escaped via backslashes.
  • However, every single backslash is mentioned in the raw interpretation, even the ones that escape substitutions and backticks. The tag function describe allows us to explore what that means.
  1. function describe(tmplObj, ...substs) {
  2. return {
  3. Cooked: merge(tmplObj, substs),
  4. Raw: merge(tmplObj.raw, substs),
  5. };
  6. }
  7. function merge(tmplStrs, substs) {
  8. // There is always at least one element in tmplStrs
  9. let result = tmplStrs[0];
  10. substs.forEach((subst, i) => {
  11. result += String(subst);
  12. result += tmplStrs[i+1];
  13. });
  14. return result;
  15. }

Let’s use this tag function:

  1. > describe`${3+3}`
  2. { Cooked: '6', Raw: '6' }
  3.  
  4. > describe`\${3+3}`
  5. { Cooked: '${3+3}', Raw: '\\${3+3}' }
  6.  
  7. > describe`\\${3+3}`
  8. { Cooked: '\\6', Raw: '\\\\6' }
  9.  
  10. > describe`\``
  11. { Cooked: '`', Raw: '\\`' }

As you can see, whenever the cooked interpretation has a substitution or a backtick then so does the raw interpretation. However, all backslashes from the literal appear in the raw interpretation.

Other occurrences of the backslash are interpreted as follows:

  • In cooked mode, the backslash is handled like in string literals.
  • In raw mode, the backslash is used verbatim. For example:
  1. > describe`\\`
  2. { Cooked: '\\', Raw: '\\\\' }
  3.  
  4. > describe`\n`
  5. { Cooked: '\n', Raw: '\\n' }
  6.  
  7. > describe`\u{58}`
  8. { Cooked: 'X', Raw: '\\u{58}' }

To summarize: The only effect the backslash has in raw mode is that it escapes substitutions and backticks.

8.4.3 Example: String.raw

The following is how you’d implement String.raw:

  1. function raw(strs, ...substs) {
  2. let result = strs.raw[0];
  3. for (const [i,subst] of substs.entries()) {
  4. result += subst;
  5. result += strs.raw[i+1];
  6. }
  7. return result;
  8. }

8.4.4 Example: implementing a tag function for HTML templating

I previously demonstrated the tag function html for HTML templating:

  1. const tmpl = addrs => html`
  2. <table>
  3. ${addrs.map(addr => html`
  4. <tr><td>!${addr.first}</td></tr>
  5. <tr><td>!${addr.last}</td></tr>
  6. `)}
  7. </table>
  8. `;
  9. const data = [
  10. { first: '<Jane>', last: 'Bond' },
  11. { first: 'Lars', last: '<Croft>' },
  12. ];
  13. console.log(tmpl(data));
  14. // Output:
  15. // <table>
  16. //
  17. // <tr><td>&lt;Jane&gt;</td></tr>
  18. // <tr><td>Bond</td></tr>
  19. //
  20. // <tr><td>Lars</td></tr>
  21. // <tr><td>&lt;Croft&gt;</td></tr>
  22. //
  23. // </table>

If you precede a substitution with an exclamation mark (!${addr.first}), it will be HTML-escaped. The tag function checks the text preceding a substitution in order to determine whether to escape or not.

This is an implementation of html:

  1. function html(templateObject, ...substs) {
  2. // Use raw template strings: we don’t want
  3. // backslashes (\n etc.) to be interpreted
  4. const raw = templateObject.raw;
  5.  
  6. let result = '';
  7.  
  8. substs.forEach((subst, i) => {
  9. // Retrieve the template string preceding
  10. // the current substitution
  11. let lit = raw[i];
  12.  
  13. // In the example, map() returns an Array:
  14. // If `subst` is an Array (and not a string),
  15. // we turn it into a string
  16. if (Array.isArray(subst)) {
  17. subst = subst.join('');
  18. }
  19.  
  20. // If the substitution is preceded by an exclamation
  21. // mark, we escape special characters in it
  22. if (lit.endsWith('!')) {
  23. subst = htmlEscape(subst);
  24. lit = lit.slice(0, -1);
  25. }
  26. result += lit;
  27. result += subst;
  28. });
  29. // Take care of last template string
  30. result += raw[raw.length-1]; // (A)
  31.  
  32. return result;
  33. }

There is always one more template string than substitutions, which is why we need to append the last template string in line A.

The following is a simple implementation of htmlEscape().

  1. function htmlEscape(str) {
  2. return str.replace(/&/g, '&amp;') // first!
  3. .replace(/>/g, '&gt;')
  4. .replace(/</g, '&lt;')
  5. .replace(/"/g, '&quot;')
  6. .replace(/'/g, '&#39;')
  7. .replace(/`/g, '&#96;');
  8. }
8.4.4.1 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 (||):
  1. !${addr.first ? addr.first : '(No first name)'}
  2. !${addr.first || '(No first name)'}
  • Dedenting: Some of the leading whitespace in each line can be removed if the first non-whitespace character defines in which column the text starts. For example:
  1. const theHtml = html`
  2. <div>
  3. Hello!
  4. </div>`;

The first non-whitespace characters are <div>, which means that the text starts in column 4 (the leftmost column is column 0). The tag function html could automatically remove all preceding columns. Then the previous tagged template would be equivalent to:

  1. const theHtml =
  2. html`<div>
  3. Hello!
  4. </div>`;
  • You can use destructuring to extract data from parameters of functions:
  1. // Without destructuring
  2. ${addrs.map((person) => html`
  3. <tr><td>!${person.first}</td></tr>
  4. <tr><td>!${person.last}</td></tr>
  5. `)}
  6.  
  7. // With destructuring
  8. ${addrs.map(({first,last}) => html`
  9. <tr><td>!${first}</td></tr>
  10. <tr><td>!${last}</td></tr>
  11. `)}

8.4.5 Example: assembling regular expressions

There are two ways of creating regular expression instances.

  • Statically (at compile time), via a regular expression literal: /^abc$/i
  • Dynamically (at runtime), via the RegExp constructor: new RegExp('^abc$', 'i') If you use the latter, it is because you have to wait until runtime so that all necessary ingredients are available. You are creating the regular expression by concatenating three kinds of pieces:

  • Static text

  • Dynamic regular expressions
  • Dynamic text For #3, special characters (dots, square brackets, etc.) have to be escaped, while #1 and #2 can be used verbatim. A regular expression tag function regex can help with this task:
  1. const INTEGER = /\d+/;
  2. const decimalPoint = '.'; // locale-specific! E.g. ',' in Germany
  3. const NUMBER = regex`${INTEGER}(${decimalPoint}${INTEGER})?`;

regex looks like this:

  1. function regex(tmplObj, ...substs) {
  2. // Static text: verbatim
  3. let regexText = tmplObj.raw[0];
  4. for ([i, subst] of substs.entries()) {
  5. if (subst instanceof RegExp) {
  6. // Dynamic regular expressions: verbatim
  7. regexText += String(subst);
  8. } else {
  9. // Other dynamic data: escaped
  10. regexText += quoteText(String(subst));
  11. }
  12. // Static text: verbatim
  13. regexText += tmplObj.raw[i+1];
  14. }
  15. return new RegExp(regexText);
  16. }
  17. function quoteText(text) {
  18. return text.replace(/[\\^$.*+?()[\]{}|=!<>:-]/g, '\\$&');
  19. }

8.5 FAQ: template literals and tagged template literals

8.5.1 Where do template literals and tagged template literals come from?

Template literals and tagged template literals were borrowed from the language E, which calls this feature quasi literals.

8.5.2 What is the difference between macros and tagged template literals?

Macros allow you to implement language constructs that have custom syntax. It’s difficult to provide macros for a programming language whose syntax is as complex as JavaScript’s. Research in this area is ongoing (see Mozilla’s sweet.js).

While macros are much more powerful for implementing sub-languages than tagged templates, they depend on the tokenization of the language. Therefore, tagged templates are complementary, because they specialize on text content.

8.5.3 Can I load a template literal from an external source?

What if I want to load a template literal such as Hello ${name}! from an external source (e.g., a file)?

You are abusing template literals if you do so. Given that a template literal can contain arbitrary expressions and is a literal, loading it from somewhere else is similar to loading an expression or a string literal – you have to use eval() or something similar.

8.5.4 Why are backticks the delimiters for template literals?

The backtick was one of the few ASCII characters that were still unused in JavaScript. The syntax ${} for interpolation is very common (Unix shells, etc.).

8.5.5 Weren’t template literals once called template strings?

The template literal terminology changed relatively late during the creation of the ES6 spec. The following are the old terms:

  • Template string (literal): the old name for template literal.
  • Tagged template string (literal): the old name for tagged template literal.
  • Template handler: the old name for tag function.
  • Literal section: the old name for template string (the term substitution remains the same).