Template strings as a template language.

# Thomas (9 years ago)

I'd really like to use Template strings as a templating language, but unless I include a lot of boilerplate code (export a template string wrapped in a function from a file) or use eval after loading a file as a string it's pretty much impossible.

Is there a simpler way to be doing this? Or any plans for a type of eval that only executes it's argument as a template string?

Sent from my iPhone

# Mark S. Miller (9 years ago)

On Sun, Sep 13, 2015 at 2:42 AM, Thomas <thomasjamesfoster at bigpond.com>

wrote:

I'd really like to use Template strings as a templating language, but unless I include a lot of boilerplate code (export a template string wrapped in a function from a file)

Hi Thomas, could you give a concrete example of the boilerplate you have in mind and what it accomplishes?

or use eval after loading a file as a string it's pretty much impossible.

Is there a simpler way to be doing this? Or any plans for a type of eval that only executes it's argument as a template string?

I am unaware of any such plans. Could you give an example of what it looks like and what it would accomplish? Thanks.

# Alexander Jones (9 years ago)

I think it would look something like this:

tag `string text ${expression} string text`
// vs.
String.templateEval("string text ${expression} string text", tag)

(Clearly, evaluation errors would be a runtime error, much like how eval itself works.)

My hunch is this would be quite a useful complement to the template string feature. It's similar in essence to constructing a RegExp using the RegExp constructor instead of the literal syntax, and is a clear parallel to eval.

# Thomas (9 years ago)

What I've been doing:

export const template = ({title, content}) => template string for ${title};

Or variations thereof. I then import that module wherever I need to use the template and call it as a function.

Using eval and having the template string as a normal string (so, read the template from file as a string, wrap it with back ticks, and then pass it to eval) at the moment is risky since it's possible for input to prematurely end the template string and do nasty stuff*. Ideally there would be a variant of eval where the string to be evaluated must be a template string expression.

Thomas

  • I'm aware that someone could still put something inside a template string and do nasty stuff, but I'm not sure if that's a easily solved problem.
# Mark S. Miller (9 years ago)

On Sun, Sep 13, 2015 at 7:08 AM, Thomas <thomasjamesfoster at bigpond.com>

wrote:

What I've been doing:

export const template = ({title, content}) => template string for ${title};

Or variations thereof. I then import that module wherever I need to use the template and call it as a function.

If you were not to call it as a function, where would it get its bindings for title and content?

# Alexander Jones (9 years ago)

Same place as eval. Arguably both should have the option of explicitly passing a scope, for extra strictness, like Python's.

# Thomas (9 years ago)

They would be whatever they are in the scope in which the template string is evaluated.

Thomas

# Mark S. Miller (9 years ago)

How is explicitly passing a scope different from calling the function with a

{title, content}

object?

# Mark S. Miller (9 years ago)

The great achievement of modern js module systems including the es6 std module system is to end the greatest pain of prior js: linkage through side effects to the shared global scope. Good riddance.

If you're talking about a scope other than the global one, how would a template come to be evaluated in that scope? If the answer is that it is textually nested in that scope, then isn't that what template strings already do?

# Thomas (9 years ago)

Mark,

I'll put together a GitHub gist or repo and send it to you in the morning (I should be asleep) to make things clearer.

I'll try explaining it differently in the meantime: What I'd like to do is use ES6 Template string as a templating language. For the sake of readability and maintainability, it would be nice to keep the templates separate from the code that uses them, and not have them inlined.

The best solution for my use case would be to have a special eval function which would evaluate a normal string variable as if it was a template string, in the current scope.

Thomas

# Alexander Jones (9 years ago)

Not exactly sure what you mean. But if you are you asking how

