The article ES6 Strings and Template Literals introduced you to the syntax of ES6 strings and template literals. However, if you wanted to use ES6 template literals for microtemplating, the article itself does not help you. In this article, you will find out more about the relationship between ES6 template literals and JavaScript templating engines.
Storing templates
In large projects, templates are stored in their own template file and are imported using ES6 modules.
In smaller projects, we can get away with storing templates in the DOM usint scripts the browser does not understand, and therefore does not interpret. These templates are typically marked with type text/template
:
<html>
<head>...</head>
<body>...</body>
<script type="text/template" class="js-my-template">
<div class="js-name">${ name.first } ${ name.last }</div>
</script>
</html>
You can access this template using
document.querySelector( '.js-my-template' ).innerHTML
This was the old way of handling templates before RequireJs and later ES6 modules with Webpack came around. With ES6 modules, you just import the templates, and get access to their text in a string variable.
Either way, you get a JavaScript string that contains your template. This template has some inserted code snippets of form ${ value }
. The problem is that these snippets are inside a string and not inside a template literal. Therefore, unlike in Underscore Templating or Handlebars, these snippets will not be evaluated. We will address the problem of evaluating templates in this article.
Evaluating templates
Suppose a JavaScript string containing code snippets of form ${ value }
is given, where value
is an arbitrary JavaScript value. Our task is to evaluate these code snippets and return a string containing the evaluated template.
To get a feel for the task, check out my article on Underscore Templating. We did the following steps to render our application:
// 1. Get the template string
var templateString = document.getElementById( 'departures-template' ).innerHTML;
// 2. Prepare a partially evaluated template function
var tableTemplate = _.template( templateString );
// 3. Prepare the data for presentation
var data = {
departures: departures,
headers: {
id : 'Id',
destination : 'Destination',
departureTime : 'DepartureTime',
expectedDepartureTime : 'Expected Departure Time',
gate : 'Gate',
status : 'Status'
}
};
// 4. Evaluate the template function with the prepared data
var departuresTable = tableTemplate( data );
// 5. Display the result
document.getElementById( 'result' ).innerHTML = departuresTable;
We care about the process of assembling the template. Therefore, let’s examine steps 2 and 4 a bit more closely:
var tableTemplate = _.template( templateString );
var departuresTable = tableTemplate( data );
If we combine the two lines, we get the following chain of function applications:
var departuresTable = _.template( templateString )( data );
So, in order to get the fully evaluated string that we can insert into the DOM, we need the following: use a templating engine, in this case _.template
, to transform templateString
into a function. This function accepts template data, and returns the evaluated templateString
with data
values inserted into it.
In this example, the templating engine _.template
was given. However, chances are, you have considered not including Underscore, LoDash, or any other templating engines in your code base just for the sake of performing a simple task. What options do we have to implement templating using template literals?
Possible solutions
After reading my article on ES6 Strings and Template Literals, the following three solutions seem logical:
- define the templates as JavaScript functions,
- write your own helper,
- use a tagged template function as the templating engine.
Before continuing reading, I encourage you to think the three approaches through. Which approaches are feasible in your opinion? What problems would you encounter with each approach?
Let’s see them one by one.
Defining templates as JavaScript functions
All you need is to create a file for each of your templates. In this example, we will use name.template.js
:
export default function ( { firstName, lastName } ) => `
<div class="js-name">
<span class="js-first-name">${ firstName }</span>
<span class="js-last-name">${ lastName }</span>
</div>
`;
If you want to render the template, you need to import the templating function:
import renderNameTemplate from 'templates/name.template.js';
// ...
document.querySelector( nameContainerSelector ).innerHTML =
renderNameTemplate( {
firstName: 'Zsolt',
lastName: 'Nagy'
} );
// ...
The templating engine is provided by a simple function wrapping the template literal. After all, we can make a partially evaluated template from a template literal by using the format
data => `template literal containing ${ data.field1 }, ${ data.field2 }, ...`
This solution is so simple that many people may consider using it. However, before you start ditching your templating engine in favor of simple functions, stay with this article, because near the end, you will find a twist. This twist will make this solution a lot less attractive because of the underlying dangers of not being careful enough.
But first, let’s move on to the next suggestion.
Write your own helper?
Suppose the string nameTemplate
is given, and its value is retrieved from the HTML code:
<html>
<head>...</head>
<body>...</body>
<script type="text/template" class="js-my-template">
<div class="js-name">
<span class="js-first-name">${ firstName }</span>
<span class="js-last-name">${ lastName }</span>
</div>
</script>
</html>
let nameTemplate = document.querySelector( '.js-my-template' ).innerHTML;
Suppose our data is
let data = {
firstName: 'Zsolt',
lastName: 'Nagy'
};
We want to render the template with a helper.
const templatingEngine =
template =>
data =>
evaluateTemplate( template, data );
If we can write an evaluateTemplate
function that evaluates template
by inserting data
into it, we will solve the problem:
templatingEngine( template )( data )
would then return the same result as
_.template( template )( data )
We could use eval
or the Function
constructor:
const evaluateTemplate = ( template, data ) => {
with ( data ) {
return eval( '`' + template + '`' );
}
}
If I sold you this as the ultimate templating solution, you may consider not taking me seriously. I mean, with
, eval
? None of these language constructs have much to do with writing maintainable software.
Mind you, eval
is not secure in JavaScript, and its performance is not that great either. However, without dynamic evaluation, we can only write our own helper with some restrictions.
Let’s make the following assumption:
- In
${ value }
, value
has to be an identifier for which data[ value ]
exists
- You can add any number of whitespaces before or after
value
.
We can then write a regular expression that captures value
inside ${
and }
. I will break it down for you in extended syntax:
/(?x)
\$\{ # Look for a $ followed by an opening {
\s* # Consume (trim) all whitespaces
( # Open capture group 1
[^\s\}]+ # Match any non-whitespace and
# non-closing brace character
) # Close capture group 1
\s* # Trim all whitespaces after the end of the identifier
\} # Look for a closing brace
/g
The regular expression is global, because we will want to replace all occurrences of ${ value }
patterns, and extract each and every value in capture groups.
As a side note, this syntax is not available in JavaScript by default, because a whitespace character matches itself. You can use a library like XRegExp to enrich the regular expression dialect implemented in JavaScript. If you sign up below, you will be notified about updates on how to master JavaScript regular expressions. You can even download a free regex cheat sheet that you can print for yourself:
Let’s get back to work. In JavaScript, we have to write our regular expression without whitespaces and comments. Let’s write our evaluateTemplate
function:
const evaluateTemplate = ( template, data ) =>
template.replace(
/\$\{\s*([^\s\}]+)\s*\}/g,
( _, capturedIdentifier ) =>
data[ capturedIdentifier ]
);
Notice that the replace
function of the String
API accepts a function in the second argument returning the string you want to replace the original value with. The first argument of this function contains the original string. We don’t need this value, so I indicated it with the throw-away value _
. The second argument is the value that was captured in-between the parentheses of the regular expression. This string is called the value of capture group 1. In the function passed to replace, the value of capture group i
is accessible via arguments[i]
.
Let’s summarize the solution:
let template = '\
<div class="js-name">\
<span class="js-first-name">${ firstName }</span>\
<span class="js-last-name">${ lastName }</span>\
</div>';
let data = {
firstName: 'Zsolt',
lastName: 'Nagy'
};
const templatingEngine =
template =>
data =>
evaluateTemplate( template, data );
const evaluateTemplate = ( template, data ) =>
template.replace(
/\$\{\s*([^\s\}]+)\s*\}/g,
( _, capturedIdentifier ) =>
data[ capturedIdentifier ]
);
console.log(
templatingEngine( template )( data )
);
The result is correct:
"<div class="js-name">
<span class="js-first-name">Zsolt</span>
<span class="js-last-name">Nagy</span>
</div>"
Would you use my code in production? Because I would not.
Why? Let me ask you a few questions: is this code harmless against injecting some suspicious values that could
- get your cookies;
- submit an AJAX request and even fire some unwanted SQL statements.
This code is not prepared for all cases. We would have to continue adding more logic into our templating engine. What happens once we add more logic? We get a templating engine like HandleBars, Moustache, Underscore or LoDash templating, Jade etc.
Conclusion: don’t write your own templating engine, use one instead that’s supported by the open source community.
What about tagged templates?
I know, we haven’t talked about tagged templates. But do you know what the main problem with tagged templates is? Their format is
tag`ES6 template literal`
and not
tag"JS string"
Unfortunately, we have a regular JavaScript string. This means, we are back to square one. In order to use tagged templates for template evaluation, we have one of the following two choices:
- convert the string into a template literal using e.g.
eval
,
- restrict our templating activities so that we can use tagged templates.
As the first option is a no-go for us, we will do the latter.
let data = { a: 'inserted' };
let template = '${ a } text';
let tag = ( substringArray, ...dataArray ) => {
console.log( substringArray, dataArray );
return dataArray[0];
}
tag`${ template }`
Unsurprisingly, the values are:
substringArray
: ["", "", raw: ["", ""] ]
dataArray
: ["${ a } text"]
This just does not add up. Why would we use tagged templates to transform a string into a template literal if we don’t use any of the capabilities of template literals?
You see, there are so many questions circulating around template rendering and template literals. Just because ES6 template literals have the word template in them, people think, they replace a JavaScript templating engine. No, they don’t. It’s like asking a mongoose to behave like a monsoon. They both start with mon, but their purpose is different.
Tagged templates still have a use case
Let’s recall our first solution:
let renderName = ( { firstName, lastName } ) => `
<div class="js-name">
<span class="js-first-name">${ firstName }</span>
<span class="js-last-name">${ lastName }</span>
</div>
`;
renderName( {
firstName: 'Zsolt',
lastName: 'Nagy'
} );
This code snippet seems to serve its purpose, even the newline characters are there:
"
<div class="js-name">
<span class="js-first-name">Zsolt</span>
<span class="js-last-name">Nagy</span>
</div>
"
What can go wrong in this example? Let’s see…
const attackerTemplate =
renderName( {
firstName: 'Zsolt',
lastName: `
<span onmouseover="javascript:alert('Got your cookie!')">
Nagy
</span>`
} );
We can insert some evil code into our HTML:
"
<div class="js-name">
<span class="js-first-name">Zsolt</span>
<span class="js-last-name">
<span onmouseover="javascript:alert('Got your cookie!')">
Nagy
</span></span>
</div>
"
Try it out:
document.body.innerHTML += attackerTemplate;
By inserting the above code to the end of the document body, you are supposed to see an alert once you scroll to the end of the document and move your mouse cursor over the text Nagy
to see the alert appear.
As the cops in Need for Speed would say, you got away with a warning this time. However, some attacks will not be this gentle.
This is why we have to sanitize the input. For instance, not allowing any tag names is a great start. We will simply replace the greater than and the less than symbols with their entity names. This is where tagged template functions come handy:
const sanitize = ( fragmentList, ...substitutionList ) => {
let result = '';
for ( let i = 0; i < fragmentList.length; ++i ) {
result += fragmentList[i];
if ( substitutionList[i] ) {
result += substitutionList[i]
.replace( />/g, '>' )
.replace( /</g, '<' );
}
}
return result;
}
let renderName = ( { firstName, lastName } ) => sanitize`
<div class="js-name">
<span class="js-first-name">${ firstName }</span>
<span class="js-last-name">${ lastName }</span>
</div>
`;
Once again, replace
is a string prototype function. Its first argument is a regular expression. If we add the global modifier to the regular expression, we replace all matches with the second argument. This time the second argument is a simple value.
Let’s see the result in action:
renderName( {
firstName: 'Zsolt',
lastName: `
<span onmouseover="javascript:alert('Got your cookie!')">
Nagy
</span>`
} );
The result is:
"
<div class="js-name">
<span class="js-first-name">Zsolt</span>
<span class="js-last-name">
<span onmouseover="javascript:alert('Got your cookie!')">
Nagy
</span></span>
</div>
"
Better luck next time, evil attacker! By not inserting any HTML, you failed to insert JavaScript too.
As you can see, tagged templates came handy. However, let me ask you the question: why bother writing it at all? There are great templating engines available. They have done a lot more than just input sanitization for replacing the greater than and less than signs.
If you want to write your own templating engine, sure, go ahead, you could be the thousandth person open sourcing one online. Otherwise, I would just default to a simple templating engine.
Conclusion
How can you get fired? Easy.
First, listen to my disclaimer: I do not bear any responsibility for the damages caused. This is an ironic, hypothetical scenario.
Second, ask for refactoring time during your next sprint. Tell your managers that ES6 template literals are the new shit on the block, and you want to replace your current templating engine with it. If your manager is dumb enough, or (s)he trusts you enough, you will get the go ahead. I am sure you will forget something. If not, you just wasted your valuable time with reinventing the wheel.
Third, wait until someone discovers the errors and finds out that ES6 template literals are not to be used as template rendering engines.
This is the recipe. For freelancers, you may have to limit your liability in your contract. Or, if you prefer meeting your needs in a more meaningful way, you could just consider omitting the idea of using ES6 template literals instead of template rendering.
This is because ES6 template literals have not much to do with templating engines. You can experiment with creating some templates with them, but once you start testing the limits of your application, you will soon conclude that you need the capabilities of a real templating engine to handle your templates.
In a small hobby-application, everything files. Once you start playing with hundreds of thousands of dollars of your employer’s money, start thinking about whether you want to write your templating engine yourself, or you prefer using a well tested one.
If you liked this article, and you are interested in extending your ES6 knowledge, sign up in the form below:
Learn ES6 in Practice
Sign up below to access an ES6 course with many exercises and reference solutions.