WICG / webcomponents

Web Components specifications
Other
4.34k stars 373 forks source link

[templates] How featureful should the default processor be? #682

Open domenic opened 6 years ago

domenic commented 6 years ago

From what I can tell the proposal includes the following features:

There's a lot of potential for scope creep here. I think the most minimal version omits all three of these so it's purely JavaScript property lookup. I'm not sure if that'd be too restrictive, or OK.

Note that Mustache doesn't allow any of these, I am pretty sure, and requires you to manipulate your data into an appropriate format beforehand. (Maybe it supports whitespace stripping?)

rniwa commented 6 years ago

Stripping the leading & trailing HTML whitespace is a feature of template parts themselves. Right now, I don't have a formal spec for the default processor. We're pretty much open to options. The only thing we ask is that we don't spend another six months debating & spec'ing the default processor's capability & semantics by making them too complicated.

caridy commented 6 years ago

I think the most minimal version omits all three of these so it's purely JavaScript property lookup.

I will definitely favor that. Looking at this from the localization point of view, you could have the default message in english, or the key for the label, or both of them combined, or even a complex default ICU or Fluent message inside those {{...}} and the processor will give you the ability to produce the right value. This is a use-case from the far end of the spectrum for simple apps, but definitely important for complex apps.

tclzcja commented 6 years ago

@domenic one concern about the not having {{ x["y"] }} part: what if the property name contains special characters? One case I can think about is i18n, a multi-language property/object may look like:

name["en-AU"] lastname["zh-CN"]

and we can't use dot in that scenario - I mean, assuming name.en-AU is illegal either in the spec.

domenic commented 6 years ago

You can create an object with the appropriate properties, like you would in Handlebars or Mustache.

stramel commented 6 years ago

@domenic As I mentioned in #688 I often find myself needing index + 1 which doesn't appear it would be supported by the proposed feature subset.

domenic commented 6 years ago

Yes, you would need to create an object such as const obj = { number: originalObj.index + 1 } and then use {{number}}. Just like in Handlebars.

justinfagnani commented 6 years ago

I think that keeping expressions in the default processors just simple keys will be the best for the first version. Even simple expressions like a.b or a[b] will end up having to define semantics that might not match JavaScript, or JavaScript best practice idioms of any given time, so we'd end up with a not JavaScript, but JavaScript like, expression language in the platform.

Ultimately I think we want to find a way to somewhat safely allow real JavaScript in the expressions with a built-in processor, and until then I think user-space processors can explore the feature space here pretty well.

FWIW - since this proposal is very much like the internals of lit-html - when lit-html produces an HTML template it doesn't put any expressions inside the delimiter, they're all just {{}} and associated with values from the JS template by order. I think this might end up being a very common approach, where the expression are empty, or have a generated key used to lookup in another structure produced from the actual expressions that the developers write.

rniwa commented 6 years ago

I think a.b is fine because we can define that as regular [[Get]] on a. What's challenging for + 1, a["b"], etc... is the parsing of it needs to match ES2017 or whatever.

domenic commented 6 years ago

The parsing for property references (a.b) is nontrivial and also needs to match the JS spec. Think about Unicode escapes, which characters are allowed as the first one after the dot, etc.

rniwa commented 6 years ago

@domenic : We can just limit to ASCII literals.

domenic commented 6 years ago

That doesn't match the JS spec

rniwa commented 6 years ago

