Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR for a junior developer who was building a custom dashboard. He was using standard template literals to inject user-generated content directly into the innerHTML of a page. To prevent XSS attacks, he was manually calling an escapeHTML() function on every single variable. It looked like this: <div>Hello, ${escapeHTML(userName)}</div>. It was tedious, and more importantly, it was fragile. He missed one single variable in a deeply nested component, and during QA, we found that a user could literally rename themselves to a <script> tag and execute code in another user's browser. It was a classic "human error" bug.

This is exactly why Tagged Templates exist. Instead of remembering to call a sanitizer function every time you interpolate a value, you can create a "tag" function that intercepts the template literal and handles the sanitization for you automatically. It moves the responsibility from the developer's memory to the language's syntax.

Parsing the Pieces

Normally, when you use a template literal, JavaScript evaluates the expressions and returns a finished string. But when you prefix that template with a function name—like myTag`hello ${name}`—JavaScript doesn't just concatenate the string. Instead, it calls your function and passes it the template in a very specific way.

The first argument is an array of the static string parts. The subsequent arguments are the evaluated results of the expressions. I like to think of it as the engine "chopping" your string wherever a ${} occurs. If you have two variables, you'll have three static string segments (the part before the first variable, the part between the two, and the part after the second).

function debugTag(strings, ...values) {
  console.log('Static strings:', strings);
  console.log('Interpolated values:', values);
}

const user = 'Alice';
const action = 'uploaded a file';
debugTag`User ${user} has ${action} to the server.`;

// Output:
// Static strings: ["User ", " has ", " to the server."]
// Interpolated values: ["Alice", "uploaded a file"]

The ...values syntax is crucial here because you never know how many expressions a user will put in their template. By using the rest parameter, you capture all of them in an array.

Applying it to Real-World Security

Now, let's look at how we can solve that XSS problem I mentioned. We can write a tag function that iterates through those strings and values, but runs the values through a sanitizer before stitching them back together. I've found this is the cleanest way to handle dynamic HTML generation without relying on a massive framework for every small project.

function safeHtml(strings, ...values) {
  return strings.reduce((result, string, i) => {
    let value = values[i] || '';
    
    // Simple escaping logic: convert special chars to HTML entities
    const escapedValue = String(value)
      .replace(/&/g, '&')
      .replace(//g, '>')
      .replace(/"/g, '"');

    return result + string + escapedValue;
  }, '');
}

const untrustedInput = '<img src=x onerror=alert("Hacked!")>';
const output = safeHtml`<div class="user-profile">Welcome, ${untrustedInput}</div>`;

console.log(output); 
// Result: <div class="user-profile">Welcome, &lt;img src=x onerror=alert("Hacked!")&gt;</div>

Notice how we used reduce here. We start with an empty string and progressively add a static piece followed by its corresponding escaped value. Since there is always one more string piece than there are values, we handle the final string segment on the last iteration where values[i] is undefined. It's an elegant pattern that makes your code significantly more robust.




📋 Practical Task

Build a Localized Translation Tag

In many professional applications, you can't hardcode strings because the app needs to support multiple languages. Your task is to create a tagged template called i18n (short for internationalization) that looks up values in a translation dictionary.

Requirements:

  • Create a translations object that contains keys for 'greeting' and 'farewell' in both English ('en') and Spanish ('es').
  • Implement the i18n tag function. If a value passed into the template is a key found in the translation dictionary for the current active language, replace it with the translated string. If the key isn't found, leave the value as is.
  • The function should use a global variable currentLang to determine which language to use.

Example Usage:

let currentLang = 'es';
const userName = 'Carlos';

// Should output: "Hola, Carlos! Adiós!"
console.log(i18n` ${'greeting'}, ${userName}! ${'farewell'}!`);
Rating
0 0

There are no comments for now.

to be the first to leave a comment.