WICG / webcomponents

Web Components specifications
Other
4.37k stars 371 forks source link

[templates] If HTML Template Instantiation proposal is adopted, consider a HTML-native syntax #695

Closed kethinov closed 6 years ago

kethinov commented 6 years ago

Apple's new proposal (https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md) is intriguing and I think worth supporting. They explicitly do not endorse any specific template syntax though, which is understandable.

The decision as to which template syntax to standardize on is certainly a tough question, given how many competing syntaxes exist.

I think there is a decent case to be made though that an objectively best syntax would be one that is quite unlike most popular templating systems - instead favoring a syntax that is grammatically native to HTML.

Consider something like this (rough) example:

<template>
  <p>hello world</p>
  <if variable>
    <p>{variable}</p>
  </if>
  <else>
    <p>the variable is not populated!</p>
  </else>
</template>

Assuming a JSON model consisting of {"variable": "dynamic data!"} is populated, the template, when parsed, renders as:

<p>hello world</p>
<p>dynamic data!</p>

If the variable were removed from the model, the if statement in the template would return false and the second <p> tag would be rendered by the <else> block instead:

<p>hello world</p>
<p>the variable is not populated!</p>

Imagine also an <include> tag for layout templates and partials which accept arguments via child <arg> elements along with a full suite of flow control tags in addition to <if> and <else> like <unless>, <elseif>, and <elseunless> for basic templating logic, and of course a <loop> tag for looping.

Such a standard as this strikes me as much more in the spirit of HTML than standardizing on any of the popular mainstream templating languages that are out there, specifically because all the others are their own languages whereas a proposal such as this is just new HTML tags.

In fact, I felt so strongly about it that I and a few like-minded others actually prototyped an implementation of exactly this syntax. We are not married to the specific syntactical details or tag names, and our implementation is still super rough around the edges, but you can play with it today, and we think there is real value in a templating syntax that is grammatically compatible with HTML.

Regardless of implementation details or specific tag name choices, it does seem that following the grammar of HTML by sticking to <tags> for flow control and reserving the curly braces to {variables} only is an idea worth considering.

I'm not sure if this is the right place to raise a discussion such as this, or if something similar has been proposed before here or elsewhere, so if this is the wrong place to discuss this, I apologize.

wiredearp commented 6 years ago

I like the spirit of this and would probably choose your prototype implementation over "native templates" if the latter turns out to be just another template language. Or I would perhaps use <p>${actual_javascript}</p> for maximum expressivenes, but I agree that HTML templates should look like HTML to gain the edge. I just wanted to remark that HTML is supposedly case insensitive and so your engine only works because you've implemented a custom parser. You would probably choose attribute values over names.

<if test="caseSensitiveVariable">
  <p text="caseSensitiveVariable"></p>
</if>
<else>
  <p>the variable is not populated!</p>
</else>

I also got rid of the final {curlybraces} like that. But yes, I could see myself choose a simple feature set if the syntax was proportionally simple, so this.

calebdwilliams commented 6 years ago

Wouldn't this have potential impact on future HTML development? Granted, if the tags are semantic enough, usage would be unlikely.

Still, I think it would make more sense to target a specific element tag like <directive> or allow for extra semantics/attributes behind <template>? I could imagine something like this:

<ul>
  <template if="{{someConditionToBeEvaluatedAgainst}}" loop="{{youGetTheIdea}}">
    <li>{{someContent}}</li>
  </template>
</ul>

This seems more in line with the current implementation of HTML and JavaScript interactions.

Edit I would think it goes without saying, but assuming we could do something like this any template element with a directive attribute would need to be recursively imported.

kethinov commented 6 years ago

Regarding the case sensitivity question, we ran into that hiccup while developing our prototype parser. The first implementation literally used DOM parsing to parse the new elements (as HTMLUnknownElements) , so we made a design decision to require all variable names passed down in the model to be case insensitive to match HTML.

