Template Literals From Frameworks To Benchmarks pt. 2

December 16, 2018 - Sam Messina

Ever wish you could compose JavaScript strings without the silly "string" + "string" notation? Want to speed up your DOM updates? Are you sick of the Virtual DOM Loading for every request?

Then do we have a show for you! Introduction: JavaScript Template Literals.

This is part two of a three part series. Check out Part 1: The Problem Introduction, and Part 3: HTML5 Template Elements as well.

Basics

Template literals were introduced with the ES6 update, and are essentially JavaScript strings on steroids. The most basic addition template literals offer are multi-line strings.

The ES5 version of the language required multi-line strings to be added together:

var long_string = "Wow, this is a " +
"really long string!";

Now with ES6, backticks can span newlines when declaring or updating strings

let long_string = `Wow, this is a
really long string!`;

Ok. Sort of cool, but not a huge deal.

Let’s dive deeper. Say you want to intermingle JavaScript data and plain text strings. Here’s the ES5 way:

var my_name  = "Sam Messina";
var greeting = "Good morning, " + my_name + "!";
// Good morning, Sam Messina! 

With ES6 template literals, we can make it a bit more concise:

let my_name  = "Sam Messina";
let greeting = `Good morning, ${my_name}!`;
// Good morning, Sam Messina! 

Those curly brackets, prepended by a dollar sign, are how we interject data into our strings. And they’re magical.

Let’s see what we can do with variable states:

let my_name  = "Sam Messina";
let date     = new Date();
let greeting = `Good ${date.getHours() > 12 ? 'afternoon' : 'morning'}, ${my_name}!`;
// Good morning, Sam Messina! 

Pretty simple, right? But even with just this, we can recreate most of what I, personally, have previously relied on JSX to do. In the following example, our createHtmlTemplate() function acts similarly to React’s render() function, returning an HTML string interspersed with dynamic JavaScript data to create a time-sensitive, personalized greeting:

const createHtmlTemplate = (name) => {
  let date = new Date();
  let greeting = `Good ${date.getHours() > 12 ? 'afternoon' : 'morning'}, ${name}!`;

  return `

${greeting}

`; //

Good morning, Sam!

}

Pretty neat, but it’s really just syntactic sugar at this point.

The Amazing Race: Template Literals vs. String concatenation

In addition to the syntax benefits, there are also performance boosts to template literals. Turns out, template literals are fantastically faster for combining strings and data.

A JSPerf test on my machine shows that template literals are 46% faster.

Let’s take it a step further…

Pulling Back The Curtain

The curious among you may be asking at this point:

“How does the JavaScript interpreter take actually do that?”

To which I’d like to say

“Great question!”

So let’s take a step back and consider how we would design a homegrown function to implement template literals ourselves (remember, the only way to get optimally performant code is to ditch the frameworks and write it yourself).

First, we need the template string.

const makeTemplateLiteral = (template_string) => {};

Next, we need the data that we inject into the template string.

We could have any number of data supplied to our template literals, so we certainly don’t want to hard-code this parameter. Fortunately, ES6 comes to the rescue with rest parameters.

I won’t get too much into these here, but they work very similarly to argc and argv in C. Essentially, any number of parameters can be added (argc), and they will be returned to the function in a single array (argv).

Rest parameters are noted by prepending “...” to the parameter, as follows:

const unknownNumOfParameters = (...array_of_parameters) => {
  for (let param of array_of_parameters) {
    console.log(`Got param: ${param}`); // template literals, y'all
  };
};

We can now pass in any number of parameters to unknownNumOfParameters(), and they will be combined into a single array: array_of_parameters.

unknownNumOfParameters("Look", "at", "all", "these", "parameters", "!");

So let’s use rest parameters to pass in our dynamic data and improve our homegrown template literal function:

const makeTemplateLiteral = (template_string, ...data_array) => {};

We now have our template string as well as the data to be injected into it. But the question remains: where in the string do we inject the data?

To answer this, we have to take another step back. Let’s revisit our greeting example. We have two pieces of data (name and time of day) that we want to inject into our string at specific points. Going back to ES5, we would write this as follows:

var createHtmlTemplate = function(name) {
  // name = "Sam Messina"

  var date     = new Date();
  var greeting = "Good " + (date.getHours() > 12 ? 'afternoon' : 'morning') + ", " + name + "!";

  return "

" + greeting + "

"; //

Good morning, Sam Messina!

}

Essentially, our template string gets broken up around the data we are trying to inject.

Since we’re already passing our data to the function with an array, why not do the same for our string? After all, in C all strings are simply arrays of characters, right?

To do this, we simply split the string any time we inject dynamic data. So Good ${time_of_day}, ${name}! turns into ["Good ", ", ", "!"] after being split. Any dynamic data supplied is added between the array’s elements.

const makeTemplateLiteral = (template_array, ...data_array) => {
  let final_string = ""; // the string we are creating
  let data_element;      // the data element to inject

  template_array.forEach((template_string, i) => {
      // look in data_array for the next data element
      data_element  = data_array[i] ? data_array[i] : '';
      final_string += template_string + data_element;
      });

  return final_string;
};

All right! We now have a primitive template literal function. Obviously, this function doesn’t accomplish all that the language specification does, but we’re well on our way to understanding how these template literals are parsed.

Tagged Template Literals: Putting it all together

Tagged template literals can be confusing, but if you’ve been following along so far, you’ve actually already made your first one.

Let’s start with a definition. Tagged template literals are nothing more than a wrapper function for the template literal specification. Regular template literals simply concatenate your data and strings into a single entity. Tagged template literals allow you to manipulate both the data, as well as how the concatenation occurs.

Looking back on our function, makeTemplateLiteral(), we’ve broken down template literals into two essential data structures:

  1. A string array to hold our template string
  2. A rest parameter array to hold our dynamic data elements

We represented this in standard function form:

const makeTemplateLiteral = (template_array, ...data_array) => {
  let final_string = ""; // the string we are creating
  let data_element;      // the data element to inject

  template_array.forEach((template_string, i) => {
      // look in data_array for the next data element
      data_element  = data_array[i] ? data_array[i] : '';
      final_string += template_string + data_element;
      });

  return final_string;
};

And we would call that function as follows:

let my_name     = "Sam Messina";
let date        = new Date();
let time_of_day = date.getHours() > 12 ? 'afternoon' : 'morning';

// notice how the data elements are passed as seperate parameters
let my_string   = makeTemplateLiteral(["Good ", ", ", "!"],
                  my_name,
                  time_of_day);

// my_string == "Good morning, Sam Messina!";

Turns out, makeTemplateLiteral() is already a Tagged Template Literal function. To use it as such, we simply use a different syntax. Instead of using the traditional function call syntax (shown above), we substitute parentheses for backticks, and substitute our parameters with a template literal:

let my_name      = "Sam Messina";
let date         = new Date();
let time_of_day  = date.getHours() > 12 ? 'afternoon' : 'morning';

// traditional function call
let func_string  = makeTemplateLiteral(["Good ", ", ", "!"],
    [my_name, time_of_day]);

// tagged template literal call
let templ_string = makeTemplateLiteral`Good ${time_of_day}, ${my_name}!`;

console.log(func_string === templ_string) // "true"

Essentially, JavaScript is giving us a default approach to template literals by simply concatenating data and strings together. But we’re also given the ability to overwrite, overload, or otherwise overhaul the default specifications to make something of our own.

In case you missed it, that’s actually amazing. Let me show you with some examples.

Use Cases

Do you need DOM Nodes instead of strings?

const createDomNode = (template_array, ...data_array) => {
  let final_string = "";

  template_array.forEach((template_string, i) => {
      data_element  = data_array[i] ? data_array[i] : '';
      final_string += template_string + data_element;
      });

  return document.createRange().createContextualFragment(final_string);
}

Or maybe all your data elements are code samples, and you want to wrap them with a pre tag.

const wrapPreTags = (template_array, ...data_array) => {
  let final_string = "";

  template_array.forEach((template_string, i) => {
      data_element  = data_array[i] ? `
data_array[i]
` : ''; final_string += template_string + data_element; }); return final_string; }

A major benefit of tagged template literals over plain template literals is the added capacity for composition. Tagged template literals can be used to evaluate functions, objects, or otherwise difficult-to-parse data structures within template literals. Free Code Camp discusses this in a bit more length in their own writeup here.

And at this point, I want to reiterate that no frameworks are needed. Not React, not Lit HTML. You can paste this code into your dev tools and be up and running.

So why doesn’t React use template literals?

The React developers actually discuss this in their post: “Why Not Template Literals?.” Essentially, non-trivial layout composition gets syntactically messy when using native JavaScript template literals.

This is particularly apparent when nesting high level components. Nested components require each component to be wrapped in its own ${} wrapper, which can cause code that looks like this (code credit to Facebook):

// Template Literals
var box = jsx`
<${Box}>
${
  shouldShowAnswer(user) ?
    jsx`<${Answer} value=${false}>no</${Answer}>`
    :
    jsx`
    <${Box.Comment}>
    Text Content
    </${Box.Comment}>
    `
}
</${Box}>
`;

React’s JSX cleans this up to look like this (again, credit to Facebook):

// JSX
<Box>
{
  shouldShowAnswer(user) ?
    <Answer value={false}>no</Answer> :
    <Box.Comment>
    Text Content
    </Box.Comment>
}
</Box>;

Admittedly, much better.

Still, the native nature of template literals makes them ripe for innovative iteration. The T7 library is one example that aims to do just that. T7 allows for template literals to be “assigned” to a JSX-like component before using them, allowing components to be called with JSX-like syntax later in the program. There is still some overhead involved in doing this, but no non-standard JavaScript has to be used.

Lit HTML and T7 are the first projects I’ve seen that’s trying to push template literals into the web development zeitgeist, but I believe it is merely the first domino to fall. As web development continues to standardize, so too will its tools. CSS Grid is sure to usurp Bootstrap, Foundation, etc. ES6 is both leaner and more robust than jQuery (full disclosure, I never was a jQuery developer, and am a bit of a fanboy for modern JavaScript). And someday, when the honeymoon period of front end frameworks ends, template literals will be there waiting for us.

While template literals make string concatenation and composition much more perfomant and manageable, we still haven’t discussed how to best inject these template literals into our DOM. To do so with as little overhead as possible, we can utilize HTML5’s template elements. Template elements will be discussed in detail in my next post.

Part 3: HTML5 Template Elements