Convertir una cadena en una cadena de plantilla


147

¿Es posible crear una cadena de plantilla como una cadena habitual?

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

y luego convertirlo en una cadena de plantilla

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

sin eval, new Functiony otros medios de generación de código dinámico?


55
¿Encontraste una manera de lograr esto? Puede que necesite hacerlo algún día y tengo curiosidad por saber a qué has llegado.
Bryan Rayner

@BryanRayner, digamos que su programa js está tratando de obtener datos de la API de descanso, cuya url está en un archivo config.js como una cadena "/ resources / <resource_id> / update /" y coloca "resource_id" dinámicamente desde su programa . A menos que desee dividir esa URL en partes y guardarla en diferentes áreas, necesita algún tipo de procesamiento de plantilla de cadena.
Ryu_hayabusa


En lugar de usar eval mejor, use regex Eval no es recomendable y altamente desaconsejado, así que no lo use developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…! dejar b = 10; deje a = "b: $ {b}"; let response = a.replace (/ \ $ {\ w +} /, b); conssole.log (respuesta);
Vijay Palaskar

Respuestas:


79

Como la cadena de su plantilla debe obtener referencia a la bvariable dinámicamente (en tiempo de ejecución), la respuesta es: NO, es imposible hacerlo sin la generación dinámica de código.

Pero con evalesto es bastante simple:

let tpl = eval('`'+a+'`');

77
eval es inseguro, al igual que otros medios de generación dinámica de código
KOLANICH

8
Para @KOLANICH particular, este caso - cotizaciones de escape traseros en la acadena y que será mucho menos insegura: let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');. Creo que lo más importante es evalevitar que el compilador optimice su código. Pero creo que es irrelevante para esta pregunta.
alexpods

3
De hecho, también puede ejecutar funciones dentro de cadenas de plantillas.
KOLANICH

9
@KOLANICH Lo siento, no te gusta eval. Sin embargo, recuerde que una plantilla literal es en sí misma una forma de eval. Dos ejemplos: prueba var = Result: ${alert('hello')}; prueba var = Result: ${b=4}; Ambos terminarán ejecutando código arbitrario en el contexto del script. Si desea permitir cadenas arbitrarias, también puede permitir eval.
Manngo

66
Ten cuidado. Desde algo así como Babel no transpile esto, este código no funcionará en IE
cgsd

79

En mi proyecto, he creado algo como esto con 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);

ACTUALIZACIÓN He eliminado la dependencia lodash, ES6 tiene métodos equivalentes para obtener claves y valores.


1
Hola, Su solución funciona muy bien, pero cuando la usé en React Native (modo de compilación), arroja un error: Carácter no válido '' ' , aunque funciona cuando ejecuto en modo de depuración. Parece, problema de Babel, ¿alguna ayuda?
Mohit Pandey

@MohitPandey Recibía el mismo error cuando ejecutaba pruebas de este código en PhantomJS y pasaba por Chrome. Si ese es el caso, creo que hay una nueva versión beta de PhantomJS en camino con un mejor soporte para ES6, puede intentar instalarlo.
Mateusz Moska

1
Desafortunadamente, no funciona y escribí una expresión regular para el mismo. Agregado como respuesta también.
Mohit Pandey

esta solución solo funciona si el carácter de retroceso "` "no está presente en la cadena de plantilla
SliverNinja - MSFT

Cuando lo intento lo tengo ReferenceError: _ is not defined. ¿Es el código no ES6 sino lodashespecífico, o ...?
xpt

29

Lo que estás pidiendo aquí:

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

es exactamente equivalente (en términos de potencia y, por ejemplo, seguridad) a eval: la capacidad de tomar una cadena que contiene código y ejecutar ese código; y también la capacidad del código ejecutado para ver variables locales en el entorno del llamante.

No hay forma en JS para que una función vea variables locales en su llamador, a menos que esa función sea eval(). Incluso Function()no puedo hacerlo.


