Converter uma sequência de caracteres em uma sequência de modelo

147

É possível criar uma sequência de modelos como uma sequência usual

let a="b:${b}";

e depois converta-o em uma string de modelo

let b=10;
console.log(a.template());//b:10

sem eval, new Functione outros meios de geração de código dinâmico?

KOLANICH
fonte
5
você encontrou uma maneira de conseguir isso? Talvez eu precise fazer isso um dia e estou curioso para saber o que você chegou.
Bryan Rayner
O @BryanRayner permite dizer que seu programa js está tentando buscar dados da API Rest, cuja URL está em um arquivo config.js como uma string "/ resources / <resource_id> / update /" e você coloca "resource_id" dinamicamente no seu programa . A menos que você queira dividir esse URL em partes e salvar em áreas diferentes, você precisa de algum tipo de processamento de modelo de string.
Ryu_hayabusa
Em vez de usar eval melhor, use-o para regex Eval não é recomendado e altamente desencorajado, portanto, não o use developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…! seja b = 10; deixe a = "b: $ {b}"; deixe resposta = a.replace (/ \ $ {\ w +} /, b); conssole.log (resposta);
Vijay Palaskar

Respostas:

79

Como a sequência do modelo deve obter referência à bvariável dinamicamente (em tempo de execução), a resposta é: NÃO, é impossível ficar sem a geração dinâmica de código.

Mas com evalisso é bem simples:

let tpl = eval('`'+a+'`');
alexpods
fonte
7
Eval é inseguro, de modo que é de outros meios de geração de código dinâmico
KOLANICH
8
@KOLANICH Para especial nesse caso - citações escapar de volta na acorda e será muito menos inseguros: let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');. Eu acho que o mais importante é que evalevite que o compilador otimize seu código. Mas acho irrelevante para esta pergunta.
Alexpods
3
De fato, você também pode executar funções dentro de strings de modelo.
KOLANICH 22/03
9
@KOLANICH Desculpe, você não gosta eval. No entanto, lembre-se de que um literal de modelo é uma forma de eval. Dois exemplos: var test = Result: ${alert('hello')}; teste var = Result: ${b=4}; Ambos acabarão executando código arbitrário no contexto do script. Se você deseja permitir seqüências arbitrárias, também pode permitir eval.
Manngo 14/08/16
6
Seja cuidadoso. Desde algo como babel não transpile isso, este código não funcionará no IE
cgsd
79

No meu projeto, criei algo assim com o ES6:

String.prototype.interpolate = function(params) {
  const names = Object.keys(params);
  const vals = Object.values(params);
  return new Function(...names, `return \`${this}\`;`)(...vals);
}

const template = 'Example text: ${text}';
const result = template.interpolate({
  text: 'Foo Boo'
});
console.log(result);

ATUALIZAÇÃO Removi a dependência do lodash, o ES6 possui métodos equivalentes para obter chaves e valores.

Mateusz Moska
fonte
1
Oi, Sua solução funciona muito bem, mas quando a usei no React Native (modo de compilação), gera um erro: Caractere inválido '' ' , embora funcione quando executo no modo de depuração. Parece, problema com babel, alguma ajuda?
Mohit Pandey
@MohitPandey Eu estava recebendo o mesmo erro quando estava executando testes desse código no PhantomJS e estava passando no chrome. Se for esse o caso, acho que há uma nova versão beta do PhantomJS a caminho, com melhor suporte para o ES6, você pode tentar instalá-lo.
Mateusz Moska
1
Infelizmente, ele não funciona e eu escrevi um regex para o mesmo. Adicionado como resposta também.
Mohit Pandey
esta solução só funciona se o caractere "` "de retorno não estiver presente na cadeia de modelo
SliverNinja - MSFT
Quando eu tento, eu tenho ReferenceError: _ is not defined. O código não é ES6, mas lodashespecífico ou ...?
xpt
29

O que você está pedindo aqui:

//non working code quoted from the question
let b=10;
console.log(a.template());//b:10

é exatamente equivalente (em termos de poder e, er, segurança) à eval: capacidade de pegar uma string que contém código e executar esse código; e também a capacidade do código executado ver variáveis ​​locais no ambiente do chamador.

Na JS, não há como uma função ver variáveis ​​locais em seu chamador, a menos que essa função seja eval(). Mesmo Function()não pode fazer isso.


Quando você ouve que algo chamado "strings de modelo" está chegando ao JavaScript, é natural assumir que é uma biblioteca de modelos embutida, como o Bigode. Não é. É principalmente apenas interpolação de cadeias e cadeias de linhas múltiplas para JS. Eu acho que isso vai ser um equívoco comum por um tempo, no entanto. :(

Jason Orendorff
fonte
2
TBH, foi o que eu pensei que era. Teria sido muito, muito útil.
Bryan Rayner
Isso (ainda) funciona? Estou recebendo template is not a function.
Jonic Biz
2
O bloco de código na parte superior desta resposta é uma citação da pergunta. Não funciona.
Jason Orendorff
27

Não, não há uma maneira de fazer isso sem a geração dinâmica de código.

No entanto, criei uma função que transformará uma sequência regular em uma função que pode ser fornecida com um mapa de valores, usando seqüências de caracteres de modelo internamente.

Gerar essência da sequência de modelos

/**
 * Produces a function which uses template strings to do simple interpolation from objects.
 * 
 * Usage:
 *    var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
 * 
 *    console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
 *    // Logs 'Bryan is now the king of Scotland!'
 */
var generateTemplateString = (function(){
    var cache = {};

    function generateTemplate(template){
        var fn = cache[template];

        if (!fn){
            // Replace ${expressions} (etc) with ${map.expressions}.

            var sanitized = template
                .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
                    return `\$\{map.${match.trim()}\}`;
                    })
                // Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
                .replace(/(\$\{(?!map\.)[^}]+\})/g, '');

            fn = Function('map', `return \`${sanitized}\``);
        }

        return fn;
    }

    return generateTemplate;
})();

Uso:

var kingMaker = generateTemplateString('${name} is king!');

console.log(kingMaker({name: 'Bryan'}));
// Logs 'Bryan is king!' to the console.

Espero que isso ajude alguém. Se você encontrar um problema com o código, por favor, atualize o Gist.

Bryan Rayner
fonte
Obrigado! Eu usei isso em vez de uma solução javascript sprintf.
precisa saber é o seguinte
1
não funciona para todos os modelos var test = generateTemplateString('/api/${param1}/${param2}/') console.log(test({param1: 'bar', param2: 'foo'}))retornados/api/bar//
Guillaume Vincent
Obrigado, consertado. O regex estava incluindo uma única correspondência de $ {param1} / $ {param2} quando deveria ter sido duas correspondências.
21916 Bryan Rayner
Observe que este não funciona no IE11, devido à falta de suporte para back ticks.
precisa saber é o seguinte
1
Obviamente, se as seqüências de modelos não forem suportadas por um navegador, esse método não funcionará. Se você deseja usar seqüências de caracteres de modelo em navegadores não suportados, recomendo usar uma linguagem como TypeScript ou um transpiler como Babel; Essa é a única maneira de colocar o ES6 em navegadores antigos.
precisa saber é o seguinte
9

TLDR: https://jsfiddle.net/w3jx07vt/

Todo mundo parece estar preocupado em acessar variáveis, por que não apenas passá-las? Tenho certeza de que não será muito difícil obter o contexto variável no chamador e transmiti-lo. Use este https://stackoverflow.com/a/6394168/6563504 para obter os adereços da obj. Não posso testar para você agora, mas isso deve funcionar.

function renderString(str,obj){
    return str.replace(/\$\{(.+?)\}/g,(match,p1)=>{return index(obj,p1)})
}