When we dropped the DOM parser approach to parsing templates (for performance reasons), we added case sensitivity as a feature, as it is common in other templating systems. Notably, even in the original pure DOM implementation, the case sensitivity only applied to variable names rather than their contents, because in practice most (all?) DOM implementations preserve case on attribute values.

Another problem with our prototype syntax is that it does violate at least one rule of HTML grammar: attributes can only be present once per element. Here's some problematic syntax we support, that HTML does not:

<if blah='something' or blah='somethingElse' or blah='stillSomethingElse'>
  <p>blah is one of those three things</p>
</if>

The above example violates the rules of HTML by allowing the blah attribute to be present three times and the or attribute to be present twice.

My thinking though is if a HTML-inspired syntax ever got adopted as a standard, it might be prudent to make the templating tags exceptions to the case sensitivity rule and the rule requiring an attribute to appear only once per element, since the templating tags are of a fundamentally different character than just marking up data since they are programmatic in nature by offering some features of true programming languages like flow control which would be entirely new to HTML.

Granted, that same logic is sometimes used to support the argument that templating systems should not just be extensions of HTML, but in fact be entirely distinct languages that are easily recognizable as templating languages and not merely the marking up of data semantically. If that design principle wins the day, then I would argue that the templating systems that make the most sense are the ones that are literally just JavaScript syntax moved to the templating layer (e.g. ejs).

The main thing I fear is an entirely new syntax and grammar getting standardized on just for templating. It seems to me that both the existing languages of HTML (my preference) and JavaScript are suitable for doing HTML templating inside of <template> elements. It would be a shame if we standardized on a whole new language when either of those should be sufficient.

caridy commented 6 years ago

I don't think this is an option. Too much ambiguity with HTML semantics, e.g.: are elements siblings? what is the parent-child relationship of elements when they have a <if> in between? Are those new elements inert? Can I use global html attributes on those inert elements? can I query them?

<else> is even more complicated and error prompt. what if there are elements between the if and the else? what if there are comments, or empty nodes? This doesn't really work without adding a bunch of new semantics to the HTML structure.

On the other hand, <template> is already special, and in the future it might be extensible via custom directives. Let's stick to that for anything that is inert.

kethinov commented 6 years ago

The requirement that <else> be a sibling to <if>, <unless>, <elseif>, or <elseunless> is not so different than the requirement that <li> only have other <li> elements as siblings and be direct children of a <ul>.

As to querying the inert elements, I presume they'd be part of the shadow DOM and only valid inside <template> elements, so, as you say, this would only require extending the specialness of <template> rather than all of HTML.

calebdwilliams commented 6 years ago

@kethinov The problem with that is that a browser can effectively swallow those errors and render the content even if they are used in a disallowable fashion. I think it would be a bad idea to let JavaScript do the same.

kethinov commented 6 years ago

I don't follow. Any templating system that gets standardized is going to have to have some sort of handling of syntax errors. What makes an HTML-inspired syntax any different than, say, standardizing on jade or ejs?

wiredearp commented 6 years ago

These could be just ordinary elements that you can style and query and insert anywhere you like without creating syntax errors, provided of course that you can live with the semantics of ordinary elements.

<p class="price">
  <choose>
    <when test="price > 1000">Ouch!</when>
    <when test="price > 100">Good</when>
    <otherwise>Great</otherwise>
  </choose>
</p>

This unnamespaced XSLT would also not throw "syntax errors", but it would of course also not work. The magic only happens when you invoke the API:

mytemplate.createInstance(mydata);

– and this is where the syntax errors can be thrown and catched in JavaScript. The elements are in other words only special to the template processor, or at least that could be the path of least resistance.

Choosing a template syntax is like choosing between tabs and spaces only with an infinite amount of options and of course the option to create your own. It doesn't matter if tabs are true, because the choice is not rational. I have colleagues that prefer React.createElement('div') to <div></div>, so I can only say that it would appeal to my personal feelings if the standardized syntax was to become a mix of plain HTML and DOM0-style inline JavaScript:

<if test="parseInt(user.age, 10) > 21">
  <p>Welcome <span text="user.name.split(' ')[0]"></span>!</p>
</if>

– because that is how it already appears to work out of the box.

<button onclick="document.body.append(usertemplate.createInstance(user));">Enter</button>

– but I am of course also not in charge of global XSS prevention. There is an argument to be made for solving this entirely within the realm of JavaScript, but if the solution should ultimately be sought in the family of "entirely distinct languages that are easily recognizable as templating languages" one could perhaps take inspiration from XQuery+JSONiq [1] since it has both curly braces and smiley faces :)

[1] Chapter 5 in this PDF shows an appropriate example.

justinfagnani commented 6 years ago

I really think that <template> is the right element to use for control flow. <template> is analogous to a function: it's lazy because the contents aren't evaluated at declaration time, and instantiating a template is analogous to calling a function.

In a programming language many constructs might be lazy, function and loop bodies, if and else blocks, the second operands of short-circuiting operations, etc. In HTML we currently only have <template> as a lazy element. It was hard enough to add, kind of difficult to polyfill, and still not all tools have caught up with this pretty major change in how documents are interpreted. Adding more template-like elements would be cause even more disruption there, and all kinds of parsers would have to be updated, and developers might have a harder time understand the growing semantics of HTML.

So I think <template type="if"> is just going to be way more practical than <if>. If we want to add more template-like elements I think we should consider a universal way to mark an element as having inert contents to parsers only have to update once more.

calebdwilliams commented 6 years ago

Yeah, the more I think about it the more template seems right for that sort of thing, it’s already semantically and behaviorally accurate. Also writing a template tag with control flow directives could still be valid HTML and feasibly imported naturally separate from the createInstance method without modifying the HTML spec too much.

vidhill commented 6 years ago

https://github.com/w3c/webcomponents/issues/697

PaulHMason commented 6 years ago

Another option could be extending native elements (templates, in this case). Ironically, Apple vetoed the "is" attribute for extension; I understand the reasoning (somewhat) but this makes more sense to me in this particular context:

<template is="if">
<template is="repeat">

Where each of the templates are template derivatives (could be "native" or custom elements).

calebdwilliams commented 6 years ago

That assumes adoption of [is] which is questionable, otherwise I like the concept.

kethinov commented 6 years ago

One concern there though is verbosity. One of the handy things about having elements like <if>, <else>, <loop>, <include>, etc that are only permitted as children of <template> is this is much more concise while also being easier to read than other concise templating systems like mustache, handlebars, dust, etc.

justinfagnani commented 6 years ago

@kethinov yeah, it might remove the parser change concern if those elements are only allowed in <template>s, but the spec would still have to specialize them as subclasses of <template> because they would have to define an InnerTemplatePart for individual instantiation.

I do like the ergonomics of it, but maybe this is something that can be added over time as tag names without dashes are essentially reserved for the platform. Curious what @rniwa and @domenic think.

domenic commented 6 years ago

I don't think we can add new template-like elements (like if etc.) since that was already a huge undertaking for template.

kethinov commented 6 years ago