Cuando escuche que hay algo llamado "cadenas de plantillas" en JavaScript, es natural suponer que es una biblioteca de plantillas incorporada, como Moustache. No lo es Es principalmente solo interpolación de cadenas y cadenas multilíneas para JS. Sin embargo, creo que esto será un error común durante un tiempo. :(


2
TBH, eso es lo que pensé que era. Hubiera sido muy, muy útil.
Bryan Rayner

¿Esto (todavía) funciona? Me estoy poniendo template is not a function.
Ionică Bizău

2
El bloque de código en la parte superior de esta respuesta es una cita de la pregunta. No funciona.
Jason Orendorff

27

No, no hay forma de hacerlo sin la generación dinámica de código.

Sin embargo, he creado una función que convertirá una cadena normal en una función que se puede proporcionar con un mapa de valores, utilizando cadenas de plantilla internamente.

Generar plantilla de cadena Gist

/**
 * 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 esto ayude a alguien. Si encuentra un problema con el código, sea tan amable de actualizar el Gist.


¡Gracias! Usé esto en lugar de una solución de JavaScript Sprintf.
seangwright

1
no funciona para cada var test = generateTemplateString('/api/${param1}/${param2}/') console.log(test({param1: 'bar', param2: 'foo'}))devolución de plantillas/api/bar//
Guillaume Vincent

Gracias, arreglado. La expresión regular incluía una sola coincidencia de $ {param1} / $ {param2} cuando debería haber sido dos coincidencias.
Bryan Rayner

Tenga en cuenta que este no funciona en IE11, debido a la falta de soporte para ticks posteriores.
s.meijer

1
Por supuesto, si un navegador no admite cadenas de plantillas, este método no funcionará. Si desea utilizar cadenas de plantillas en navegadores no compatibles, le recomendaría usar un lenguaje como TypeScript o un transpilador como Babel; Esa es la única forma de llevar ES6 a los navegadores antiguos.
Bryan Rayner

9

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

Todo el mundo parece estar preocupado por acceder a las variables, ¿por qué no simplemente pasarlas? Estoy seguro de que no será demasiado difícil obtener el contexto variable en la persona que llama y transmitirlo. Use este https://stackoverflow.com/a/6394168/6563504 para obtener los accesorios de obj. No puedo hacerte una prueba en este momento, pero esto debería funcionar.

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

Probado Aquí está el 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

@ s.meijer podría elaborar? Estoy usando este código con éxito. jsfiddle.net/w3jx07vt
M3D

1
Una mejor expresión regular le permitiría no seleccionar los ${}caracteres. Prueba:/(?!\${)([^{}]*)(?=})/g
Eric Hodonsky

@Relic jsfiddle.net/w3jx07vt/2 No pude hacerlo funcionar, ¿ me gustaría echar una mano y actualizaré mi publicación? :)
M3D

Entonces, la forma en que tratas de agarrarlo, esto en realidad no ayudará mucho, terminé haciendo un reemplazo de cadena en su lugar. En lugar de agregar un paso de interpolación, puedo usar la cadena como interp o cadena. No es lujoso, pero funcionó.
Eric Hodonsky

8

El problema aquí es tener una función que tenga acceso a las variables de su llamador. Es por eso que vemos que evalse usa directamente para el procesamiento de plantillas. Una posible solución sería generar una función tomando parámetros formales nombrados por las propiedades de un diccionario y llamándolo con los valores correspondientes en el mismo orden. Una forma alternativa sería tener algo simple como esto:

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

Y para cualquiera que use el compilador de Babel, necesitamos crear un cierre que recuerde el entorno en el que se creó:

console.log(new Function('name', 'return `' + message + '`;')(name));

Su primer fragmento es en realidad peor que evalporque funciona solo con una namevariable global
Bergi

@Bergi Su declaración es válida: se perderá el alcance de la función. Quería presentar una solución fácil al problema y proporcioné un ejemplo simplificado de lo que se puede hacer. Simplemente se podría llegar a lo siguiente para superar el problema: var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return '+ mensaje +';')();}
didinko

No, eso es exactamente lo que no funcionaría: new Functionno tiene acceso a var namela templatefunción.
Bergi

El segundo recorte solucionó mi problema ... ¡Voto de mí! Gracias, esto ayudó a resolver un problema temporal que teníamos con el enrutamiento dinámico a un iframe :)
Kris Boyd

7

Hay muchas buenas soluciones publicadas aquí, pero todavía no hay ninguna que utilice el método ES6 String.raw . Aquí está mi contribución. Tiene una limitación importante porque solo aceptará propiedades de un objeto pasado, lo que significa que no funcionará la ejecución de código en la plantilla.

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. Dividir la cadena en partes textuales sin argumentos. Ver expresiones regulares .
    parts: ["Hello, ", "! Are you ", " years old?"]
  2. Dividir la cadena en nombres de propiedad. Matriz vacía si la coincidencia falla.
    args: ["name", "age"]
  3. Parámetros de mapeo objpor nombre de propiedad. La solución está limitada por un mapeo superficial de un nivel. Los valores no definidos se sustituyen con una cadena vacía, pero se aceptan otros valores falsos.
    parameters: ["John Doe", 18]
  4. Utilizar String.raw(...)y devolver el resultado.

Por curiosidad, ¿qué valor está proporcionando String.raw realmente aquí? Parece que está haciendo todo el trabajo de analizar la cadena y realizar un seguimiento de cuáles son las subtensiones. ¿Es esto muy diferente de simplemente llamar .replace()repetidamente?
Steve Bennett

Punto justo, @SteveBennett. Tuve algunos problemas para convertir una cadena normal en una cadena de plantilla, y encontré una solución construyendo el objeto en bruto yo mismo. Supongo que reduce String.raw a un método de concatenación, pero creo que funciona bastante bien. Me gustaría ver una solución agradable con .replace(), sin embargo :) Creo que la lectura es importante, por lo que durante el uso de expresiones regulares a mí mismo, trato de nombrarlos para ayudar a hacer sentido a todo ...
pekaaw

6

Similar a la respuesta de Daniel (y la esencia de mi mejer ) pero más legible:

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: Esto mejora ligeramente el original de s.meijer, ya que no coincidirá con cosas como ${foo{bar}(la expresión regular solo permite caracteres de llaves no rizadas dentro ${y }).


ACTUALIZACIÓN: Me pidieron un ejemplo usando esto, así que aquí tienes:

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

interpolate('My name is ${name}, and I am ${age}.', replacements)

¿Puedes publicar un ejemplo realmente usando esto? Este javascript está un poco más allá de mí. Sugeriría una expresión regular de /\$\{(.*?)(?!\$\{)\}/g(para manejar llaves de nido). Tengo una solución que funciona, pero no estoy seguro de que sea tan portátil como la suya, por lo que me encantaría ver cómo se debe implementar en una página. El mío también usa eval().
Regular Joe

Seguí adelante y publiqué una respuesta también, y me encantaría tu opinión sobre cómo hacer eso más seguro y orientado al rendimiento: stackoverflow.com/a/48294208 .
Joe regular el

@RegularJoe Agregué un ejemplo. Mi objetivo era mantenerlo simple, pero tienes razón en que si quieres manejar llaves rizadas anidadas, necesitarías cambiar la expresión regular. Sin embargo, no puedo pensar en un caso de uso para eso cuando evalúo una cadena regular como si fuera un literal de plantilla (todo el propósito de esta función). ¿Qué tenías en mente?
Matt Browne

Además, no soy un experto en rendimiento ni un experto en seguridad; mi respuesta es realmente solo combinar dos respuestas anteriores. Pero diré que usarlo evalte deja mucho más abierto a posibles errores que causarían problemas de seguridad, mientras que todo lo que mi versión está haciendo es buscar una propiedad en un objeto desde una ruta separada por puntos, lo que debería ser seguro.
Matt Browne

5

Puede usar el prototipo de cadena, por ejemplo

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

Pero la respuesta de la pregunta original es de ninguna manera.


5

Me gustó la respuesta de s.meijer y escribí mi propia versión basada en la suya:

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

1
¡Ordenado! Realmente aseado!
xpt

4

Requerí este método con soporte para Internet Explorer. Resultó que los ticks posteriores no son compatibles ni siquiera con IE11. También; usando evalo es equivalente Functionno se siente bien.

Para el que nota; También uso backticks, pero estos son eliminados por compiladores como babel. Los métodos sugeridos por otros dependen de ellos en el tiempo de ejecución. Como se dijo antes; Este es un problema en IE11 y versiones anteriores.

Entonces esto es lo que se me ocurrió:

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);
  });
}

Salida de ejemplo:

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 -

"usar eval o su equivalente Function no se siente bien". ... Sí ... estoy de acuerdo, pero creo que este es uno de los pocos casos de uso en el que se podría decir, "mmhkay, usémoslo". Consulte jsperf.com/es6-string-tmpl : es mi caso de uso práctico. Usando su función (con la misma expresión regular que la mía) y la mía (eval + literales de cadena). ¡Gracias! :)
Andrea Puddu

@AndreaPuddu, su rendimiento es realmente mejor. Pero entonces de nuevo; las cadenas de plantilla no son compatibles con IE. Entonces eval('`' + taggedURL + '`')simplemente no funciona.
s.meijer

"Parece" mejor, diría, porque se probó de forma aislada ... El único propósito de esa prueba era ver los posibles problemas de rendimiento eval. Con respecto a los literales de plantilla: gracias por señalarlo nuevamente. Estoy usando Babel para transpilar mi código, pero aparentemente mi función todavía no funcionará 😐
Andrea Puddu

3

Actualmente no puedo comentar sobre las respuestas existentes, así que no puedo comentar directamente sobre la excelente respuesta de Bryan Raynor. Por lo tanto, esta respuesta va a actualizar su respuesta con una ligera corrección.

En resumen, su función no puede en realidad almacenar en caché la función creada, por lo que siempre se recreará, independientemente de si se ha visto la plantilla antes. Aquí está el código corregido:

    /**
     * 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;
    })();

3

@Mateusz Moska, la solución funciona muy bien, pero cuando la usé en React Native (modo de compilación), arroja un error: Carácter no válido '' ' , aunque funciona cuando lo ejecuto en modo de depuración.

Entonces escribí mi propia solución 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)

Demostración: https://es6console.com/j31pqx1p/

NOTA: Como no conozco la causa raíz de un problema, planteé un ticket en el repositorio react-native, https://github.com/facebook/react-native/issues/14107 , para que una vez que puedan arreglar / guiarme por lo mismo :)


esto admite plantillas que contienen el carácter de retroceso. Sin embargo, en lugar de tratar de inventar un patrón de plantilla, probablemente sea mejor usar solo bigote o similar . dependiendo de cuán complejas sean sus plantillas, este es un enfoque de fuerza bruta que no considera casos extremos: la clave podría contener un patrón de expresión regular especial.
SliverNinja - MSFT

2

Todavía dinámico, pero parece más controlado que simplemente usando una evaluación desnuda:

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


1

Esta solución funciona sin 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: el Functionconstructor siempre se crea en el ámbito global, lo que podría hacer que la plantilla sobrescriba las variables globales, p. Ej.render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});


0

Ya que estamos reinventando la rueda en algo que sería una característica encantadora en javascript.

Yo uso eval(), que no es seguro, pero JavaScript no es seguro. Admito que no soy excelente con javascript, pero tenía una necesidad y necesitaba una respuesta, así que hice una.

Elegí estilizar mis variables con un en @lugar de un $, particularmente porque quiero usar la función multilínea de literales sin evaluar hasta que esté lista. Entonces la sintaxis variable es@{OptionalObject.OptionalObjectN.VARIABLE_NAME}

No soy un experto en JavaScript, por lo que con gusto tomaría consejos sobre mejoras pero ...

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);
}

Sigue una implementación muy simple

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);
}

En mi implementación real, elijo usar @{{variable}}. Un juego más de llaves. Absurdamente improbable encontrar eso inesperadamente. La expresión regular para eso se vería así/\@\{\{(.*?)(?!\@\{\{)\}\}/g

Para que sea más fácil de leer

\@\{\{    # 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.

Si no tiene experiencia con expresiones regulares, una regla bastante segura es escapar de todos los caracteres no alfanuméricos, y nunca escapar innecesariamente de las letras, ya que muchas letras tienen un significado especial para prácticamente todos los sabores de expresiones regulares.


0

Deberías probar este pequeño módulo JS, de Andrea Giammarchi, de 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);
};

Demostración (todas las siguientes pruebas son verdaderas):

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});

0

Hice mi propia solución haciendo un tipo con una descripción como función

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

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

y al hacerlo:

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

0

En lugar de usar eval mejor es usar regex

Eval no es recomendable y altamente desaconsejado, así que no lo use ( mdn eval ).

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

let response = a.replace(/\${\w+}/ ,b);
conssole.log(response);

funciona para uno, ¿qué pasa si tengo "a es $ {a}, b es {b} ..."?
leachim
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.