Testado. Aqui está o código completo.

function index(obj,is,value) {
    if (typeof is == 'string')
        is=is.split('.');
    if (is.length==1 && value!==undefined)
        return obj[is[0]] = value;
    else if (is.length==0)
        return obj;
    else
        return index(obj[is[0]],is.slice(1), value);
}

function renderString(str,obj){
    return str.replace(/\$\{.+?\}/g,(match)=>{return index(obj,match)})
}

renderString('abc${a}asdas',{a:23,b:44}) //abc23asdas
renderString('abc${a.c}asdas',{a:{c:22,d:55},b:44}) //abc22asdas
M3D
fonte
@ s.meijer você poderia elaborar? Estou usando esse código com sucesso. jsfiddle.net/w3jx07vt
M3D
1
Regex melhor permitiria que você não selecionasse os ${}caracteres. Tente:/(?!\${)([^{}]*)(?=})/g
Eric Hodonsky
@Relic jsfiddle.net/w3jx07vt/2 Não consegui fazer isso funcionar, gostaria de ajudar e atualizarei minha postagem? :)
M3D
Então, do jeito que você está tentando pegá-lo, isso não ajuda muito, acabei substituindo uma string. Em vez de adicionar uma etapa de interpolação, para que eu possa usar a string como interp ou string. Não é chique, mas funcionou.
Eric Hodonsky
8

O problema aqui é ter uma função que tenha acesso às variáveis ​​de seu chamador. É por isso que vemos o uso direto evalno processamento de modelos. Uma solução possível seria gerar uma função usando parâmetros formais nomeados pelas propriedades de um dicionário e chamando-a com os valores correspondentes na mesma ordem. Uma maneira alternativa seria ter algo simples como este:

var name = "John Smith";
var message = "Hello, my name is ${name}";
console.log(new Function('return `' + message + '`;')());

E para quem usa o compilador Babel, precisamos criar um fechamento que se lembre do ambiente em que foi criado:

console.log(new Function('name', 'return `' + message + '`;')(name));
didinko
fonte
Seu primeiro trecho é realmente pior do que evalporque ele funciona apenas com a global namevariável
Bergi
@ Bergi Sua declaração é válida - o escopo da função será perdido. Eu queria apresentar uma solução fácil para o problema e forneci um exemplo simplificado do que pode ser feito. Alguém poderia simplesmente vir para cima com o seguinte para superar o problema: var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return '+ message +';')();}
didinko
Não, isso é exatamente o que não trabalho - new Functionnão tem acesso ao var namena templatefunção.
Bergi
O segundo recorte corrigiu meu problema ... Voto de mim! Obrigado, isso ajudou a resolver um problema temporário que estávamos tendo com dinâmica encaminhamento para um iframe :)
Kris Boyd
7

Há muitas boas soluções publicadas aqui, mas nenhuma ainda utiliza o método ES6 String.raw . Aqui está minha contriubuição. Ele tem uma limitação importante, pois aceita apenas propriedades de um objeto passado, o que significa que nenhuma execução de código no modelo funcionará.

function parseStringTemplate(str, obj) {
    let parts = str.split(/\$\{(?!\d)[\wæøåÆØÅ]*\}/);
    let args = str.match(/[^{\}]+(?=})/g) || [];
    let parameters = args.map(argument => obj[argument] || (obj[argument] === undefined ? "" : obj[argument]));
    return String.raw({ raw: parts }, ...parameters);
}
let template = "Hello, ${name}! Are you ${age} years old?";
let values = { name: "John Doe", age: 18 };

parseStringTemplate(template, values);
// output: Hello, John Doe! Are you 18 years old?
  1. Divida a sequência em partes textuais sem argumento. Veja regex .
    parts: ["Hello, ", "! Are you ", " years old?"]
  2. Divida a string em nomes de propriedades. Matriz vazia se a correspondência falhar.
    args: ["name", "age"]
  3. Mapeie parâmetros a partir objdo nome da propriedade. A solução é limitada pelo mapeamento superficial de um nível. Valores indefinidos são substituídos por uma sequência vazia, mas outros valores falsos são aceitos.
    parameters: ["John Doe", 18]
  4. Utilize String.raw(...)e retorne o resultado.
pekaaw
fonte
Por curiosidade, que valor o String.raw realmente fornece aqui? Parece que você está fazendo todo o trabalho de analisar a string e acompanhar o que são as subestações. Isso é muito diferente de simplesmente ligar .replace()repetidamente?
Steve Bennett
Fair point, @SteveBennett. Eu tive alguns problemas ao transformar uma string normal em uma string de modelo e encontrei uma solução criando o objeto bruto. Eu acho que reduz String.raw a um método de concatenação, mas acho que funciona muito bem. Eu gostaria de ver uma boa solução com .replace(), no entanto :) Eu acho que a legibilidade é importante, então, enquanto eu mesmo uso expressões regulares, tento nomeá-las para ajudar a entender tudo ...
pekaaw
6

Semelhante à resposta de Daniel (e à essência do s.meijer ), mas mais legível:

const regex = /\${[^{]+}/g;

export default function interpolate(template, variables, fallback) {
    return template.replace(regex, (match) => {
        const path = match.slice(2, -1).trim();
        return getObjPath(path, variables, fallback);
    });
}

//get the specified property or nested property of an object
function getObjPath(path, obj, fallback = '') {
    return path.split('.').reduce((res, key) => res[key] || fallback, obj);
}

Nota: Isso melhora um pouco o original do s.meijer, já que não corresponde a coisas como ${foo{bar}(a regex permite apenas caracteres entre chaves não encaracolados dentro ${e }).


UPDATE: Me pediram um exemplo usando isso, então aqui está:

const replacements = {
    name: 'Bob',
    age: 37
}

interpolate('My name is ${name}, and I am ${age}.', replacements)
Matt Browne
fonte
Você pode postar um exemplo realmente usando isso? Este javascript está um pouco além de mim. Eu sugeriria uma regex de /\$\{(.*?)(?!\$\{)\}/g(para manipular chaves de ninho). Eu tenho uma solução funcional, mas não tenho certeza de que seja tão portátil quanto a sua, então adoraria ver como isso deve ser implementado em uma página. O meu também usa eval().
Joe Regular
Fui adiante e publiquei uma resposta também, e eu adoraria seus comentários sobre como tornar isso mais seguro e voltado para o desempenho: stackoverflow.com/a/48294208 .
Joe Regular
@RegularJoe eu adicionei um exemplo. Meu objetivo era simplificá-lo, mas você está certo de que, se quiser lidar com chaves aninhadas, precisará alterar o regex. No entanto, não consigo pensar em um caso de uso para isso ao avaliar uma sequência regular como se fosse um literal de modelo (todo o objetivo dessa função). O que voce tinha em mente?
Matt Browne
Além disso, não sou especialista em desempenho nem em segurança; minha resposta é realmente apenas combinando duas respostas anteriores. Mas vou dizer que o uso evaldeixa você muito mais aberto a possíveis erros que causariam problemas de segurança, enquanto tudo que minha versão está fazendo é procurar uma propriedade em um objeto a partir de um caminho separado por pontos, o que deve ser seguro.
Matt Browne
5

Você pode usar o protótipo de cadeia, por exemplo

String.prototype.toTemplate=function(){
    return eval('`'+this+'`');
}
//...
var a="b:${b}";
var b=10;
console.log(a.toTemplate());//b:10

Mas a resposta da pergunta original não é possível.

sarkiroka
fonte
5

Gostei da resposta do s.meijer e escrevi minha própria versão com base na dele:

function parseTemplate(template, map, fallback) {
    return template.replace(/\$\{[^}]+\}/g, (match) => 
        match
            .slice(2, -1)
            .trim()
            .split(".")
            .reduce(
                (searchObject, key) => searchObject[key] || fallback || match,
                map
            )
    );
}
Daniel
fonte
1
Arrumado! Realmente limpo!
xpt
4

Eu exigi esse método com suporte para o Internet Explorer. Acontece que os ticks anteriores não são suportados pelo IE11. Além disso; usando evalou equivalente Functionnão parece certo.

Para aquele que percebe; Eu também uso backticks, mas esses são removidos por compiladores como o babel. Os métodos sugeridos por outros dependem deles no tempo de execução. Como dito antes; esse é um problema no IE11 e inferior.

Então é isso que eu criei:

function get(path, obj, fb = `$\{${path}}`) {
  return path.split('.').reduce((res, key) => res[key] || fb, obj);
}

function parseTpl(template, map, fallback) {
  return template.replace(/\$\{.+?}/g, (match) => {
    const path = match.substr(2, match.length - 3).trim();
    return get(path, map, fallback);
  });
}

Exemplo de saída:

const data = { person: { name: 'John', age: 18 } };

parseTpl('Hi ${person.name} (${person.age})', data);
// output: Hi John (18)

parseTpl('Hello ${person.name} from ${person.city}', data);
// output: Hello John from ${person.city}

parseTpl('Hello ${person.name} from ${person.city}', data, '-');
// output: Hello John from -
s.meijer
fonte
"usando eval ou sua função equivalente não parece correta." ... Sim ... eu concordo, mas acho que este é um dos poucos casos de uso em que se pode dizer "mmhkay, vamos usá-lo". Verifique jsperf.com/es6-string-tmpl - este é o meu caso de uso prático. Usando sua função (com o mesmo regexp que o meu) e o meu (eval + literal de string). Obrigado! :)
Andrea Puddu
@AndreaPuddu, seu desempenho é realmente melhor. Mas, novamente; seqüências de caracteres de modelo não são suportadas no IE. Então, eval('`' + taggedURL + '`')simplesmente não funciona.
precisa saber é o seguinte
"Parece" melhor, eu diria, porque foi testado isoladamente ... O único objetivo desse teste era ver os possíveis problemas de desempenho em uso eval. Em relação aos literais do modelo: obrigado por apontar isso novamente. Eu estou usando Babel para transpilar meu código, mas minha função ainda não funcionará aparentemente #
Andrea Puddu
3

Atualmente, não posso comentar sobre as respostas existentes, por isso não posso comentar diretamente sobre a excelente resposta de Bryan Raynor. Portanto, essa resposta atualizará sua resposta com uma leve correção.

Em resumo, sua função falha ao armazenar em cache a função criada; portanto, ela sempre será recriada, independentemente de ter visto o modelo antes. Aqui está o código corrigido:

    /**
     * Produces a function which uses template strings to do simple interpolation from objects.
     * 
     * Usage:
     *    var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
     * 
     *    console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
     *    // Logs 'Bryan is now the king of Scotland!'
     */
    var generateTemplateString = (function(){
        var cache = {};

        function generateTemplate(template){
            var fn = cache[template];

            if (!fn){
                // Replace ${expressions} (etc) with ${map.expressions}.

                var sanitized = template
                    .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
                        return `\$\{map.${match.trim()}\}`;
                    })
                    // Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
                    .replace(/(\$\{(?!map\.)[^}]+\})/g, '');

                fn = cache[template] = Function('map', `return \`${sanitized}\``);
            }

            return fn;
        };

        return generateTemplate;
    })();
