WICG / webcomponents

Web Components specifications
Other
4.36k stars 370 forks source link

Interaction between imperative slotting and declarative shadow DOM #967

Open rniwa opened 2 years ago

rniwa commented 2 years ago

It's unclear how imperative slotting would work with declarative shadow DOM. Presumably, slot needs to remain empty until scripts become available.

I wonder if there is a way to serialize imperative slot API though. If there was a way to express imperative assignment using, for example, IDs, that would pave a way for declarative shadow DOM to avoid showing slot's fallback content while scripts are getting loaded.

justinfagnani commented 2 years ago

There is an impedance mismatch here, somewhat similar to scoped registries, but more tractable to a purely declarative solution...

I think, like proposed for scoped registries, the slotAssignment option can be represented as an attribute on <template shadowroot to at the very lease keep the fallback content from showing until script takes over.

I wonder if there is a way to serialize imperative slot API though

The naive thing, and maybe even quite practical in SSR systems, is to write the slot attribute back onto the children - but we obviously don't want to modify children and don't have a way to switch from declarative to imperative slotting. Children don't have to have IDs, but element ordering should be pretty stable to use for SSR, so maybe <slot> can have an attribute that's the index of the child nodes assigned to it? This could allow text nodes to be assigned too.

sashafirsov commented 2 years ago

The hydration lifecycle which is now under development on libs level and preparing for proposal would give a proper concept on interaction of DSD and materializing its content. Rather trying to make an insulated niche DSD opinionated decision, perhaps better to adopt more generic pattern and make DSD capable and compliant with its requirements?

The hydrate option reflects the exact timing when it has a sense to apply as the slots as associated JS API.

Scripts availability also meant to be the subject for hydration, sometimes materialized further in component life cycle. Hence better not to make a decision instead of page developers. Let it be, let it be, let it be 🧑‍🎤

rniwa commented 1 year ago

@sashafirsov : I don't really follow what you're proposing. Are you saying that we should come up with a generic hydration mechanism for custom elements? That seems like an orthogonal issue. This issue is about how declarative shadow DOM can specify the slot assignment mode, which is determined at the time of shadow root creation and not dynamically changeable.

rniwa commented 1 year ago

@justinfagnani : So I guess a proposal is something like this:

<custom-element>
    <template shadowroot="closed" slotassignment="manual">
        <slot initialassignment="0"></slot>, <slot initialassignment="2 3"></slot>
    </template>
    <custom-child>hello</custom-chid>
    <custom-child>world</custom-chid>
    <custom-child>shadow</custom-chid>
    <custom-child>DOM</custom-chid>
</custom-element>

would yield: "hello, shadow DOM"

justinfagnani commented 1 year ago

@rniwa that seems about right.

Would the index have to be into the child list, not just the child element list, though? So initialassignment="0" would be the text node with just whitespace before the first <custom-child>?

rniwa commented 1 year ago

Hm... I guess so. So the above example will be more like this:

<custom-element>
    <template shadowroot="closed" slotassignment="manual">
        <slot initialassignment="1"></slot>, <slot initialassignment="5 7"></slot>
    </template>
    <custom-child>hello</custom-chid>
    <custom-child>world</custom-chid>
    <custom-child>shadow</custom-chid>
    <custom-child>DOM</custom-chid>
</custom-element>
justinfagnani commented 1 year ago

The unfortunate bit there is the sensitivity to whitespace. I wonder if, similar to assignedNodes() vs assignedElement(), if we'd want two attributes so that if you only want to slot elements it's not sensitive to whitespace.

<custom-element>
    <template shadowroot="closed" slotassignment="manual">
        <slot initialelements="0"></slot>, <slot initialnodes="5 7"></slot>
    </template>
    <custom-child>hello</custom-chid>
    <custom-child>world</custom-chid>
    <custom-child>shadow</custom-chid>
    <custom-child>DOM</custom-chid>
</custom-element>
rniwa commented 1 year ago

@justinfagnani : That works. We should also make us skip any non-digit tokens separated by spaces so that we can extend it in the future (e.g. mixing IDs, supporting non-direct child, etc...).

annevk commented 1 year ago

@mfreed7 @yuzhe-han @smaug---- are you following this?

Nit: should we prefix all attributes on template with shadow?

@rniwa makes a good point that if we want to extend this in the future we should think carefully about the processing model. In particular if we want to allow new things with fallback.

bathos commented 1 year ago

The unfortunate bit there is the sensitivity to whitespace. I wonder if, similar to assignedNodes() vs assignedElement(), if we'd want two attributes so that if you only want to slot elements it's not sensitive to whitespace.

My impression had been that declarative shadow’s use cases were tied up with some notion of producing/taking a “snapshot” which would likely not be maintainable without some kind of generative tooling (e.g. build tools if static or runtime libraries that don’t permit naive string concatenation if dynamic SSR). Since the concern expressed here seems to regard ergonomics, it made me wonder if there are additional motivating use cases where authors would be writing declarative shadows directly?

(Idea seems fine to me, just trying to get a sense of how much this is meant to be an “author feature” vs (metaphorically) “html bytecode”).

sashafirsov commented 1 year ago

@rniwa

Are you saying that we should come up with a generic hydration mechanism for custom elements? That seems like an orthogonal issue. This issue is about how declarative shadow DOM can specify the slot assignment mode, which is determined at the time of shadow root creation and not dynamically changeable.

It just seems like orthogonal. Once the hydration define the loading life cycle, the slots filling, imperative or declarative, has to be triggered as one of Web Component life cycle sequence item . I.e. without hydration, defining slots filling convention is premature. That could happen even before come to the browser, during build time or SSR, or way later on scroll or click event.

rniwa commented 1 year ago

So I can think of two different processing models.

  1. initialelements / initialnodes are features of HTMLSlotElement. In this case, initialelements / initialnodes are supported independent of declarative shadow DOM. This would mean that we'd have to define between when these attributes define the assigned nodes (probably until the first call to assign()?).
  2. initialelements / initialnodes are features of declarative shadow DOM. In this case, initialelements / initialnodes are only supported within a declarative shadow DOM, and it substitutes assign() function calls after shadow host's children had been inserted.

One annoying thing about (1) is that we'd have to monitor any changes to the shadow host's child nodes over time, and update the currently assigned nodes. For (2), we'd have to think about whether slot assignment will be dynamically updated as more of shadow host's child nodes get parsed (i.e. allow streaming) or not. If we were to dynamically update the assigned nodes, it would have the same issue as (1) in that we'd be effectively monitoring any changes to shadow host's child nodes.

annevk commented 1 year ago

For 2 it would only be for the duration of the parse although the parser doesn't really have a generic "end tag seen" hook at which point we could remove the listeners. Or perhaps we could do it as part of the parser's insert operation...

mfreed7 commented 1 year ago

My impression had been that declarative shadow’s use cases were tied up with some notion of producing/taking a “snapshot” which would likely not be maintainable without some kind of generative tooling (e.g. build tools if static or runtime libraries that don’t permit naive string concatenation if dynamic SSR). Since the concern expressed here seems to regard ergonomics, it made me wonder if there are additional motivating use cases where authors would be writing declarative shadows directly?

(Idea seems fine to me, just trying to get a sense of how much this is meant to be an “author feature” vs (metaphorically) “html bytecode”).

I’m also curious about the use case. Mixing imperative slot assignment with declarative shadow dom, on its face, sounds at least a little odd. If tooling is doing the SSR work, perhaps the generated HTML can just use normal declarative slot assignment? I.e. assign values to the <slot name=foo> and <div id=light-child slot=foo> to slot in the correct content?

rniwa commented 1 year ago

I’m also curious about the use case. Mixing imperative slot assignment with declarative shadow dom, on its face, sounds at least a little odd. If tooling is doing the SSR work, perhaps the generated HTML can just use normal declarative slot assignment? I.e. assign values to the <slot name=foo> and <div id=light-child slot=foo> to slot in the correct content?

The primary use case here would be SSR of imperative slot assignment, particularly those that re-order nodes. Since slot assignment mode is fixed per shadow root, imperative slotting doesn't work well right now with SSR and limits the use scenarios of imperative slotting. FWIW, I wouldn't imagine specifying the initial assignment this way would be a common developer experience.

keithamus commented 1 year ago

WCCG had their spring F2F in which this was discussed. Present members of WCCG reached a consensus to resolve this issue with the proposed two attributes on <slot>.