DEV Community

Nick | OneThingWell.dev
Nick | OneThingWell.dev

Posted on • Originally published at betterways.dev

Simple JavaScript template engine by (ab)using template literals

Templating inline strings is simple using ES6 template literals:

const a = "hello"
const b = "world"

console.log(`${a} ${b}`)
//hello world
Enter fullscreen mode Exit fullscreen mode

But what if you have a string that can't be inlined (i.e. it comes from an external source)?

Here's the gist of the function I recently used for this purpose:

function tpl(str, props) {
    let names = Object.keys(props);
    let vals = Object.values(props);
    return new Function(...names, `return \`${str}\`;`)(...vals);
}
Enter fullscreen mode Exit fullscreen mode

Or, (a slightly less readable) one-liner version:

const tpl = (str, props) => new Function(...Object.keys(props), `return \`${str}\`;`)(...Object.values(props))
Enter fullscreen mode Exit fullscreen mode

Example:

console.log(tpl('${a} ${b}', {a: 'hello', b: 'world'}))
//hello world
Enter fullscreen mode Exit fullscreen mode

Explanation

So, we are defining a function called tpl:

function tpl(str, props) {
    let names = Object.keys(props);
    let vals = Object.values(props);
    return new Function(...names, `return \`${str}\`;`)(...vals);
}
Enter fullscreen mode Exit fullscreen mode

The first two lines are self-explanatory - we are just extracting keys and values from the passed-in props object.

In the last line, we are dynamically creating a function from a string. Instead of doing eval, we are using the Function constructor, which is a much safer alternative.

Our new (dynamically generated) function receives a list of names of our props keys, and returns the same passed-in str string but delimited with backtick characters (basically, we are turning our "external" string into an inline template string).

After that, we just need to call our new function with a list of values.

Function.call alternative

We could simplify things by doing something like this instead of extracting names and values:

new Function(`return \`${str}\`;`).call(props)
Enter fullscreen mode Exit fullscreen mode

But then we would need to use ${this.a} instead of just ${a}.

Caching

Each time you're calling the Function constructor, the function body needs to be parsed (some overhead), but it would be very easy to do some caching based on the value of str.




Note: This is a snapshot of the wiki page from the BetterWays.dev wiki, you can find the latest (better formatted) version here: betterways.dev/simple-javascript-template-engine-by-ab-using-template-literals.

Top comments (0)