user2501097
fonte
3

@Mateusz Moska, a solução funciona muito bem, mas quando a usei no React Native (modo de compilação), gera um erro: Caractere inválido '' ' , embora funcione quando a executo no modo de depuração.

Então, eu escrevi minha própria solução usando regex.

String.prototype.interpolate = function(params) {
  let template = this
  for (let key in params) {
    template = template.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), params[key])
  }
  return template
}

const template = 'Example text: ${text}',
  result = template.interpolate({
    text: 'Foo Boo'
  })

console.log(result)

Demonstração: https://es6console.com/j31pqx1p/

NOTA: Como não sei a causa raiz de um problema, aumentei um ticket no repositório react-native, https://github.com/facebook/react-native/issues/14107 , para que assim que possível corrigir / orientar-me sobre o mesmo :)

Mohit Pandey
fonte
isso suporta modelos que contêm o caractere de retorno. No entanto, em vez de tentar inventar um padrão de modelo, você provavelmente está melhor apenas usando bigode ou similar . dependendo da complexidade dos modelos, é uma abordagem de força bruta que não considera casos extremos - a chave pode conter um padrão especial de expressão regular.
SliverNinja - MSFT
2

Ainda dinâmico, mas parece mais controlado do que apenas usar uma avaliação nua:

const vm = require('vm')
const moment = require('moment')