@domenic : Obviously not. But we don't have to support the full JS spec as long as it's a strict subset (and I know not all ASCII characters aren't allowed in property list, etc...). For that matter, we wouldn't match the spec regardless of what we spec. Even if we just supported {{~}}, we would have differences to the real JS behavior due to escaping, etc...

rniwa commented 6 years ago

I'm going to say that we're gonna insist pretty hard on supporting dot syntax because it's going to be very important for our declarative custom elements syntax.

domenic commented 6 years ago

Ok, we can discuss this point of contention further in person. We'd prefer that to not be in the default processor. (Edit: but, are looking forward to working together and especially seeing the integration you allude to, which will surely add to the discussion.)

caridy commented 6 years ago

@rniwa

it's going to be very important for our declarative custom elements syntax.

I'm very curious about this use-case. I might be having the wrong intuition here, but a declarative custom element syntax + the constrains imposed by HTML attributes (string values), how is that accessing property members will become an important use-case for declarative declarations? When do you plan to share the declarative syntax proposal?

rniwa commented 6 years ago

@caridy : Unfortunately, I'm behind on finalizing the proposal. I was hoping to post it by now but we've found a couple of design flows so we're going to do some reconciliation. We'll try to post it by the web components discussions on Friday at TPAC.

justinfagnani commented 6 years ago

@rniwa I think having a brief summary as early as possible would help get us thinking about it before Friday.

maxnordlund commented 6 years ago

I imagine this would work like the template strings, so by default any JavaScript expression would be allowed, and by "prefixing" aka choosing which processor to run allow for anything else.

But that's a deep rabbit hole which is not very nice to dive into. What about not specifying a default processor to begin with and let the community try out difference sub-languages?

Kind like pythons format strings, and how date can implement its own language.

tomalec commented 6 years ago

Speaking of "path syntax" - which would require separate definition, I believe.

Why can't we use JSON Pointer (RFC 6901) it already covers all the rules for escaping, characters allowed by JS, etc.

The downside of that would be the syntax for expressions that would look alien {{foo(/a/b, /c/d/e)}}, but expressions and dot-syntax could be delegated to libraries and frameworks.

But we would not have to bother about specing it and re-inventing the wheel.

domenic commented 6 years ago

Resolved on simplest option for now: https://tc39.github.io/ecma262/#prod-IdentifierName (not https://tc39.github.io/ecma262/#prod-Identifier).

Error-handling behavior: we have options, either throw, output empty string, or just output the curlies as-is. We all want to be forward-compatible with future expansion of the default processor.

Whitespace stripping remains as currently proposed, as a feature of the template parts, not related to the processor.

glen-84 commented 6 years ago

Have you considered using Jinja/Twig syntax for conditionals and loops?

{% if condition %}
    <span>{{ val }}</span>
{% endif %}
{% for user in users %}
    * {{ user.name }}
{% else %}
    No users have been found.
{% endfor %}

The <template directive="foreach" expression="items"> syntax seems really verbose to me, and the Handlebars syntax is aesthetically unpleasing in my opinion.

It might also help to distinguish between expression output and control structures in this way.

justinfagnani commented 6 years ago

@glen-84 I suspect that would add a lot of complexity to the parser, making it build a tree structure out of something other than Nodes. By using elements for directives is that the parser can reusing element parsing and tree-building. <template> is really the right element for this too, as it's only potential DOM, and to be interpreted by some other mechanism, in this case the default template processor.

glen-84 commented 6 years ago

Can it at least be made less verbose?

Aurelia:

<template repeat.for="item of items">

Vue:

<template v-for="item in items">
dy commented 3 years ago

Are literals supported? Eg. <p>Hello, {{ user || 'guest' }}.

Also − possible syntax for loops, conditionals:

<ul class="todo-list">
  <li class="todo-item" item:each-of="{{ items }}" :if-not="{{ item.done }}">{{ item.text }}
</ul>
ByteEater-pl commented 3 years ago

In my opinion JsRender is worth a look. It has Mustache style syntax, so familiar to many and aligned with most of what's been discussed for [templates]. It also has a judiciously selected and tuned span of functionality. Its developer Boris Moore originally wanted it to be a templating plugin for jQuery, it was created and contributed by Microsoft, but when it was decided not to move on with it, he split it into a separate project, independent of jQuery. So it seems very well suited for the goal at hand.

taylor-hunt-kr commented 3 years ago

We could compromise by reviving Netscape 4’s JavaScript Entities, which would appeal to everyone the same amount (i.e. not at all):

<template>
  <time datetime="&{input.iso8601};">&{input.forHumans};</time>
</template>

More seriously, are there specific features we want to avoid from the last time declarative templating was added to the Web in XSLT?

<xsl:template>
<xsl:for-each select="items">
  <xsl:choose>
    <xsl:when test="self.done"/> <!-- forgive my bad XPath -->
    <xsl:otherwise>
      <xsl:value-of select="item.text"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:for-each>
</xsl:template>

I assume the verbosity is undesirable, but accumulated knowledge from implementing/working with it seems like it would usefully inform how powerful this feature should be.

Additionally, the other existing API to compete with on convenience is one we don’t want to look more appealing:

<template>
  <ul class="todo-list">
    <script>
      items.forEach(item => if (!item.done) document.write(`<li class="todo-item">${item.text}</li>`))
    </script>
  </ul>
</template>