let template = 'this ${foo} and that ${bar}';
// later...
let output = String.evalTemplate(template, {foo: "thing", bar: "other
thing"});

is different to

let template = ({foo, bar}) => `this ${foo} and that ${bar}`;
// later...
let output = template({foo: "thing", bar: "other thing"});

then I have a couple of answers off the top of my head:

  • The value of template is a simple string and thus can be trivially loaded from JSON or a file in the former case, but not the latter. Getting the latter involves some kind of eval anyway.
  • The number of occurrences of each template parameter (e.g. "foo") is limited to once at the definition site, and once at the invocation site, instead of twice at definition. with seems like a non-starter.
# Thomas (9 years ago)

On 14 Sep 2015, at 1:38 AM, Alexander Jones <alex at weej.com> wrote:

Not exactly sure what you mean. But if you are you asking how

let template = 'this ${foo} and that ${bar}';
// later...
let output = String.evalTemplate(template, {foo: "thing", bar: "other thing"});

is different to

let template = ({foo, bar}) => `this ${foo} and that ${bar}`;
// later...
let output = template({foo: "thing", bar: "other thing"});

then I have a couple of answers off the top of my head:

  • The value of template is a simple string and thus can be trivially loaded from JSON or a file in the former case, but not the latter. Getting the latter involves some kind of eval anyway.

Exactly - being able to not touch modules for something like this is important.

  • The number of occurrences of each template parameter (e.g. "foo") is limited to once at the definition site, and once at the invocation site, instead of twice at definition. with seems like a non-starter.

Minimising definitions is worth doing. Although, the good thing about arguments is that it's possible to specify default values...

# Bob Myers (9 years ago)

Templating languages typically "compile" templates into functions through various lexical transformations.

Consider a template file foo.tem:

My name is ${this.name}.

Lexically transform this into

function foo() {
  return `My name is ${this.name|}.`;
}

Then invoke the template as eg

foo.call({name: 'Bob'})

Having said that, I doubt if ES6 template strings serve as a useful basis for a full-fledged templating system. To take just one basic example, how would one implement the equivalent of {{#if}}?

Bob

# Mark S. Miller (9 years ago)

On Sun, Sep 13, 2015 at 8:58 AM, Bob Myers <rtm at gol.com> wrote:

Templating languages typically "compile" templates into functions through various lexical transformations.

Consider a template file foo.tem:

My name is ${this.name}.

Lexically transform this into

function foo() {
  return `My name is ${this.name|}.`;
}

Then invoke the template as eg

foo.call({name: 'Bob'})


Having said that, I doubt if ES6 template strings serve as a useful basis
for a full-fledged templating system. To take just one basic example, how
would one implement the equivalent of `{{#if}}`?

What does {{#if}} mean?

# Isiah Meadows (9 years ago)

On Sun, Sep 13, 2015 at 7:09 PM, Mark S. Miller <erights at google.com> wrote:

On Sun, Sep 13, 2015 at 8:58 AM, Bob Myers <rtm at gol.com> wrote:

Templating languages typically "compile" templates into functions through various lexical transformations.

Consider a template file foo.tem:

My name is ${this.name}.

Lexically transform this into

function foo() {
  return `My name is ${this.name|}.`;
}

Then invoke the template as eg

foo.call({name: 'Bob'})


Having said that, I doubt if ES6 template strings serve as a useful basis
for a full-fledged templating system. To take just one basic example, how
would one implement the equivalent of `{{#if}}`?

What does {{#if}} mean?

An example from Handlebars' website (which is likely where he drew the syntax from):

{{permalink}}
{{#each comments}}
  {{../permalink}}

  {{#if title}}
    {{../permalink}}
  {{/if}}
{{/each}}
# Ron Buckton (9 years ago)

This is theoretically possible:

let t = $template`
  ${$item.permalink}
  ${$each($item.comments)`
    ${$parent.permalink}
    ${$if($item.title)`
      ${$parent.permalink}
    `}
  `}
`;
let s = t(data);

...given an adequate implementation using proxies (to create bindings for e.g. $item.permalink for later evaluation) and tagged template functions. Whether or not this would make for a reasonable implementation is left to the reader.

Ron

# Thomas (9 years ago)

For those interested, this gist better shows what's being discussed: gist.github.com/thomasfoster96/193e7c08aae499f810a1

Ron: Yes, that's already possible - but tagged template strings don't really offer much of an advantage over a function as far as templating goes (IMHO).

Thomas

# Andrea Giammarchi (9 years ago)

without using eval, discussed already months ago

# Andrea Giammarchi (9 years ago)

sorry, early send (I think it was a ctrl+return ? ) ... I was saying ..

without using eval and already discussed months ago: gist.github.com/WebReflection/8f227532143e63649804

it's based on the glorious with statement and it works like a charm

# Herby Vojčík (9 years ago)

Andrea Giammarchi wrote:

sorry, early send (I think it was a ctrl+return ? ) ... I was saying ..

without using eval and already discussed months ago: gist.github.com/WebReflection/8f227532143e63649804

I had the impression the "no-eval" was in fact "no eval or Function", in which case this does not count either.

# Andrea Giammarchi (9 years ago)

Function is not even closely comparable to eval in this case cause it's used mostly to opt out from possible surrounding strict directive and it doesn't need to evaluate in the scope like both templates and eval do. But yeah, I guess it can be considered a runtime string evaluation. It does what it's supposed to do and it works even under CSP.

# Thomas (9 years ago)

Doesn't the code in that gist create functions at runtime from strings? If so that's not any better than eval.

# Andrea Giammarchi (9 years ago)

again, Function is better and different than eval, but I see this conversation is bringing nothing so I'll just stop explaining.

# Thomas (9 years ago)

Sorry, I sent that before I saw your other explanation.

On a second look that function does work pretty well. The use of Function still means strings are being evaluated at runtime, but I agree that it's safer/better than eval (though I'm not sure why CSP would still like it over eval).

Have you any plans to make the String.prototype.template function a proposal?

Thomas

# Andrea Giammarchi (9 years ago)

nobody here raised the minimal interest in what I've achieved with those few lines of code, I rather had people saying "with statement? ewww" or similar reactions about Function like yours ... I don't think as proposal would ever make it, it's too easy to polyfill so I'd expect many - 1

¯_(ツ)_/¯

# Thomas (9 years ago)

With all due respect to your solution (it works, and it works well), it's far from ideal to be relying upon with and Function. I guess what I'm trying to get at is - wouldn't it be better to think about having a built in version that didn't use with or eval (should be easy enough)? I sense that you're not confident about it being successful in that sense, but a built in function for this trumps with and Function any day of the week.

Thomas

# Bob Myers (9 years ago)

May be getting off-topic here, but I wonder why we need templating languages at all in this day and age. Originally templating systems were developed for server-centric applications such as PHP etc. which generate HTML and blast it down to the client, as a way to untangle program logic and display logic and get away from madness such as <p><? $post.text ?></p> or much, much worse.

When people starting writing client-side frameworks such as Ember etc., they brought over the notion of template languages without much thought, if I may say so. The client has perfectly good mechanisms for creating HTML with JS, that with bits of sugar are just as expressive as a template. Actually more expressive, because I can use arbitrary JS in my element creation, not just those constructs which the template language designer deigned to provide to me.

Template languages bring in an entire new set of programming constructs, such as conditionals, loops, and computations, which duplicate those already present in JS. So if I want to loop in JS I use forEach, but if I want to loop in my template language I have to use {{#each}}. Some templating languages even provide the equivalent of function calls with their own weird syntax and some now support currying (!). For religious reasons, some templating languages limit the amount of computation that can be done within the template, so if I want to do something as simple as add one to a value, I have to precalculate that in my program logic and include the value in the scope passed to the template.

It's weird when you think about it: We have a template which is for all practical purposes a big string, then we do what is essentially a kind of macro pre-processing of the string, then we feed the string into the DOM with innerHTML or whatever which requires parsing it again.

And what happens if a value changes? In the worst case, we have to re-evaluate the entire template, and replace the entire result in the DOM. Now some templating implementations are developing ways to incrementally update the template and proclaiming loudly that they have invented an amazing new wheel. But if we dealt with the DOM directly via JS without the intermediation of templates, it would be trivial to update the DOM when something changed.

What's more, a new language like templates brings with it an entire new toolchain. We now need ways to compile these templates, and lint these templates, and debug these templates, bringing yet another level of complexity into our build systems.

In React.js, we see an interesting evolution of the entire notion of constructing the DOM. As I understand it, elements are created directly within JS, as in my opinion they should be. To avoid having to write var foo = document.createElement('div').appendChild(document.createTextElement('bar')), it makes HTML syntax directly available from within JS, So you can say var foo = <div>bar</div>;. It may horrify some purists, and it does introduce another step in the build chain, but it's a great improvement over templates IMHO.

One interesting client-side framework called Mithril does away with templates entirely and creates DOM nodes via JS via sugar routines such as var foo = m("div", "bar");. Works for me.

So rather than attempting to figure out how the ES6 template string hammer can be used to pound in the templating system nail, I'd ask instead why we need to pound in that nail at all.

Bob

On Tue, Sep 15, 2015 at 10:54 AM, Thomas <thomasjamesfoster at bigpond.com>

wrote:

# Andrea Giammarchi (9 years ago)

Bob, not sure why you think template means HTML, in my case the main problem I have in JS is the absence of basic printf like syntax so that I can build at runtime any kind of i18n string passing an object, without needing to have the string evaluated in place, which is the strength of ES6 templates when it comes to server side and tooling tasks, but completely overhead and non i18n friendly on the client side.

Since printf for some reason has never landed in JS but we have a lightweight eval through ${this} I've recycled the syntax and semantics expected by ES6 templates and tags in order to be able to use any sort of source of data for those values within curly brackets.

TL;DR it amuses me how much people still relate JS strictly with the Web and the HTML in this era of Internet of Things, drones, nodejs nashorn, espruino, kinomajs and all other JS engines that don't even necessarily need to know what is HTML and wouldn't benefit a single bit from the good old E4X or the current React thing

So if template should be, please let's think about an agnostic one and de-couple JS from HTML as much as possible, thank you.

# Herby Vojčík (9 years ago)

OTOH, it is a question if it is wprth it at all, as we have IIFEs and maybe will have do expressions, so you can stick anything to template string anyway. OTYOH, new Regexp had its merits....

Dňa 15. septembra 2015 7:24:26 CEST používateľ Thomas <thomasjamesfoster at bigpond.com> napísal:

# Herby Vojčík (9 years ago)

Dňa 15. septembra 2015 12:05:16 CEST používateľ "Herby Vojčík" <herby at mailbox.sk> napísal:

OTOH, it is a question if it is wprth it at all, as we have IIFEs and maybe will have do expressions, so you can stick anything to template string anyway. OTYOH, new Regexp had its merits....

s/had/has/

# Andrea Giammarchi (9 years ago)

Yet it doesn't play well with i18n since arguments passed to the IIFE would still come from the current local scope. RegExp would play nicer but it can't do fancy inline JS evaluation, example:

String.prototype.template = function (o) {
  return this.replace(/\$\{(.+?)\}/g, function ($0,  $1) {
    return  o[$1];
  });
};

'Helo ${name}!'.template({name: 'World'});

That could also work combined with template strings so that HTML could be written like

`<a href="/?search=\${query}">click</a>`.template({query:

encodeURIComponent('hope')});

But it's not so cool and footgun prone to remember the \${...} escape. Oh well, not sure how this thread will end up. Maybe there's nothing to do, let's stick with tooling and libraries :-)

# Herby Vojčík (9 years ago)

Andrea Giammarchi wrote:

Yet it doesn't play well with i18n since arguments passed to the IIFE would still come from the current local scope. RegExp would play nicer

Oh, I meant it other way, my dense communication again produce a misunderstanding, I meant something like "well, one can make regexp as well using eval('/'+aString.replace(///g, '\/')+'/') or so, but there still is new Regexp there. So, having string-to-regexp in language, maybe we can as well have string-to-template.

# Claude Pache (9 years ago)

Le 15 sept. 2015 à 14:02, Herby Vojčík <herby at mailbox.sk> a écrit :

Andrea Giammarchi wrote:

Yet it doesn't play well with i18n since arguments passed to the IIFE would still come from the current local scope. RegExp would play nicer

Oh, I meant it other way, my dense communication again produce a misunderstanding, I meant something like "well, one can make regexp as well using eval('/'+aString.replace(///g, '\/' smb:///')+'/') or so, but there still is new Regexp there. So, having string-to-regexp in language, maybe we can as well have string-to-template.

That doesn't make much sense, because regexpes are first-class objects, while template literals are syntax.

The nearest equivalent of the string-to-regexp feature is the string-to-code conversion facility provided by eval and Function.

I have the impression that people want to use features provided by eval, Function or with, but without pronouncing these taboo words. Just use them if you need to: at least you will be clear about what you are really doing.

# Edwin Reynoso (9 years ago)

This seems to be the same thing I posted before as String.substitute() I guess I didn't explain correctly, but ayy I'm glad you guys are discussing this.

# Thomas (9 years ago)

On 16 Sep 2015, at 12:39 AM, Claude Pache <claude.pache at gmail.com> wrote:

That doesn't make much sense, because regexpes are first-class objects, while template literals are syntax.

The nearest equivalent of the string-to-regexp feature is the string-to-code conversion facility provided by eval and Function.

I have the impression that people want to use features provided by eval, Function or with, but without pronouncing these taboo words. Just use them if you need to: at least you will be clear about what you are really doing.

I would like to use a feature that today can only be achieved with eval, with or Function, but those three are hugely overpowered for the job (turning any old string into a template string). The 'taboo' about using eval, with and Function is justified, and it'd be nice to not have to rely upon them.

# Andrea Giammarchi (9 years ago)

I don't think there's any risk in using my initial gist based on Function and with ... really. You should never pass within ${parts} user inputs, you just pas there a variable name, or you reach a property.

var str = 'my ${gist}';
str.template({gist: 'window.alert(123)'});
// my window.alert(123)

The procedure is safe, it uses JSON per each surrounding string, and it won't accept anything dangerous, unless you are the one passing templates with dangerous stuff in them. In few words, you can surely footgun yourself but only by your own, it's your code.

However, if you leave users write template strings for you ... then I believe the problem is not exactly Function but yeah, in such case I would think some better tool that can parse properly template strings upfront so there's no risk at all once already converted.

I have the impression that people want to use features provided by eval, Function or with, but without pronouncing these taboo words.

Yep, "somebody" once mentioned how evil are these things and his book is still around (as well as his linter) ... people are scared just mentioning those features, and use strict deprecating one of them didn't help neither. I still hope there will be soon a replacement for with even in read only, it was so handy in some case.

Best

# Isiah Meadows (9 years ago)

Sent with the wrong subject...

On Tue, Sep 15, 2015 at 7:03 PM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

I think you forgot to change the subject/strip other content/etc. ;)

Plus, I learned the hard way myself that it's easier to have my subscription set to forward everything. It's not a particularly high volume list.

On Mon, Sep 14, 2015 at 12:59 AM, Mohsen Azimi <me at azimi.me> wrote:

I actually used template strings as templateing in two of my projects. You can take aa look here:

mohsen1/json-schema-view-js/blob/master/src/index.js#L59-L175

Two main problems I had with this was:

let o = undefined;
console.log(`${o}`); // => 'undefined'

[omitted]