let template = '### ${context.hours_worked[0].value} \n Hours worked \n #### ${Math.abs(context.hours_worked_avg_diff[0].value)}% ${fns.gt0(context.hours_worked_avg_diff[0].value, "more", "less")} than usual on ${fns.getDOW(new Date())}'
let context = {
  hours_worked:[{value:10}],
  hours_worked_avg_diff:[{value:10}],

}


function getDOW(now) {
  return moment(now).locale('es').format('dddd')
}

function gt0(_in, tVal, fVal) {
  return _in >0 ? tVal: fVal
}



function templateIt(context, template) {
  const script = new vm.Script('`'+template+'`')
  return script.runInNewContext({context, fns:{getDOW, gt0 }})
}

console.log(templateIt(context, template))

https://repl.it/IdVt/3

Robert Moskal
fonte
1

Esta solução funciona sem o ES6:

function render(template, opts) {
  return new Function(
    'return new Function (' + Object.keys(opts).reduce((args, arg) => args += '\'' + arg + '\',', '') + '\'return `' + template.replace(/(^|[^\\])'/g, '$1\\\'') + '`;\'' +
    ').apply(null, ' + JSON.stringify(Object.keys(opts).reduce((vals, key) => vals.push(opts[key]) && vals, [])) + ');'
  )();
}

render("hello ${ name }", {name:'mo'}); // "hello mo"

Nota: o Functionconstrutor é sempre criado no escopo global, o que pode potencialmente fazer com que variáveis ​​globais sejam substituídas pelo modelo, por exemplo,render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});

cruzanmo
fonte
0

Como estamos reinventando a roda em algo que seria um recurso adorável em javascript.

Eu uso eval(), o que não é seguro, mas o javascript não é seguro. Eu admito prontamente que não sou excelente com javascript, mas tinha uma necessidade e precisava de uma resposta, então fiz uma.

Eu escolhi estilizar minhas variáveis ​​com um @e não um $, principalmente porque eu quero usar o recurso de múltiplas linhas de literais sem avaliar até que esteja pronto. Portanto, a sintaxe variável é@{OptionalObject.OptionalObjectN.VARIABLE_NAME}

Como não sou especialista em javascript, gostaria de receber conselhos sobre melhorias, mas ...

var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.length; i++) {
    prsLiteral = rt.replace(prsRegex,function (match,varname) {
        return eval(varname + "[" + i + "]");
        // you could instead use return eval(varname) if you're not looping.
    })
    console.log(prsLiteral);
}

Uma implementação muito simples segue

myResultSet = {totalrecords: 2,
Name: ["Bob", "Stephanie"],
Age: [37,22]};

rt = `My name is @{myResultSet.Name}, and I am @{myResultSet.Age}.`

var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.totalrecords; i++) {
    prsLiteral = rt.replace(prsRegex,function (match,varname) {
        return eval(varname + "[" + i + "]");
        // you could instead use return eval(varname) if you're not looping.
    })
    console.log(prsLiteral);
}

