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

[declarative-custom-elements] Capabilities needed and open questions #1009

Open
justinfagnani opened this issue Apr 25, 2023 · 28 comments
Open

Comments

@justinfagnani
Copy link
Contributor

justinfagnani commented Apr 25, 2023

At the Spring 2023 F2F we talked about the need to discover all the open questions and dependencies on other specs that declarative custom elements have, in order to use declarative custom elements to help discover, prioritize other work.

Here are some questions and spec dependencies I can think of:

Questions

  • Declaration
    • How are attributes and properties declared?
      • How are attributes reflected to properties? Do attributes have converters for numbers, boolean, enums, etc?
    • How are styles declared?
      • Are they part of the template or can they be shared and adopted?
      • Can they be external? Or bundled and referenced from a nested sheet?
    • Are registrations always global, or can they be scoped?
      • If scoped, does the declaration establish an id that can be used to define the element in a scope?
      • If scoped, do we need a way to define all the child definitions that might be used?
    • How do you set ShadowRootInit options like mode and delegates focus?
  • Scripting
    • Do declarative custom elements run with JavaScript disabled?
    • Will HTML Modules be importable from HTML with JavaScript disabled?
    • What is the base class for a declarative custom element? Does it directly extend HTMLElement, or is there another class with shared behavior?
    • Can a declarative custom element be enhanced with script?
      • How is that script associated with the element?
      • Must the script be inline, or can it be bundled?
      • Can the script be loaded late? Is there an upgrade-like step when it does?
  • Lifecycle
    • How do elements detect changes to data?
    • When do element re-render? Synchronously or asynchronously?
      • If synchronously, how do you handle data-consistency? ie <img src="/avatars/{{lastName}}/{{firstName}}.jpg"> will make erroneous requests if firstName and lastName are set at the same time.
      • If asynchronous, on what timing? Microtask? Is there an API for waiting for an element to update?
  • Templates
    • What are the semantics of template updates?
      • Is static DOM guaranteed to not be updated when re-rendering the same template?
      • Are bindings dirty-checked?
        • If so, do we need an opt-out for binding to stateful properties like input.value?
      • If a new template (ie from a different branch in a conditional) is rendered, does it clear the existing DOM?
    • How are event listeners added in templates?
      • Do event listeners make sense in declarative custom elements? What kinds of things could they do declaratively ? Fire another event? Assign a property?
      • Or does event handling require augmenting declarative definitions with script?
    • How are properties set on elements in templates?
      • How do you set mixedCase property names?
    • How are conditional and looping handled in templates?
      • Is there a keyed looping construct that reorders DOM?
    • Can templates be shared? Is there a sub-template "call" operation and a named template declaration?
    • What is the expression language for bindings? Is it a subset of JS?
      • Does it support literals? Array literals?
      • Does it support optional-chaining and nullish coalescing?
      • Can expressions contain method calls? If not, does the scope for expressions need a wrapper over some of the DOM to turn methods into getters? (ie this.getAttribute('name') -> this.attributes.name)
    • What is the data model available to to expressions?
      • Is the scope something like class scope where you have the globals and this?
    • What happens if expressions throw?
    • Is there a way to remove an attribute that has bindings? ie <img src="/foo/{{bar}}.jpg"> when bar is null
  • Security
    • Do declarative custom elements introduce a script gadget vulnerability?
      • Do they only run when script would have run? ie, not from innerHTML?
      • Are they inline script for CSP?
      • Do they support nonces?
    • Is expression evaluation sanitized?
      • Do we denylist things like window.location which can be used to exfiltrate data in bindings?
      • Do we denylist the Function constructor which can be used to eval code?
    • Are bindings in text positions always escaped?
      • If so, is there an unsafe operation for inserting HTML as a string?
    • Can bindings to attributes do contextual autoescaping? (disallow onclick="javascript:...")
  • Can ElementInternals properties be set?
    • How do you define ARIA properties?
    • How can an element be a FACE?
    • How can ARIA associations be made across children shadow boundaries?
  • SSR
    • How do declarative elements handle an existing shadow root from declarative shadow DOM (hydration)?
    • Do we need a deferred hydration system to ensure top-down initialization?
    • Depending on how template updates work, do we need a system for marking what template SSR'ed a chunk of DOM so that we can perform an update on hydration instead of a replace?

I'm sure there are many more questions than this, of course.

Probable Spec Dependencies

  • DOM Parts
  • Template Instation
  • Expressions
  • Declarative ARIA
  • Hydration
  • Declarative CSS Modules
  • ...

Use cases

Many of these questions need to be driven from use cases. We'll need to know what kind of elements we're trying to allow to be built. One of the most import questions there whether interactive elements are in scope. For example, can you build the basic counter element that's a common framework example? That alone requires event listeners and self-updating state.

@justinfagnani justinfagnani changed the title [declarate-custom-elements] Capabilities needed and open questions [declarative-custom-elements] Capabilities needed and open questions Apr 25, 2023
@bahrus
Copy link

bahrus commented Apr 26, 2023

This is a nice and thorough list. Is this issue meant to serve only as snapshot in time of open questions, or an (evolving) index of supporting issues/proposals, or is it (also) to serve as a place to discuss possible solutions/pose other questions?

@justinfagnani
Copy link
Contributor Author

We definitely need to collect more open questions. I'm not claiming this list is complete.

@bahrus
Copy link

bahrus commented Apr 27, 2023

To the SSR category I would add:

How much (if any) of the SSR/SSG generated element(s) can be optionally used to define the custom element -- can the rendered DOM be used to take a snapshot and turn it into the template? If so, does it make sense to standardize some markers (like processing instructions) indicating things that can be removed/added during that process?

@justinfagnani
Copy link
Contributor Author

justinfagnani commented Apr 27, 2023

@bahrus I'm not completely following. I think this is covered by the hydration question, no? Is it just more specifics there on how hydration is achieved?

@bahrus
Copy link

bahrus commented Apr 27, 2023

Maybe.

When I look at some of the proposals for what declarative custom elements might look like:

<definition name="percentage-bar">
    <template shadowmode="closed">
        <div>...</div>
        <style>/*...*/</style>
    </template>
    <script type=module>
        export default class MyEl extends HTMLElement { /*...*/ }
    </script>
</definition>

... it's unclear to me whether that definition is intended to serve dual roles, or a single role. Dual roles would be not only does it define the structure of the custom element, but it would also be rendered directly in the live DOM tree as one instance of the custom element.

A single role would mean that the SSR generated content would automatically become ShadowDOM(ed) (already possible today in Chrome and Safari), but without that separate definition tag added somewhere, it would remain static HTML, and not morph into a web component.

I make that distinction clear (in my mind) here. Examples 1,2, 4, 5, the defining element serves dual roles. Example 3, that uses a template to define the element, doesn't. Example 3 has fewer parts to resolve, as there's no issue with how to construct the template for repeated renderings, for starters.

It's not clear to me whether I'm the only one who thinks serving dual roles is something worth pursuing, or if everyone is assuming that that goes without saying?

@justinfagnani
Copy link
Contributor Author

Definitions are just that - only definitions. They are not instances. Your example would only create an instance when there's a <percentage-bar> element.

<template shadowrootmode> does not belong inside a definition. Shadow roots are for instances. A definition would have a plain <template> (possibly with a new attribute opting into the expression syntax).

@bahrus
Copy link

bahrus commented Apr 27, 2023

That's what I thought, looking at that example of the percentage-bar. I guess, then, I'm the only one who thinks it is worth pursuing making the first instance also serve as a definition, as the second link I provide demonstrates.

Does my open question make sense to you now? I think you are saying no, by definition, the definition should not also be an instance. I.e. my open question has been opened and shut :-). I agree taking the stance you are taking simplifies things. Maybe I can reraise the possibility once that's accomplished (baby steps).

@justinfagnani
Copy link
Contributor Author

I personally think there should be a clear line between definitions and instances.

Making an instance also a definition raises all kinds of issues around expressions, conditionals, and control flow. They might be solvable, but I believe if they are that it could be added later. It could also probably be done in user-land by patching up the element definition based on the first instance's DOM.

@matthewp
Copy link

What is the use-case for having a definition also be an instance? This would be quite unexpected to me personally. It also makes rendering complicated. It's easy to add a definition at the top of the body tag. If it were an instance I would need to keep track of how many times a tag has been rendered and then do some special rendering so that the definition is added the first time and then never again.

@bahrus
Copy link

bahrus commented Apr 27, 2023

There are some web components that may contain quite a bit of HTML, and aren't even guaranteed to appear more than once on the page.

Consider how much html is behind this period table, for example. Expand the pug to generate the full html to see what I mean. Other examples: Calendars, Calculators, Chessboards.

So not supporting this would double the payload, for a single instance, especially if there's no dynamic logic to generate the HTML.

I also think it makes quite a bit of sense when writing to HTML to "define" the HTML markup for something, previewing what you are generating as you type, then give it a name, and reuse it, similar to defining a variable when coding. That's what my POC was meant to convey.

@bahrus
Copy link

bahrus commented Apr 27, 2023

Another example would be the hamburger menu, which might likely be 1% JavaScript, 10% HTML, 89% CSS. We know we will need one instance for the top menu. We might, later, need to reuse the menuing capabilities it provides by some pieces of the page, lazily loaded. Again, seems like we are doubling the payload to accommodate complex scenarios (lots of dynamic content), at the expense of simple, static-ish scenarios. I'm perfectly fine doing one first, holding off on the other, but I just wanted to provide my use cases (there may be others).

@matthewp
Copy link

I don't understand how this doubles the payload. You would define all of the HTML in the <definition> tag and then when you use it it's just <periodic-table></periodic-table>. Having the definition first means you don't need to include the DSD (correct me if I'm wrong here @justinfagnani).

@EisenbergEffect
Copy link
Contributor

That should be correct. A declarative element would have a template with some sort of binding syntax in it so that the instances used afterwards simply render using the template declared as part of the original definition.

@bahrus
Copy link

bahrus commented Apr 28, 2023

In the context of streaming declarative ShadowDOM (which is the context in which I raised the issue) you would have the definition plus at least one instance rendered to by the server.

@bahrus
Copy link

bahrus commented Apr 28, 2023

If we either:

  1. Move the binding out of the template, like what is done with trans-render or possibly the corset libraries, that's one way to avoid needing to pass the template instance (for simple to rather complex scenarios) down. (I am struggling with the proper syntax to account for certain interpolating scenarios, between a closing tag and opening tag).
  2. For inline binding, I think it would possible, at least for simple to moderately complex streaming declarative ShadowDOM, to reverse engineer the output to infer what the template should look like with inline binding, especially if that (future?) requirement is considered when formulating what the processing instructions should look like.

@justinfagnani
Copy link
Contributor Author

Can we open a new issue for this discussion?

@EisenbergEffect
Copy link
Contributor

@justinfagnani In creating this issue, did you review/aggregate everything from the TPAC2022 report? If not, I can go over that and find anything that's missing. Just wanted to check first. Let me know.

@justinfagnani
Copy link
Contributor Author

@EisenbergEffect I built this list myself. I'm not sure we ever covered such a list of requirements for declarative custom elements in WCCG. If you know of anything missing, I'll add it.

@EisenbergEffect
Copy link
Contributor

@justinfagnani I'll do a quick review of what I have on my list and if I find anything not covered I'll drop it here. Adding this to my todo list. Probably will be a week or so before I get around to it.

@matthewp
Copy link

The templating approach seems to me to be incompatible with server language agnosticism. This is a fundamental feature of the web that we should preserve at all costs. Instead of approach DOM manipulation from the perspective of templating; which inevitably leads towards "isomorphism" (the attempt to have the exact same code run in server and client), we should look to CSS which already successfully enhances HTML regardless of what backend technology was used to create it.

@justinfagnani
Copy link
Contributor Author

The templating approach seems to me to be incompatible with server language agnosticism.

I don't agree with this statement at all.

@matthewp
Copy link

How does one write a DCE on the server in Python and then enhance in JS on the client without writing the same thing twice?

@justinfagnani
Copy link
Contributor Author

There are two options:

One, you write the DCE in the HTML served by your Python or Ruby or whatever server, and you use the component definition as a macro by reusing the custom element tag you just defined.

The other is to implement deep-SSR by implementing the standardize HTML template system and expression language in your server language of choice. I would absolutely expect to see HTML template evaluators in the major programming languages - in fact, why would you expect not to?

@matthewp
Copy link

It's more than just reusing the template, you need to reuse the logic used to turn the template into HTML. Otherwise you are going to repeat the logic.

Client templates are a subset of the same server templates. For example say you have a template of:

<div>{a} <span>{b}</span> {c}</div>

Where only b changes in the client. Because it's in the same template you need to use the same logic to generic a and c as well. This is, of course, wasteful, as these values do not change. It also forces you to write the same logic twice, in both languages.

Since no one wants to write the same code twice, in practice almost no one actually does this. Instead they choose JS and run the same code in both environments.

@justinfagnani
Copy link
Contributor Author

I think this is creating noise for this issue.

If you think that WICG should not pursue declarative custom elements and/or template instantiation, I suggest that you open an separate issue stating your argument.

@matthewp
Copy link

There's a section in this document about SSR that presumes the common JS-only pattern of hydration. This is the part I'm objecting to. This is the only issue where I've seen that proposed. If you want to make template instantiation a DOM API only and not touch light DOM or DSD then I have less of a problem with it.

@justinfagnani
Copy link
Contributor Author

@matthewp There are open questions that I've listed under the SSR section as a hopefully helpful way of organizing things. These have no answers proposed for them here as that is not the purpose of this issue. If you have a new open question, I'd be happy to add it to the list.

If you have an proposed answer to a specific question, I suggest opening a new issue for that.

For instance, from your comments, I suspect you might have an opinion on:

How do declarative elements handle an existing shadow root from declarative shadow DOM (hydration)?

If you want to propose an answer, like: the element should crash and fail to render because attachShadow() will throw, or: the element should simply overwrite any server-generated content, feel free to do so.

@sashafirsov
Copy link

I would try to give a list for most generic and essential questions. Only upon those answered, the full list has a sence.

  • should DCE provide completely declarative programming capabilities to build web app, i.e. declarative web app?
    • if so, running with JS disabled is answered: yes.
    • if so, should template be functionally complete is answered: yes.
    • if so, should DCE support the modular development? is answered: yes. along with:
      • DCE loaded from page and declared inline and from external URL
      • WC lib with scoped custom registry + tag maps + import maps, compatible with CDN deployment
  • should DCE be effect-free? I.e. its content should be not able to change anything in container.
    • if so, a bunch of perks for implementers like parallel load and rendering including streaming all the way load-parse-render-diff-merge.
    • if so, there is a need to sync some state with the parent
  • should DCE state be serialize-able and persisted inline?
    • if so, full hydration support from SSR to off-screen render

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants