Tagged Templates

Tagged Templates and Unicode Handling

A tag function before a template literal receives a frozen array of the text parts (raw text in .raw), created once per call site, followed by the evaluated values. Since ES2018 invalid escapes there yield undefined, not an error.

An HTML-escaping tag and three ways to measure a stringJavaScriptLive
const esc = (v) => String(v).replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
const html = (strings, ...values) => strings.reduce((out, s, i) => out + esc(values[i - 1]) + s);
const name = '<img src=x onerror=alert(1)>';
console.log(html`<p title="${name}">Hi ${name}</p>`);
const text = 'e\u0301\u{1F44D}\u{1F3FD}';      // e + combining acute; thumbs up + skin tone
const graphemes = [...new Intl.Segmenter('en', { granularity: 'grapheme' }).segment(text)];
console.log(text.length, [...text].length, graphemes.length);
console.log(text.charCodeAt(2).toString(16), text.codePointAt(2).toString(16));

length and indexes count UTF-16 code units; code points above U+FFFF take two. Spread, for...of and codePointAt() use code points. A user-perceived character is a grapheme cluster, counted by Intl.Segmenter (Other Intl Formatters).

One string measured as grapheme clusters, code points and UTF-16 code units
One string measured as grapheme clusters, code points and UTF-16 code units