Na minha implementação real, eu escolho usar @{{variable}}. Mais um conjunto de chaves. É absurdamente improvável encontrar isso inesperadamente. O regex para isso seria semelhante/\@\{\{(.*?)(?!\@\{\{)\}\}/g

Para facilitar a leitura

\@\{\{    # opening sequence, @{{ literally.
(.*?)     # capturing the variable name
          # ^ captures only until it reaches the closing sequence
(?!       # negative lookahead, making sure the following
          # ^ pattern is not found ahead of the current character
  \@\{\{  # same as opening sequence, if you change that, change this
)
\}\}      # closing sequence.

Se você não tem experiência com regex, uma regra bastante segura é escapar a todos os caracteres não alfanuméricos e nunca escapar desnecessariamente de letras, pois muitas letras escapadas têm um significado especial para praticamente todos os tipos de regex.

Joe Regular
fonte
0

Você deve experimentar este pequeno módulo JS, de Andrea Giammarchi, do github: https://github.com/WebReflection/backtick-template

/*! (C) 2017 Andrea Giammarchi - MIT Style License */
function template(fn, $str, $object) {'use strict';
  var
    stringify = JSON.stringify,
    hasTransformer = typeof fn === 'function',
    str = hasTransformer ? $str : fn,
    object = hasTransformer ? $object : $str,
    i = 0, length = str.length,
    strings = i < length ? [] : ['""'],
    values = hasTransformer ? [] : strings,
    open, close, counter
  ;
  while (i < length) {
    open = str.indexOf('${', i);
    if (-1 < open) {
      strings.push(stringify(str.slice(i, open)));
      open += 2;
      close = open;
      counter = 1;
      while (close < length) {
        switch (str.charAt(close++)) {
          case '}': counter -= 1; break;
          case '{': counter += 1; break;
        }
        if (counter < 1) {
          values.push('(' + str.slice(open, close - 1) + ')');
          break;
        }
      }
      i = close;
    } else {
      strings.push(stringify(str.slice(i)));
      i = length;
    }
  }
  if (hasTransformer) {
    str = 'function' + (Math.random() * 1e5 | 0);
    if (strings.length === values.length) strings.push('""');
    strings = [
      str,
      'with(this)return ' + str + '([' + strings + ']' + (
        values.length ? (',' + values.join(',')) : ''
      ) + ')'
    ];
  } else {
    strings = ['with(this)return ' + strings.join('+')];
  }
  return Function.apply(null, strings).apply(
    object,
    hasTransformer ? [fn] : []
  );
}

template.asMethod = function (fn, object) {'use strict';
  return typeof fn === 'function' ?
    template(fn, this, object) :
    template(this, fn);
};

Demo (todos os seguintes testes retornam true):

const info = 'template';
// just string
`some ${info}` === template('some ${info}', {info});

// passing through a transformer
transform `some ${info}` === template(transform, 'some ${info}', {info});

// using it as String method
String.prototype.template = template.asMethod;

`some ${info}` === 'some ${info}'.template({info});

transform `some ${info}` === 'some ${info}'.template(transform, {info});
colxi
fonte
0

Eu fiz minha própria solução fazendo um tipo com uma descrição em função

export class Foo {
...
description?: Object;
...
}

let myFoo:Foo = {
...
  description: (a,b) => `Welcome ${a}, glad to see you like the ${b} section`.
...
}

e assim fazendo:

let myDescription = myFoo.description('Bar', 'bar');
cesarmarinhorj
fonte
0

Em vez de usar eval melhor, use regex

Eval não é recomendado e altamente desencorajado, portanto, não o use ( mdn eval ).

 let b = 10;
 let a="b:${b}";

let response = a.replace(/\${\w+}/ ,b);
conssole.log(response);
Vijay Palaskar
fonte
funciona para um, e se eu tiver "a é $ {a}, b é {b} ..."?
leachim