Would it be significantly easier to standardize on a string-based templating system like mustache (like in Apple's proposal)?

domenic commented 6 years ago

We need to use the template element for control flow, and a syntax like Apple's proposal for subsitition, since you can substitute inside attributes which can only contain strings.

kethinov commented 6 years ago

I guess what I don't get is why is this from Apple's proposal substantively less difficult to standardize on:

<template type="with-for-each" id="list">
    <ul>
    {{foreach items}}
        <li class={{class}} data-value={{value}}>{{label}}</li>
    {{/foreach}}
    </ul>
</template>

Than something like this?

<template id="list">
  <ul>
    <loop through="items" val="item">
      <li class={item.class} data-value={item.value}>{item.label}</li>
    </loop>
  </ul>
</template>

In both cases, an arbitrary templating syntax is being invented separate and apart from the current HTML spec and only parsed within <template> elements. Why would making the syntax used for that HTML-like or HTML-inspired be significantly more difficult than just using mustache or similar?

domenic commented 6 years ago

The former isn't in Apple's proposal.

Neither are possible.

kethinov commented 6 years ago

Ah you're right. I misread their proposal. They cited that as an example of something that is a desired use case, using mustache syntax just as an example.

Reading it again, for that example, they're actually proposing this:

<template type="with-for-each" id="list">
    <ul>
        <template directive="foreach" expression="items">
            <li class={{class}} data-value={{value}}>{{label}}</li>
        </template>
    </ul>
</template>

Still though, at the start of the proposal they talked about a "standardized template language" and referenced mustache as one possibility among many which seemed to imply to me that they proposed native parsing by the browser of one of the competing templating systems out there. But <template directive="foreach" expression="items"> certainly isn't mustache.

So I guess I'm a bit confused as to what they're proposing if it isn't native mustache parsing (or native parsing of a similar templating syntax) in the browser. What am I missing?

domenic commented 6 years ago

Well, there's a pretty detailed proposal that goes over the details, and even a spec. If you're still having trouble understanding after reading the proposal and walking through the spec, maybe then ask specific questions about the parts you don't understand?

kethinov commented 6 years ago

Is it a correct read of their proposal to say the only changes to HTML itself they're proposing are new attributes to <template> like type, directive, and expression and that the only connection to mustache in the proposal is simply that the example values they gave for those attributes are mustache-inspired (e.g. "foreach") rather than standardizing on mustache syntax itself?

rniwa commented 6 years ago

Yes, we're not proposing to standardize mustache itself. For simple substitution within a template, we would use {{~}} but for looping, conditionals, and other control flows, we're suggesting to use template element instead so the proposal is substantially different from what the mustache library does.

kethinov commented 6 years ago

I see. That is much more narrowly-tailored than I initially thought. Thanks for the clarification.

Given that, I see why my proposal to add new elements (even with the restriction that they are only allowed in template elements) is both beyond the scope of what's being proposed here and also perhaps insurmountably difficult to do anyway as was mentioned earlier by @justinfagnani and @domenic.

Given that, I'll close this issue. Thanks all for unpacking this for me.

As an aside, I didn't know <template> was such a difficult thing to get into the HTML spec. Can anyone link to some reading about why that was the case?

Nashorn commented 5 years ago

I know this thread is closed, but was interesting.

After reading through Apples proposal and the ideas on this thread, my conclusion is XSLT. We have an XSLT processor in the browser already, it's functional and more powerful beyond what we could reinvent. I think the new devs who have never used XSLT, just didn't consider it (?). User passes a JSON {} to template which receives it as serialized XML. From there, XSLT syntax. There is a performance hit i can imagine, but we won't do this when building a game and having to update thousands of sprites, but better employed to render 1 off tables and grids, (plus we paginate, never thousands of rows) article-type content, custom order-like screens or forms. These are less cpu intensive.

The other idea is what we already have, template literals. I don't have to explain this.

3rd, use a templating api like so many of them out there.

Nashorn commented 5 years ago

Also, the idea of calling .update() on object to revalidate templates is overkill, this bleeds into 2-way data binding. I hope this idea is dead because it would destroy the simplicity and beauty of what W3C is doing. We already have solutions for it.

dy commented 3 years ago

May I ask your thoughts on this:

<ul class="todo-list" :for="{{ item in todos }}">
  <li class="todo-item" :if="{{ !item.done }}">{{ item.text }}
</ul>

(it takes best parts of vue, angular, somewhat reminds XSLT, no need for special syntax (svelte/etc-like) - it's compatible with existing HTML) vs current proposal:

<ul class="todo-list">
  <template directive="foreach" expression="todos">
    <template directive="if" expression="!done">
      <li class="todo-item">{{ text }}
    <template>
  </template>
</ul>