Joel Hassan

JavaScript Tagged Template Literals

2019-07-15

intro

I've been getting into GraphQL recently, and I came across this type of syntax:

  gql`...`

I recognised the template literal, but didn't understand how it could be tagged onto label like that. I did some digging, and found out these are known as a tagged template literals, a feature introduced with ES5. Here follows a brief post about them and template literals in general.


template literals

Before looking at tagged templates, let's briefly revisit template literals:

Put in the simplest way, template literals are just special strings. They differ from regular strings in that they:

  1. Use different syntax - backticks (`) instead of double/single quotes
  2. Enable multiline strings to be created (easily and without bug exploitation)
  3. Enable JavaScript expressions to be embedded in them - called 'substitutions'

These template literals have been extremely useful as JavaScript developers can use them to create more complex strings. Ways of creating multiline strings, for instance, pre-ES6 were somewhat clumsy.

example

Here's a simple template literal example:

const one = 1;
const two = 2;
const result = `One add two is ${one + two}`; // not possible with regular strings! 
console.log(result); // output: One add two is 3

The ${ } is the required syntax for adding in any expressions.

Another thing to keep in mind is the scope. The template literal has access to all, and only, the variables in the scope that it was defined in.

In a nutshell, template literals allow developers to neatly compose long strings and embed all kinds of expression within them, including variables and functions. Here's an example of a function call placed within a template literal:

const hello = () => 'hello world!'

const sayHelloFromString = `I say ${hello()}` 
console.log(sayHelloFromString); // I say hello world!

We're using a function call here - the brackets after hello are necessary for the function to actually run!

The ability to place variables directly into template literals means that developers have a safer and easier-to-use tool than string concatenation for composing long strings with variables.

They are extremely useful and can be found sprinkled all over modern JavaScript codebases.


tagged template literals

We've seen how template literals provide a more flexible way of working with strings. Tagged templates take template literals a notch further, making them even more powerful.

Tagged template literals are simply regular template literals that have been tagged with a function, known as the tag. The tag manipulates the template string passed to it as an argument in some defined way.

As a result, different variations of the template literal can returned from the tag based on the expressions that are part of the template literal and the actions defined in the tag.

So, a tag function transforms the string in some way and then returns it. The returned string from the function will then be the final form that the template literal takes.

examples

Let's have an example shed some light on this:

// the tag - transforms template literal
function transformFunc() {
    // do something
}

const ourString = transformFunc`hello`; 
console.log(ourString); // undefined

The idea is that the template tag (the function) takes the template literal (here hello) and returns a string, which will be the final value of ourString.

So, the tag controls the final string value. If we had the following:

function transformFunc() {
    return 'not the original string'
}

const ourString = transformFunc`hello`; 
console.log(ourString); // not the original string

Here, the tag has transformed hello into its own return value.

To actually return something using the template literal, we need to look at how a tag works.

tag parameters

The tag function takes in data about the template literal that's passed into it in the following form:

function tag(literals, ...substitutions) {
	// do something
}

We can see that we have two parameters:

  1. literals - an array of the individual literals
  2. ...substitutions - an array of expressions we've embedded into our template string

The literals refers to individual pieces of the template literal. The substitutions are the evaluations of any expressions we put in our template literal, such as variables or even calculations.

This will make more sense with the examples to come.

And as for the ... in the ...substitutions parameter — that's rest parameters syntax. It allows us to use as many arguments as we want for the substitutions array, enabling us to flexibly add or remove expressions depending on our needs.

Perhaps the trickiest part is figuring out what exactly goes into each one of the parameter arrays.

Essentially, the template string gets spliced at each point where an expression is used. For example, say we had:

const firstValue = 1;
const secondValue = 2;
tag`${firstValue} + ${firstValue} is  ${firstValue + secondValue}`

The arguments to the tag would, in this case, be:

function tag(literals, ...substitutions){
    console.log(literals) // ['', ' + is ', '']
    console.log(substitutions); // [1, 1, 3] 
}

how the tag arguments are formed

What comes before any expression gets put in the strings array, and the expressions (their evaluations) go in the substitutions array.

Let's keep it simple and go back to the previous example. We'll use the tag function to return the original string:

function transformFunc(strings, ...substitutions) {
	return strings[0]; // we only have one string
}
const ourString = transformFunc`hello`;
console.log(ourString); // hello

We had to extract the literal from the strings arrays - here, to keep things simple, there was only a single element - the hello string.

However, what if we had something that looked like this:

const world = 'world';
const myPassion = 'JavaScript';
const ourString = transformFunc`hello ${world}, I love ${myPassion}`;
console.log(ourString);

Here, the ourString takes 2 literals and 2 substitutions as arguments. The literals:

  1. 'hello '
  2. ', I love '

And the substitutions (expressions):

  1. world
  2. myPassion

Now we have to do something a bit more involved in the tag to return this template in the same form as its passed in. The function could look as follows:

function transformFunc(strings, ...substitutions) {
	// joining the strings with the help of .reduce() method
  let returnString = strings.reduce((stringsJoined, currentString, i) => {
    return stringsJoined + currentString + (substitutions[i] || '');
  }, '');
  return returnString;
}

The point obviously isn't to return the template literal unaltered, this is just to concentrate on what the tagged templates do and the syntax.

Keep in mind that the values contained in substitutions don't have to be strings. They could just as well have been numbers, for instance.

real-world usage

Tagged template literals have their uses. For instance, they're used in the context of CSS in JavaScript, with a notable example being the styled-components library that has used tagged template literals to scope styling to a single component. You can read more about it here.


concluding thoughts

To sum up:

  • tagged templates make template strings more powerful
  • a tag is just a function that transforms the template literal we passed to it
  • the tag takes in two arrays - template string parts in one, expressions in the other
  • The transformed string will be the final value of the string

Tagged template literals tripped me up, so I wanted to write about them. Hopefully, whenever you happen to come across them you won't be as befuddled as I was 😊