Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[templates] Template proposal essentials (shave the mustache) #739

Closed
yuri-karadzhov opened this issue Feb 20, 2018 · 20 comments
Closed

[templates] Template proposal essentials (shave the mustache) #739

yuri-karadzhov opened this issue Feb 20, 2018 · 20 comments

Comments

@yuri-karadzhov
Copy link

yuri-karadzhov commented Feb 20, 2018

Do we really need the mustache syntax?

Having a template string in JavaScript allow us to inject values into specific parts of a string:

`word1 ${executedValue1} word2 ${executedValue2} ...`

It appears that the essence of html templates are very similar.

A mustache syntax is powerful, yet it is not descriptive enough (people already mentioned that in other issues e.g. #704). So the question is, should we have a template syntax at all? Taking into account that we can use function results in mustache templates as well as nested templates, means that we can avoid using any template syntax and just have a placeholder like we do in template string (the logic can be moved inside a function)

<div>
  <h1>Hello {{name}}</h1>
  {{^name}} Wellcome! {{/name}}
<div>

Which can be rewritten:

<div>
  <h1>Hello $$</h1>
  $$
<div>

The placeholder ($$) can be anything, but the point is that we don't have any logic in the template at all, and we can transform this template (string) into something very familiar:

const strings = text.split('$$'); // Place your favourite separator here
// ['<div>/n  <h1>Hello ', '</h1>/n  ', '/n</div>']

The result is exactly the same as in tagged template string. It is native, performant, straightforward and we don't need anything new (no new syntax, no parser, nor anything else). But now, there are some parts that should be improved (and this is the only part that should be added to the spec), i.e. we need to turn this array of strings into a DocumentFragment and an object that holds the array of context for the dynamic parts (values).

DocumentFragment representation:

<div>
  <h1></h1>
<div>

and

Parts = {
  update(instance, ...values) {
    this.context.forEach((context, index) => context.update(instance, values(index)));
  },
  context: [nodeContext1, nodeContext2]
}

Where nodeContext1 and nodeContext2 are placeholders that hold the values and have an update method that updates an appropriate part of the DocumentFragment.

nodeContext1 = {
   type: 'node', // There should be different types of context: node, slot, attribute, class, style and may be more
   value: '',
   update(instance, value) {
     this.dispatchEvent(new CustomEvent('contextupdate', {
       detail: {
         type,
         oldValue: this.value,
         newValue: value,
         part: this
       }
     })); // Update context callback
     this.value = value;
     instance.querySelector('h1').innerHTML = this.value; // it should be something more reliable based on a placeholder position in the array
  }
}

Having a regular DocumentFragment and Parts object, it is possible to update Fragment with a new data:

Parts.update(instance, ...values); // update DOM instance with values

Moreover, we can add complex logic inside javascript template strings like below:

const instance = html`
  <div>
    <h1>${list.length ? list.length : ''}</h1>
  </div>
`;

Libraries like lit-html uses similar mechanism under the hood as well.

This way, we do not introduce any new syntax and solve the basic issue that can fit both html templates and template strings. Template logic can go to the template strings rather than the template itself (instead of {{#loop}} we can use native js). Having this bare minimum native implementation of templates, allows us to extend the power of templates with 3rd party libraries later.

@domenic
Copy link
Collaborator

domenic commented Feb 20, 2018

You can use {{}} with nothing inside it if you don't want to give your template parts descriptive names. It'll then work exactly as you're describing.

@yuri-karadzhov
Copy link
Author

yuri-karadzhov commented Feb 20, 2018

Sure, it could be any placeholder, the point is - we don't need to introduce any logic or syntax and the only thing that should be added to spec is ability to transform array of strings (same array that you will get from template string) into DocumentFragment and parts.

@domenic
Copy link
Collaborator

domenic commented Feb 20, 2018

I'm saying the proposed spec already covers your needs.

@yuri-karadzhov
Copy link
Author

I'm saying it covers much more then we all need. It introduces new syntax and some logic in temlpates that is not enough to write descriptive templates and can be moved to js template strings.

This issue propose to introduce less significant changes to a spec: Transform array of strings into a document fragment and parts, nothing more (no mustache, no template changes)

This is the only things that we really need the rest is already there because of template strings.

@domenic
Copy link
Collaborator

domenic commented Feb 20, 2018

OK. You can use the subset of the spec that you're interested in though; we'll continue exploring the rest of the spec for other peoples' use cases which go beyond your own.

@yuri-karadzhov
Copy link
Author

I'm not talking about my personal needs, I'm talking about not to implement something that you and other developers do not need, because it is already there.

@yuri-karadzhov
Copy link
Author

I will schedule a call to @rniwa, may be he will understand my point better. I will repeat it once again - logic can be moved from the template itself to a template string, the only thing that specification is missing is ability to transform array of strings into a DocumentFragment and parts. The rest is already there or could be implemented with zero efforts.

@rniwa
Copy link
Collaborator

rniwa commented Feb 21, 2018

First off, I'd much prefer discussion this over Github or email threads.

I understand what you're saying but this approach tends to break down when if you have nested templates. There, you need to be able to recognize inner templates as separate entities while parsing $$ and ignore any $$ inside inner template elements. As such, a simple string replacement wouldn't work.

@yuri-karadzhov
Copy link
Author

Let's discuss this part here.

It would not break down with nesting templates or logic. Symbol $$ should not be a part of the spec (we don't want to introduce any new syntax), it was just an example how developer can write flat templates and turn them into array of strings.

We won't have simple string replacement, rather we will have a mechanism (based on build in HTML parser) that turn that array of strings into DocumentFragment and Parts object.

So if you ask where all the logic and nesting should go then? The answer is to javascript through template strings e.g.

const template = html`
  <div>
    <h1>${name}</h1>
    ${name ? html`<h2>Wellcome ${name}!</h2>` : ''}
  </div>
`

As you see we have nesting template and if logic here and html tag that will receive that array of strings and turn it into a DocumentFragment and Parts object.

More specifically the inner template will receive plain (string) name value as a part value, while the outer template will receive inner template as a part value. So there is no issues when we are substituting string or DocumentFragment into a template (very naive implementation could use innerHTML as in example of the original post, smarter - will take care of context, but it is still very native to substitute DocumentFragment into another DocumentFragment).

This way we might have very descriptive templates with all familiar js syntax, while keeping proposal small. As a result we will be able to write templates in js (with template strings) and html (developer will introduce a preferred separator and have only plain template that he will split into array of strings, more complex template logic and composition should be done in js part through the template strings)

@yuri-karadzhov
Copy link
Author

Moreover, let's say that html tag from the example above are being part of the standard, then having flat html templates

<div>
  <h1>$$</h1>
  $$
</div>

and

<h2>Wellcome $$!</h2>

We can compose them into complex template with logic this way

const SEPARATOR = '$$';
const template = html(tpl1.split(SEPARATOR), name, name ? html(tpl2.split(SEPARATOR), name) : '');

Well it is working but does not look quite good, but there is a better way to say the same, we can define a template like this

<div>
    <h1>${name}</h1>
    ${name ? html`<h2>Wellcome ${name}!</h2>` : ''}
</div>

And then instantiate it this way (it is dirty, but having scoped eval and native implementation will work better)

const template = eval('html`' + tpl + '`');

@rniwa
Copy link
Collaborator

rniwa commented Feb 23, 2018

Nesting template literal within template literal like that is too cumbersome for many use cases. Also, we certainly don't want to be encouraging the use of eval.

@yuri-karadzhov
Copy link
Author

Well it is the matter of preferences, you can always wrap it to a function and hide nesting complexity. The good part - it is well known, flexible and valid JavaScript, you need to change very little in spec and you will get full power of templates vs you will add a lot and got mustache templates.

Eval was example of very quick and dirty polyfill. It is not required and you can parse the same expression without using eval or entroduce better native way to construct template strings from regular strings.

@rniwa
Copy link
Collaborator

rniwa commented Feb 24, 2018

Well, one of the requirements for this template API is that it needs to work for declarative custom elements. I don't think what you're proposing would be able to satisfy that use case.

@yuri-karadzhov
Copy link
Author

Why? The example above is declarative.

@rniwa
Copy link
Collaborator

rniwa commented Feb 24, 2018

What example? I don't see any example which doesn't involve scripts here. See https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Declarative-Custom-Elements-Strawman.md for how the current proposal does this.

What you're proposing would require ordering of properties to substitute $$ or whatever delimiter is, and doesn't allow name-based referencing. This alone is a show stopper in declarative custom elements since you'd have to remember which argument corresponds to which part based on ordering. In the world where a template is defined away from where it's used having to remember what n-th argument means in the template poses a serious if not fatal developer ergonomics issue.

@yuri-karadzhov
Copy link
Author

I’m talking about the example above that does include template strings (sorry, I am far from computer to copy it). Instantiation with scoped eval or other parser might be internal mechanism. So you just put template strings with html tag into your html template.

@yuri-karadzhov
Copy link
Author

My point is, having basic mechanism that turn array of strings into a DOM and Parts e.g.
['<div class="', "">", "</div>"] -> DOM(<div></div>), Parts
we can easily move to having a tag that turn template strings into a DOM and parts (saving result in a cache)

html`
  <div class="${name}">
    ${name ? html`<div>${value}</div>` : ''}
</div>` -> DOM(<div></div>), Parts

and from this point we can move to declarative templates

<div class="${name}">
  ${name ? html`<div>${value}</div>` : ''`}
</div>

@rniwa
Copy link
Collaborator

rniwa commented Feb 24, 2018

That is not decralative. We can't have evaluation of a string literal in the middle of a template.

@yuri-karadzhov
Copy link
Author

We don't need to have immediate evaluation. Evaluation can be delayed until actual instantiation.

@rniwa
Copy link
Collaborator

rniwa commented Feb 25, 2018

No, that won't work because we can't execute arbitrary scripts like that from template at least in v1. That would either not run under CSP's eval restriction, or we'd need to invent a new CSP option.

I feel like we're not going anywhere on this. All I can say at this point is that we've already considers what you're proposing here, and it doesn't satisfy all the requirements we have.

@rniwa rniwa closed this as completed Feb 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants