Template literals y tagged templates en JavaScript

Los template literals, introducidos en ES6, resuelven los problemas históricos de JavaScript con strings multilínea e interpolación. Los tagged templates llevan esto mucho más lejos: permiten preprocesar el template con una función, lo que abre la puerta a SQL parametrizado, HTML seguro automático, sistemas de internacionalización y librerías como styled-components.

Template literals: interpolación y multilínea

Los template literals usan backticks (`) en lugar de comillas. Permiten incrustar expresiones con ${} y escribir strings que se extienden en varias líneas manteniendo los saltos:

const nombre = 'Ana';
const edad = 30;

// Interpolación (cualquier expresión válida dentro de ${})
console.log(`Hola, ${nombre}. Tienes ${edad} años.`);
console.log(`El doble de tu edad es ${edad * 2}`);
console.log(`Mayúsculas: ${nombre.toUpperCase()}`);

// Multilínea (los saltos de línea se preservan)
const html = `
  

${nombre}

Edad: ${edad}

`; // Expresiones complejas: ternario, métodos, funciones const precio = 29.99; const iva = 0.21; const mensaje = `Total: ${(precio * (1 + iva)).toFixed(2)} €`; console.log(mensaje); // "Total: 36.29 €" // Comparación con el operador +: const viejo = 'Hola, ' + nombre + '. Tienes ' + edad + ' años.'; const nuevo = `Hola, ${nombre}. Tienes ${edad} años.`;

Tagged templates: función que procesa el template

Un tagged template es una función que recibe el template dividido en sus partes. La función recibe: un array de las partes literales del string, y los valores interpolados como argumentos adicionales:

function etiqueta(partes, ...valores) {
  console.log(partes);   // Array de strings literales
  console.log(valores);  // Array de valores interpolados

  // Reconstruir el string (comportamiento por defecto)
  return partes.reduce((acc, parte, i) => {
    return acc + (valores[i - 1] ?? '') + parte;
  });
}

const x = 5, y = 10;
etiqueta`El resultado de ${x} más ${y} es ${x + y}`;
// partes:  ['El resultado de ', ' más ', ' es ', '']
// valores: [5, 10, 15]

HTML seguro: escapado automático con tagged templates

Uno de los casos de uso más importantes: escapar automáticamente todos los valores interpolados para prevenir XSS:

function html(partes, ...valores) {
  const escapar = (str) =>
    String(str)
      .replace(/&/g, '&')
      .replace(//g, '>')
      .replace(/"/g, '"')
      .replace(/'/g, ''');

  return partes.reduce((acc, parte, i) => {
    return acc + (i > 0 ? escapar(valores[i - 1]) : '') + parte;
  });
}

const nombreUsuario = '';
const saludo = html`

Hola, ${nombreUsuario}!`; console.log(saludo); //

Hola, <script>alert("XSS")</script>!

SQL parametrizado con tagged templates

function sql(partes, ...valores) {
  const query = partes.reduce((acc, parte, i) => {
    return acc + (i > 0 ? `$${i}` : '') + parte;
  });
  return { query, params: valores };
}

const id = 42;
const activo = true;
const { query, params } = sql`
  SELECT nombre, email
  FROM usuarios
  WHERE id = ${id} AND activo = ${activo}
`;

console.log(query);
// SELECT nombre, email FROM usuarios WHERE id = $1 AND activo = $2
console.log(params);  // [42, true]

// Uso con pg (PostgreSQL para Node.js):
// const resultado = await client.query(query, params);

styled-components y sistemas de i18n

Los tagged templates son la base de styled-components en React y de muchos sistemas de internacionalización:

// styled-components (simplificado):
function css(partes, ...valores) {
  return partes.reduce((acc, parte, i) => {
    const valor = i < valores.length
      ? (typeof valores[i] === 'function' ? valores[i]() : valores[i])
      : '';
    return acc + valor + parte;
  });
}

// En styled-components real:
// const Boton = styled.button`
//   background: ${props => props.primary ? 'blue' : 'white'};
//   color: ${props => props.primary ? 'white' : 'blue'};
// `;

// Sistema de i18n:
const traducciones = { 'Hola, {0}!': 'Hello, {0}!' };

function i18n(partes, ...valores) {
  const clave = partes.join('{0}');
  const traducida = traducciones[clave] || clave;
  return traducida.replace(/{(d+)}/g, (_, i) => valores[parseInt(i)]);
}

const usuario = 'Ana';
console.log(i18n`Hola, ${usuario}!`);  // 'Hello, Ana!'

La propiedad .raw del primer argumento contiene las partes del template sin procesar las secuencias de escape, útil para escribir etiquetas que trabajan con regex o rutas de archivo donde los backslashes no deben interpretarse.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP