Como conseguir o literal com strings de modelo ES6

As strings em JavaScript sempre foram limitadas, sem os recursos que se esperaria de linguagens como Python ou Ruby. As strings de modelo ES6 (disponíveis no Chrome 41 e versões mais recentes) mudam isso fundamentalmente. Elas introduzem uma maneira de definir strings com linguagens específicas de domínio (DSLs), trazendo melhorias em:

  • Interpolação de strings
  • Expressões incorporadas
  • Strings de várias linhas sem gambiarras
  • Formatação de strings
  • Inclusão de tag de string para escape de HTML seguro, localização e muito mais.

Em vez de inserir mais um recurso em strings como as conhecemos hoje, as strings de modelo apresentam uma maneira completamente diferente de resolver esses problemas.

Sintaxe

As strings de modelo usam crases (``) em vez de aspas simples ou duplas, como estamos acostumados com strings regulares. Uma string de modelo pode ser escrita da seguinte forma:

var greeting = `Yo World!`;

Até agora, as strings de modelo não nos deram nada além do que as strings normais fazem. Vamos mudar isso.

Substituição de string

Um dos primeiros benefícios reais é a substituição de strings. A substituição permite usar qualquer expressão JavaScript válida (incluindo, por exemplo, a adição de variáveis) e, dentro de um modelo literal, o resultado será gerado como parte da mesma string.

As strings de modelo podem conter marcadores de posição para substituição de strings usando a sintaxe ${ }, conforme demonstrado abaixo:

// Simple string substitution
var name = "Brendan";
console.log(`Yo, ${name}!`);

// => "Yo, Brendan!"

Como todas as substituições de string em strings de modelo são expressões JavaScript, podemos substituir muito mais do que nomes de variáveis. Por exemplo, abaixo podemos usar a interpolação de expressões para incorporar algumas operações matemáticas legíveis em linha:

var a = 10;
var b = 10;
console.log(`JavaScript first appeared ${a+b} years ago. Wow!`);

//=> JavaScript first appeared 20 years ago. Wow!

console.log(`The number of JS MVC frameworks is ${2 * (a + b)} and not ${10 * (a + b)}.`);
//=> The number of JS frameworks is 40 and not 200.

Elas também são muito úteis para funções dentro de expressões:

function fn() { return "I am a result. Rarr"; }
console.log(`foo ${fn()} bar`);
//=> foo I am a result. Rarr bar.

O ${} funciona bem com qualquer tipo de expressão, incluindo expressões de membros e chamadas de método:

var user = {name: 'Caitlin Potter'};
console.log(`Thanks for getting this into V8, ${user.name.toUpperCase()}.`);

// => "Thanks for getting this into V8, CAITLIN POTTER";

// And another example
var thing = 'template strings';
console.log(`Say hello to ${thing}.`);

// => Say hello to template strings

Se você precisar de crases na sua string, elas podem ser escapadas usando o caractere de barra invertida \ da seguinte maneira:

var greeting = `\`Yo\` World!`;

Strings de várias linhas

Há algum tempo, as strings de várias linhas em JavaScript exigem soluções alternativas complicadas. As soluções atuais exigem que as strings estejam em uma única linha ou sejam divididas em strings de várias linhas usando uma \ (barra invertida) antes de cada nova linha. Exemplo:

var greeting = "Yo \
World";

Embora isso funcione bem na maioria dos mecanismos JavaScript modernos, o comportamento em si ainda é um pouco complicado. Também é possível usar a concatenação de strings para simular o suporte a várias linhas, mas isso também deixa a desejar:

var greeting = "Yo " +
"World";

As strings de modelo simplificam muito as strings de várias linhas. Basta incluir novas linhas onde elas são necessárias e BOOM. Veja um exemplo:

Qualquer espaço em branco dentro da sintaxe de crase também será considerado parte da string.

console.log(`string text line 1
string text line 2`);

Modelos com tag

Até agora, vimos como usar strings de modelo para substituição de strings e para criar strings de várias linhas. Outro recurso avançado que eles oferecem são os modelos marcados. Os modelos de tag transformam uma string de modelo colocando um nome de função antes dela. Exemplo:

fn`Hello ${you}! You're looking ${adjective} today!`

A semântica de uma string de modelo com tag é muito diferente da de uma normal. Em essência, elas são um tipo especial de chamada de função: o "desaçúcar" acima em

fn(["Hello ", "! You're looking ", " today!"], you, adjective);

Observe como o argumento (n + 1) corresponde à substituição que ocorre entre a n-ésima e a (n + 1)-ésima entradas na matriz de strings. Isso pode ser útil para vários fins, mas um dos mais simples é o escape automático de variáveis interpoladas.

Por exemplo, você pode escrever uma função de escape de HTML para que...

html`<p title="${title}">Hello ${you}!</p>`

retorna uma string com as variáveis adequadas substituídas, mas com todos os caracteres não seguros para HTML substituídos. Vamos fazer isso. Nossa função de escape de HTML vai usar dois argumentos: um nome de usuário e um comentário. Ambos podem conter caracteres não seguros para HTML (ou seja, ', ", <, > e &). Por exemplo, se o nome de usuário for "Domenic Denicola" e o comentário for "& é uma tag divertida", o resultado será:

<b>Domenic Denicola says:</b> "&amp; is a fun tag"

Nossa solução de modelo com tag pode ser escrita da seguinte forma:

// HTML Escape helper utility
var util = (function () {
    // Thanks to Andrea Giammarchi
    var
    reEscape = /[&<>'"]/g,
    reUnescape = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g,
    oEscape = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;',
        '"': '&quot;'
    },
    oUnescape = {
        '&amp;': '&',
        '&#38;': '&',
        '&lt;': '<',
        '&#60;': '<',
        '&gt;': '>',
        '&#62;': '>',
        '&apos;': "'",
        '&#39;': "'",
        '&quot;': '"',
        '&#34;': '"'
    },
    fnEscape = function (m) {
        return oEscape[m];
    },
    fnUnescape = function (m) {
        return oUnescape[m];
    },
    replace = String.prototype.replace
    ;
    return (Object.freeze || Object)({
    escape: function escape(s) {
        return replace.call(s, reEscape, fnEscape);
    },
    unescape: function unescape(s) {
        return replace.call(s, reUnescape, fnUnescape);
    }
    });
}());

// Tagged template function
function html(pieces) {
    var result = pieces[0];
    var substitutions = [].slice.call(arguments, 1);
    for (var i = 0; i < substitutions.length; ++i) {
        result += util.escape(substitutions[i]) + pieces[i + 1];
    }

    return result;
}

var username = "Domenic Denicola";
var tag = "& is a fun tag";
console.log(html`<b>${username} says</b>: "${tag}"`);
//=> <b>Domenic Denicola says</b>: "&amp; is a fun tag"

Outros usos possíveis incluem escape automático, formatação, localização e, em geral, substituições mais complexas:

// Contextual auto-escaping
qsa`.${className}`;
safehtml`<a href="${url}?q=${query}" onclick="alert('${message}')" style="color: ${color}">${message}</a>`;

// Localization and formatting
l10n`Hello ${name}; you are visitor number ${visitor}:n! You have ${money}:c in your account!`

// Embedded HTML/XML
jsx`<a href="${url}">${text}</a>` // becomes React.DOM.a({ href: url }, text)

// DSLs for code execution
var childProcess = sh`ps ax | grep ${pid}`;

Resumo

As strings de modelo estão disponíveis no Chrome 41 Beta e versões mais recentes, no IE Tech Preview, no Firefox 35 e versões mais recentes e no io.js. Na prática, se você quiser usá-los em produção hoje, eles serão compatíveis com os principais transpiladores ES6, incluindo Traceur e 6to5. Confira nosso exemplo de strings de modelo no repositório de exemplos do Chrome se quiser testar. Você também pode se interessar por Equivalentes do ES6 no ES5, que demonstra como alcançar algumas das strings de modelo de açúcar usando o ES5 hoje.

As strings de modelo trazem muitos recursos importantes para o JavaScript. Isso inclui melhores maneiras de fazer interpolação de strings e expressões, strings de várias linhas e a capacidade de criar suas próprias DSLs.

Um dos recursos mais importantes que eles trazem são os modelos marcados, que são essenciais para a criação dessas DSLs. Eles recebem as partes de uma string de modelo como argumentos, e você pode decidir como usar as strings e substituições para determinar a saída final da string.

Leitura adicional