As tagged template literals (ou literais de modelo com etiqueta) são uma funcionalidade avançada do JavaScript que permite que você processe template literals de uma maneira personalizada, através de uma função (a "tag"). Em vez de simplesmente interpolar valores em uma string, você pode passar tanto as partes literais da string quanto os valores interpolados para uma função, que pode transformá-los, validá-los ou até mesmo retornar algo completamente diferente. Isso abre um leque de possibilidades para criar DSLs (linguagens específicas de domínio), sanitizar entradas de usuário, implementar sistemas de internacionalização e muito mais.

Antes de mergulharmos nos detalhes, é importante ter uma boa compreensão de template literals básicos, que são strings delimitadas por crases (``) e que permitem interpolação de expressões usando ${expressao}. As tagged template literals estendem esse conceito, adicionando uma função antes da string. Por exemplo: tag`Olá, ${nome}!`. A função tag recebe os pedaços da string e os valores interpolados, podendo processá-los de qualquer forma.

Como funcionam

Quando você usa uma tagged template literal, a função é chamada com dois tipos de argumentos: o primeiro é um array de strings (as partes literais da string, separadas pelos pontos de interpolação) e os argumentos seguintes são os valores interpolados (as expressões dentro de ${...}). A ordem é sempre: primeiro o array de strings, depois cada valor na ordem em que aparece.

Vamos a um exemplo simples:

function minhaTag(strings, ...valores) {
  console.log(strings); // Array de strings
  console.log(valores); // Array de valores
  return "Resultado processado";
}

const nome = "Maria";
const idade = 30;
const resultado = minhaTag`Nome: ${nome}, Idade: ${idade}`;
console.log(resultado); // "Resultado processado"

No exemplo acima, strings será ['Nome: ', ', Idade: ', ''] e valores será ['Maria', 30]. Note que o array de strings sempre tem um elemento a mais que o número de valores, porque as strings são separadas pelos valores. Isso é fundamental para reconstruir a string original, se necessário.

Outra característica importante é que as strings têm uma propriedade raw no array de strings, que contém as versões "cruas" das strings, sem processamento de escapes (por exemplo, \n permanece como dois caracteres, não como quebra de linha). Isso é útil para processar código ou texto onde os escapes devem ser preservados.

function tag(strings, ...valores) {
  console.log(strings.raw[0]); // Exibe a string crua
  return strings.raw[0] + valores.join(" ");
}

const texto = tag`Linha 1\nLinha 2`;
console.log(texto); // Linha 1\nLinha 2 (com \n literal)

Essa capacidade de acessar as strings cruas é essencial para ferramentas que precisam interpretar o conteúdo literal, como compiladores ou processadores de linguagem.

Casos de uso (sanitização, i18n)

As tagged template literals são extremamente versáteis. Dois casos de uso muito comuns são a sanitização de strings (para evitar injeção de código ou HTML) e a internacionalização (i18n). Vamos explorar cada um.

Sanitização

Quando você insere valores de usuário em uma string que será usada em HTML, SQL ou outros contextos, é crucial escapar caracteres perigosos para evitar ataques como XSS (Cross-Site Scripting) ou injeção de SQL. Com tagged templates, você pode criar uma função que automaticamente escapa os valores interpolados, garantindo que sejam seguros.

function escaparHTML(strings, ...valores) {
  const escapar = (valor) => {
    return String(valor)
      .replace(/&/g, "&")
      .replace(//g, ">")
      .replace(/"/g, """)
      .replace(/'/g, "'");
  };
  return strings.reduce((resultado, string, i) => {
    return resultado + string + (i < valores.length ? escapar(valores[i]) : "");
  }, "");
}

const usuario = "";
const saudacao = escaparHTML`Olá, ${usuario}!`;
console.log(saudacao); // Olá, <script>alert('XSS')</script>!

Nesse exemplo, a função escaparHTML substitui caracteres especiais por entidades HTML, neutralizando qualquer código malicioso. Isso é muito mais limpo e seguro do que tentar lembrar de escapar cada interpolação manualmente.

Internacionalização (i18n)

Em aplicações multilíngues, você pode usar tagged templates para escolher a tradução correta com base em um idioma, além de formatar números e datas de acordo com o local. A tag pode receber um objeto de traduções ou o idioma atual e retornar a string traduzida.

const traducoes = {
  pt: {
    saudacao: (nome) => `Olá, ${nome}!`,
    despedida: (nome) => `Até logo, ${nome}!`,
  },
  en: {
    saudacao: (nome) => `Hello, ${nome}!`,
    despedida: (nome) => `Goodbye, ${nome}!`,
  },
};

function i18n(strings, ...valores) {
  const idioma = i18n.idiomaAtual || "pt";
  const chave = strings.join("").trim(); // Ex.: "saudacao"
  const funcao = traducoes[idioma][chave];
  if (typeof funcao === "function") {
    return funcao(...valores);
  }
  return strings.join(" "); // fallback
}

i18n.idiomaAtual = "pt";
console.log(i18n`saudacao ${ "João" }`); // Olá, João!

i18n.idiomaAtual = "en";
console.log(i18n`despedida ${ "John" }`); // Goodbye, John!

Nesse exemplo, a tag i18n usa o primeiro segmento da string como chave de tradução e os valores como argumentos para a função de tradução. Isso permite escrever templates que são automaticamente traduzidos, mantendo o código limpo e legível.

Exemplos

Vamos ver mais alguns exemplos práticos de tagged template literals, incluindo formatação de moeda, criação de HTML dinâmico e construção de consultas SQL seguras.

Formatação de moeda

Você pode criar uma tag que formata números como moeda usando o objeto Intl.NumberFormat.

function moeda(strings, ...valores) {
  const formatador = new Intl.NumberFormat("pt-BR", {
    style: "currency",
    currency: "BRL",
  });
  let resultado = "";
  strings.forEach((string, i) => {
    resultado += string;
    if (i < valores.length) {
      const valor = Number(valores[i]);
      resultado += formatador.format(valor);
    }
  });
  return resultado;
}

const preco = 1234.5;
console.log(moeda`O preço é ${preco}`); // O preço é R$ 1.234,50

Criação de HTML dinâmico

Você pode construir templates HTML com segurança, escapando valores e criando elementos dinamicamente.

function html(strings, ...valores) {
  const escapar = (valor) => {
    return String(valor)
      .replace(/&/g, "&")
      .replace(//g, ">");
  };
  let resultado = "";
  strings.forEach((string, i) => {
    resultado += string;
    if (i < valores.length) {
      resultado += escapar(valores[i]);
    }
  });
  return resultado;
}

const usuario = { nome: "Ana", email: "ana@example.com" };
const card = html`
  

${usuario.nome}

${usuario.email}

`; console.log(card);

Consultas SQL seguras

Em Node.js, você pode usar tagged templates para criar consultas SQL parametrizadas, evitando injeção de SQL.

const sql = (strings, ...valores) => {
  // Simulação: retorna a consulta com placeholders
  return strings.reduce((acc, string, i) => {
    return acc + string + (i < valores.length ? "?" : "");
  }, "");
};

const id = 1;
const nome = "João";
const query = sql`SELECT * FROM usuarios WHERE id = ${id} AND nome = ${nome}`;
console.log(query); // SELECT * FROM usuarios WHERE id = ? AND nome = ?

Nesse exemplo, a tag sql substitui os valores por placeholders ?, que depois podem ser passados a um driver de banco de dados que os preenche de forma segura.

Boas práticas e observações finais

Ao usar tagged template literals, é importante considerar alguns pontos:

  • Nomes descritivos: dê nomes claros às suas tags para indicar o que elas fazem (ex.: html, sql, i18n).
  • Performance: em loops ou chamadas frequentes, evite criar funções desnecessárias dentro da tag, pois isso pode impactar o desempenho. Prefira funções definidas fora.
  • Teste: como as tags podem transformar strings de formas inesperadas, escreva testes para garantir que o comportamento está correto, especialmente quando usadas em segurança.
  • Compatibilidade: tagged template literals são suportados em todos os ambientes modernos (Node.js 4+, navegadores atuais). Para ambientes mais antigos, use transpiladores como Babel.

Dominar tagged template literals é um passo importante para escrever JavaScript mais expressivo e robusto. Eles permitem criar APIs elegantes e seguras, reduzindo a probabilidade de erros e ataques. Experimente usá-los em seus projetos e veja como podem simplificar tarefas complexas.

Referências

Exercícios

  1. Exercício 1: Escreva uma tagged template literal chamada maiuscula que transforme todos os valores interpolados em letras maiúsculas, mas mantenha as strings literais inalteradas. Teste com maiuscula`Olá, ${nome}!` onde nome = "mundo".
  2. ✓ Resposta:
    function maiuscula(strings, ...valores) {
      let resultado = "";
      strings.forEach((string, i) => {
        resultado += string;
        if (i < valores.length) {
          resultado += String(valores[i]).toUpperCase();
        }
      });
      return resultado;
    }
    
    const nome = "mundo";
    console.log(maiuscula`Olá, ${nome}!`); // Olá, MUNDO!
    
  3. Exercício 2: Crie uma tag destacar que envolva cada valor interpolado em <strong> e </strong> para uso em HTML, mas escape o valor para evitar XSS. Utilize a função de escape do exemplo de sanitização.
  4. ✓ Resposta:
    function destacar(strings, ...valores) {
      const escapar = (valor) => {
        return String(valor)
          .replace(/&/g, "&")
          .replace(//g, ">");
      };
      let resultado = "";
      strings.forEach((string, i) => {
        resultado += string;
        if (i < valores.length) {
          resultado += `${escapar(valores[i])}`;
        }
      });
      return resultado;
    }
    
    const texto = "