Declarative Shadow DOM
tabatkins opened this issue Β· 144 comments
One of the promises of early Shadow DOM (and several other Parkour-descended products) was that while we would build the first version as an imperative (JS-based) API, for flexibility and composability, we'd come in later and backfill a declarative version that made it very easy to use for common cases. We haven't done this yet for Shadow DOM, and the spec is stable enough now that I think we can safely do it.
Rough proposal:
<div-or-something>
<template attach-shadow shadow-mode="open | closed">
...shadow content...
</template>
</div-or-something>
That is, using a <template>
element with an attach-shadow
attribute automatically (during parsing) attaches its contents to its parent element as a shadow root. The mode is selected by the optional shadow-mode
attribute; if omitted, it defaults to "open" (or we can make it required, like the JS API does?).
Reasoning:
Today you can get something close to declarative with JS like:
<parent-element>
</parent-element>
<script>
"use strict";
const shadowRoot = document.currentScript.previousElementSibling.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `<child-element></child-element>`;
</script>
This has several downsides:
- Doesn't work nicely with syntax highlighting.
- Doesn't work nicely with any tooling that wants to be able to output HTML.
- Doesn't nest easily. (That is, having an element in the shadow contain another declarative shadow.)
- Can't include
<script>
in the shadow unless you remember to do the various tricks to avoid having a literal</script>
in the string. - Also have to be aware of which quoting character you're using, and remember to escape it when you're writing your page.
- Including user-generated content is now more complicated; it not only needs to be escaped as safe HTML, but as safe script and string contents, too, which are decently more complex (and not, afaik, supported by the core libraries in PHP or Python).
- Lots of non-trivial typing for something intended to be easy and widespread.
- Inline script isn't compatible with safe CSP practice, unless you go the extra mile to add nonces (more effort! must use crypto safely!)
- Inline script isn't run for .mhtml archives (at least in Chrome, for historical reasons).
- Users with JS turned off won't get any page at all, for no good reason - page might have just been wanting style isolation, but is now tied to JS evaluation.
- Inline script halts the speculative parser, I think?
- Doesn't allow for server-side rendering (doing as much of the page as possible on the server, only using client-side JS for final fixups and event hookups, etc). The page instead has to do all this work on client-side (which is apparently fairly slow, per #510 (comment))
I am not personally convinced of the need here (but I don't oppose it). Let me try to help with some details that could be problematic if people want to advance this.
- We don't use hyphens in built-in attribute names. So attachshadow/shadowmode instead of attach-shadow/shadow-mode
- It seems like these could be combined into a single attribute, e.g. shadow="open|closed". If shadow is not present then it's a normal template.
- We'll have to recapitulate the great defaulting wars that led to mode being mandatory in the JS API. In particular, we need to answer what
<template shadow="asdf">
and<template shadow>
do. Maybe the answer is nothing (as if you'd omitted the attribute), but at least for<template shadow>
that seems unfortunate. - Harder question: how does this interact with the existing processing model for templates?
- Template contents are parsed in a completely separate document, the template contents owner document. That prevents e.g. script from running or images from downloading. It sounds like that will no longer be the case here?
- What does
templateEl.content
return? Maybe it becomes an alias fortemplateEl.shadowRoot
? Is that too weird? - What are the cloning and adopting steps?
I think I'd prefer <shadowroot mode="open|closed">
with the parser simply not treating this as a ShadowRoot
insertion request if the mode
attribute cannot be validated (until we can agree on a default).
This kind of feature also helps with first load where the server has done all the templating work to make rendering happen faster.
That would require more parser work and thus ecosystem churn though, right? On the same order of magnitude as that when we originally introduced template? Otherwise I agree it'd be nicer.
It should be less work now we have <template>
, but probably still non-trivial. I'm not a big fan of overloading elements, that hasn't worked well historically (e.g., <object>
and <embed>
are still a poorly understood mess).
(Note that I suspect that overloading <template>
would also require parser changes, due to the things you mentioned.)
Hi! It's me again. Sorry. Some background:
- https://github.com/skatejs/ssr
- https://www.youtube.com/watch?v=yT-EsESAmgA
- and oh so many twitter discussions with React folks; too many to ever find and list
I've tried to keep most of the history and rationale for our thoughts in the README of the skatejs/ssr repo, but some of it might be missing. It's worth a study to at least see where we're coming from.
The thing I've found most interesting in this work is that the imperative API is exclusive in its DOM behaviour. For example, <div>test</div>
means something completely different when attachShadow()
is imperatively invoked. Finding a declarative equivalent that doesn't break existing assumptions is difficult. For example:
<div>
World
<shadow-root>
Hello, <slot />!
</shadow-root>
<div>
Whether or not this is used with a <template />
element or something else, there's several problems with this:
- SEO and content order when read in engines that don't execute JS.
- What happens to
<shadow-root />
? Does it get removed? - What happens to
childNodes
? Attaching a root modifies this. - Does
appendChild(document.createElement('shadow-root'))
work?
Libraries like React (and many more) have based themselves on the pillar that a core subset of DOM works as intended. If appending a shadow root mutates the host and makes childeNodes
behave differently, this breaks the web. Inversely, if this is only available to the initial parse, it seems inconsistent. Meaning if <div><shadow-root /></div>
is only available to the parser, but means nothing when using the imperative alternatives (i.e. appendChild(shadowRootElement)
), then expectations of declarative vs imperative APIs are inconsistent.
I'm sorry if my thoughts seem a bit disjointed right now. Somebody linked this to me and it's a bit late here, but I really wanted to lay out some of my thoughts otherwise I may have forgotten about it (I do web components in my spare time).
I'm excited to see traction on this!
Thanks for starting this issue @tabatkins. To add some weight that this is a need, I tried the JS approach that you mentioned some time ago and it was fairly slow: https://discourse.wicg.io/t/declarative-shadow-dom/1904/8
We don't use hyphens in built-in attribute names. So attachshadow/shadowmode instead of attach-shadow/shadow-mode
Ah yeah, right.
It seems like these could be combined into a single attribute, e.g. shadow="open|closed". If shadow is not present then it's a normal template.
I did that at first, but separated it because the mode isn't a positional attribute of attachShadow, it's just a required entry in the options dict. I thought we might add more in the future, but now that I'm thinking, I guess we can't add more required options anyway as it would break the API. So sure, we can combine them.
Maybe the answer is nothing (as if you'd omitted the attribute), but at least for
<template shadow>
that seems unfortunate.
Strongly agree. I think there's an obvious answer - default to open, because you're just using this as a styling boundary; nothing more special is happening at all anyway, because no JS.
Template contents are parsed in a completely separate document, the template contents owner document. That prevents e.g. script from running or images from downloading. It sounds like that will no longer be the case here?
My intention is that it'll append the template contents to the auto-created shadow root. I think scripts'll run at that point, yeah?
What does templateEl.content return? Maybe it becomes an alias for templateEl.shadowRoot? Is that too weird?
Per the above, I guess the append automatically removes them from the template's doc fragment? The answer is then ".content is empty because its contents have been transferred out". Or is there a stamping operation that doesn't remove the contents? I'm not sure of the details here. I'm flexible, whatever answer falls out is fine.
What are the cloning and adopting steps?
I think in my conception this is a parser operation more or less, so cloning/adopting just does whatever those operations do for elements that have shadow roots attached in the JS way. I'm not sure what the implications of that are, tho.
This kind of feature also helps with first load where the server has done all the templating work to make rendering happen faster.
Ah yes, thank you, that's another tick in the Pro column.
[Adding a
<shadowroot>
element] would require more parser work and thus ecosystem churn though, right? On the same order of magnitude as that when we originally introduced template? Otherwise I agree it'd be nicer.
I completely agree - it would definitely be nicer, but given the non-trivial parser work, I was thinking leaning on template
would be better. On the other hand, I guess now that template
exists widely, one can lean on that for polyfilling (doing <template><shadowroot>...
) and we could add a new element. I don't have a strong opinion on this, because I understand the tradeoffs. Whatever works best for everyone.
SEO and content order when read in engines that don't execute JS.
There's no JS here. Tools that read the DOM would have to update to know about declarative shadow roots, sure, but that's vastly easier than supporting JS.
What happens to
<shadow-root />
? Does it get removed?
I'd think it would just stay there, like template
does. The shadow-attaching is a parse-time operation, it's a dead element afterwards.
What happens to childNodes? Attaching a root modifies this.
What do you mean? The element's childNodes are unaffected by the presence of a shadow root. The children get distributed, which matters for CSS and some other stuff down-stream, but the DOM doesn't care at all.
Does appendChild(document.createElement('shadow-root')) work?
No, this is a parse-time operation. Post-parsing, the shadowroot element is dead, identical to a plain template
. If you have JS, just use attachShadow()
. ^_^
The answer is then ".content is empty because its contents have been transferred out". Or is there a stamping operation that doesn't remove the contents?
I think template.content.cloneNode leaves the content in place (sorry on my phone right now so can't check).
Really excited to see this discussion happening! @kevinpschaaf @samuelli
There's no JS here. Tools that read the DOM would have to update to know about declarative shadow roots, sure, but that's vastly easier than supporting JS.
This for me is a potential issue - if we're relying on support from SEO engines / HTML scrapers to add support, it might hurt adoption as people are after a solution with current bots / tools - particularly given the ambiguity around what bots can do already.
Is this a concern for others? If so, it might be worth exploring a declarative 'composed' DOM, rather than declarative Shadow DOM, so current bots can 'see' what the user will see. Something we're exploring over at skatejs/ssr
Apologies if this is sidetracking from the main issue, but feel its worth mentioning. Thanks for getting this discussion going!
This for me is a potential issue - if we're relying on support from SEO engines / HTML scrapers to add support, it might hurt adoption as people are after a solution with current bots / tools - particularly given the ambiguity around what bots can do already.
That's a fully general counter-argument against any addition to HTML at all, and so can't really be used as a specific objection without more details.
If so, it might be worth exploring a declarative 'composed' DOM,
That's just... DOM, right? Like, normal ordinary DOM children.
That's a fully general counter-argument against any addition to HTML at all, and so can't really be used as a specific objection without more details.
Yeah that's fair - I'll go into specific details and whether tradeoffs are worthwhile.
That's just... DOM, right? Like, normal ordinary DOM children.
What I meant by this was declarative way to present the resultant composed tree after shadow root is attached and light DOM nodes are distributed - apologies if I'm getting the terminology wrong here.
More concretely, what I was thinking was a composed
flag to tell the parser to pull a part this elements DOM into Shadow and Light DOMs.
<div composed>
Hello <slot><strong>World</strong></slot>
</div>
which the parser would turn into:
<div>
#shadow-root
Hello <slot></slot>
<strong>World</strong>
</div>
This wouldn't be a 1:1 declarative version of imperative Shadow DOM API, just a way to tell the parser to construct an element's Shadow and Light trees. Straight off - and I'm sure there's a lot more I'm not considering - downsides are:
- No support for undistributed light DOM nodes
- No support for default slot content
- Certainly a confusing look at declarative Shadow DOM
I realise this may be way off base, but I just wanted to throw it out there as an idea, incase supporting current bots is worth the tradeoffs.
That's just... DOM, right? Like, normal ordinary DOM children.
Yes, but this is useful. People outside of the WC community actually want this.
What we're proposing is two distinct modes:
- Shadow DOM and CSS encapsulation via
attachShadow()
(the current state of things) - Only CSS encapsulation via the
composed
attribute (maybe also works viaattachShadow({ composed: true })
but that can be worked out later. Declarative design first IMO).
To concretely illustrate this, React is a good example.
class Hello extends Component {
static defaultProps = {
name: 'World'
}
render () => (
<div composed>
<style>
/* Works on <div composed>. */
:host {}
/* This works because of <slot />! */
::slotted(*) {}
/* Only works for top-level span, not the slotted one. */
span { font-weight: bold; }
</style>
Hello,{' '}
{/* This span would be affected... */}
<span>
<slot>
{/* ...but not this one. */}
<span>{ this.props.name }</span>
</slot>
</span>!
</div>
)
}
The idea here is a lot like the old <style scoped />
form of encapsulation, but it differs because the full gamut of Shadow CSS works (:host
, ::slotted()
, etc).
The <slot />
element provides an encapsulation barrier that CSS selectors cannot cross but that's it (querySelector()
still works). The assignedNodes()
method does not return anything because nothing is being projected. In this mode, DOM accessors are not encapsulated. However, to turn this into full Shadow DOM, one can call attachShadow()
and the composed tree is reverse engineered:
- The host receives a shadow root.
childNodes
of slots are attached to the host, thus are projected.- Any content within a slot that has the
default
attribute (flagging for default content) is not attached to the host, and remains as default content. - For named slots, this is declared as normal and is thus projected as normal.
This is literally the algorithm we're employing for declarative shadow DOM at the moment. The thing that's nice about it is that it's backward compatible and it fits very closely with the current model for shadow roots. @bedeoverend did I miss anything in that list?
EDIT
My proposal and @bedeoverend's proposal are slightly different. In his, he states that composed
should reverse engineer the DOM tree whereas in mine it leaves it alone until attachShadow()
is called.
The "laugh" here along with the comment (not the comment alone) seems a bit condescending. We're outsiders of the W3C desperately trying to have our say in context of our extensive experience in working with these APIs and being in close working contact with other communities. Though, subtle, this downplays that and makes outsiders less motivated to participate which is bad for the specs. Can we please have a discussion here that places a bit more weight on non-W3C members' experience? Thanks!
@bedeoverend did I miss anything in that list?
Don't think so - only thing is we're looking to handle undistributed nodes, but IMO that shouldn't be baked into platform.
FWIW I'm still unsure about whether composed
should be an entirely different mode, or whether it should just trigger the HTML parser to behave differently while parsing that element. I'd be for a a different 'CSS-only' mode, but is it reaching too far right now? Perhaps there's an iterative way to get there? Keen to balance what's feasible now and what's going to garner adoption from wider web community.
Though, subtle, this downplays that and makes outsiders less motivated to participate which is bad for the specs. Can we please have a discussion here that places a bit more weight on non-W3C members experience?
I agree with this. I appreciate it must be incredibly hard to work on specs and and the same time keep it open, but reactions which minimise external contribution only serve to ostracise the wider community. I want to try help, and I'm happy to be told my ideas aren't going to work, or I'm not communicating them well enough, but there's got to be a better way of doing that while keeping the community open.
FWIW I'm still unsure about whether composed should be an entirely different mode, or whether it should just trigger the HTML parser to behave differently while parsing that element. I'd be for a a different 'CSS-only' mode, but is it reaching too far right now? Perhaps there's an iterative way to get there? Keen to balance what's feasible now and what's going to garner adoption from wider web community.
I've had discussions about this with several outside of the WC community. Since they have their own component model (whether or not they should be using Custom Elements is besides the point), they don't need the DOM encapsulation, they just want the CSS aspect. If what their internals expect changes underneath them (their declared DOM changes from what they think it looks like) then it precludes them from declarative usage. This is a barrier to entry for them because they must modify their internals.
I truly believe what I've proposed actually fits as two modes that can be composed together to achieve what you want here. It can service both use-cases while maintaining compatibility with libraries and frameworks, without requiring they update their internals.
@annevk wrote:
I think I'd prefer <shadowroot ...
Using <template ...>
would make this much easier to polyfill. For example one could write a custom element which does the parent node stuffing and the syntax would look like <template is="shadow-root" shadow="...
I guess it will be easier to add "popping the element stack and the element is a shadow-setting template" steps to the HTML parser than to separate out the context-inheriting stuff of the template to reuse it for shadowroot.
If the idea is to avoid the adoption tree walk by creating the nodes in the target document then, yeah, not complicating template is better. The inoperativeness of template parsing (specifically not running script and custom elements) would be tricky to preserve.
I tend to agree @domenic. I am not personally convinced of the need here, but I don't oppose it.
What happens to ? Does it get removed?
I'd think it would just stay there, like template does. The shadow-attaching is a parse-time operation, it's a dead element afterwards.
If it would just stay there as a dead element, the memory usage would increase, doubled or maybe more than that.
Suppose the following html, using declarative Shadow DOM via <shadowroot>
,
<custom-a>
<shadowroot>
<slot></slot>
<div></div>
</shadowroot>
<custom-b>
<shadowroot>
<slot></slot>
<custom-c>
<shadowroot>
<slot></slot>
<div></div>
</shadowroot>
<div></div>
</custom-c>
</shadowroot>
</custom-b>
</custom-b>
In this case, the composed tree which the rendering engine actually holds would be:
custom-a
::shadow-root
slot
div
shadowroot
slot
div
custom-b
::shadow-root
slot
custom-c
::shadow-root
slot
div
shadowroot
slot
div
div
shadowroot
slot
custom-c
shadowroot
slot
div
div
I would prefer to remove a <shadowroot>
element from the light tree after parsing and attaching it as a shadow tree, to save memory.
This can be:
custom-a
::shadow-root
slot
div
custom-b
::shadow-root
slot
custom-c
::shadow-root
slot
div
I would prefer to remove ` (sic) element from the light tree after parsing, to save memory.
Removing it would also make feature detection easier.
It's probably not a big deal but implementations should be able to avoid creating the template element because you won't be able to observe its brief existence (unless it is a custom element.)
I'll echo @treshugart's point that, for React, the shadow DOM is only overhead and complexity, when the only thing we'd really want out of this is scoped CSS. If we can't have that, we could work with a shadow DOM too. I'm sure @wycats had thoughts on this too.
I would expect many trees to result in code like:
<div>
<template attach-shadow shadow-mode="open">
<div>
<template attach-shadow shadow-mode="open">
<div>
<template attach-shadow shadow-mode="open">
<div>
<template attach-shadow shadow-mode="open">
...
</template>
</div>
</template>
</div>
</template>
</div>
</template>
</div>
Since that's semantically what a modular composed tree would be like. We'd just treat the shadow tree as the new children tree. Anything else would be an optimization (which might need to bail out).
What would be very interesting, however, is if you could target a particular ID that was defined earlier to attach the shadow lazily later in the document stream.
<section>
<h1>Heading</h1>
<p id="content">Loading...</p>
...
</section>
...
<template attachto="content">
Hello world!
</template>
Even then, I'd prefer it if it replaced the children with the content of template, but we could live with the workaround to use shadow DOM.
Given the extraordinary lift that was required to get <template>
done (we had to get changes into XML's parsing behavior, e.g.), a new element for this seems like a non-starter.
From the perspective of implementation difficulty, whether <template newattribute>
or <shadowroot>
-ish new element shouldn't be a big deal, I guess, though it still needs non-trivial efforts.
From the perspective of spec, that might be a big deal.
@treshugart I think that by definition declarative shadow trees would have to be somewhat new as shadow trees are new. You cannot expect it to map to appendChild()
just like you cannot expect that for contents of <template>
.
Now, having a separate kind of feature where there is some kind of boundary that ends up applying to CSS, but doesn't involve creation shadow trees, might be worthwhile, but I don't think it's a good idea to conflate that request with the topic of this issue: declarative shadow trees. There's not even an imperative API for what you're suggesting. I have seen the request before to decouple more and I can certainly appreciate that though I haven't seen a good enough solution yet. In any event, please open a separate issue to discuss that.
@hayatoito I think we'll hit about as much complexity either way and I suspect that overloading <template>
this way will make it harder to add new features to it in the future.
@treshugart I apologize, and have removed the offending emoji. I'll step back from this thread.
I would prefer to remove a
<shadowroot>
element from the light tree after parsing and attaching it as a shadow tree, to save memory.
Sure, I don't have an opinion on this. If removing it makes more sense, let's remove it. That might end up being less confusing for authors too, since the element doesn't do anything if it's sitting there.
It's probably not a big deal but implementations should be able to avoid creating the template element because you won't be able to observe its brief existence (unless it is a custom element.)
Another reasonable argument for removing it.
I've had discussions about this with several outside of the WC community. Since they have their own component model (whether or not they should be using Custom Elements is besides the point), they don't need the DOM encapsulation, they just want the CSS aspect. If what their internals expect changes underneath them (their declared DOM changes from what they think it looks like) then it precludes them from declarative usage. This is a barrier to entry for them because they must modify their internals.
The current concept of CSS encapsulation is built on Shadow DOM. It can't be separated from it in a reasonable way; doing so would be a completely new, totally separate feature that would need its own justification. (And I can tell you right now, it would be a hard sell to justify having two totally different ways to encapsulate CSS.) I'm not willing to try and tie declarative shadow DOM to this sort of limitation. As Anne said, this should be addressed via a separate issue.
(I also, personally, am 100% on the "stop rolling your own component models, they're not interoperable and they lock users in, just use WC" train.)
Thank you, @domenic, and just to be clear - for posterity - you didn't necessarily "offend" me and it's not just you. This in isolation wouldn't be an issue. Community involvement in this working group has been pulling teeth for many, and this is one of the many forms of negativity (even if it's small) towards it. All we want is for our extensive experience to carry weight in the discussions.
I won't let this detract from the rest of the discussion. I'm over the moon this is even being discussed right now.
Sure, I don't have an opinion on this. If removing it makes more sense, let's remove it. That might end up being less confusing for authors too, since the element doesn't do anything if it's sitting there.
Regarding, template overloading (<template newattribute>
) vs new element (<shadowroot>
-ish),
if we can agree on "don't leave <template>
element in the light tree", I prefer a new element, <shadowroot>
or something, here because:
Descendant nodes of declarative shadow dom are effectively NOT inert, from user's perspective.
e.g.
<custom-a>
<template newattribute> (or <shadowroot>)
<img src='...'>
<script src='...'>
</template>
</custom-a>
In this case, <img>
or <script>
look inside of <template>
element, but they would reside in the shadow tree, as the final result, after parsing html. We fetch resources for them. That sounds a big difference to me, as compared to usual <template>
element's usage pattern. newattribute
is too weak as a visual sign of this big different semantics.
In addition to that, as Dominic suggested, the engine (or parser) might want to optimize.
Instead of:
- Parse a template element (and its descendants), as usual
- Call attachShadow on the parent element of the template
- shadowRoot.appendChild(template.content);
- Remove template element from the light tree
We might want to optimize:
- Parser encounters opening
<shadowroot>
- Call attachShadow on the parent element of the
<shadowroot>
- Parser continues to parse descendants of
<shadowroot>
, and build a shadow tree directly - Parser encounters a closing
</shadowroot>
. - Parser continues to parse html, and continue to build a light tree
I am not sure how this approach is feasible, but that could be an option as a starter. If we choose this, there might be no longer a good reason to use <template>
.
I can't speak to how difficult it would be to get a new element in, but @hayatoito's description of optimized <shadowroot>
sounds perfectly aligned to the use-case, and not even an optimization, how how it should just work.
Hi everyone,
I am just new here but I would like to get some clarifications. Given that you need define the shadow root of a given custom-element, I am really confused on how the proposed way of defining it in a declarative way should be. All I am seeing right now is:
<custom-a>
<template newattribute> (or <shadowroot>)
<img src='...'>
<script src='...'>
</template>
</custom-a>
or
<custom-a>
<shadowroot>
<slot></slot>
<div></div>
</shadowroot>
<custom-b>
<shadowroot>
<slot></slot>
<custom-c>
<shadowroot>
<slot></slot>
<div></div>
</shadowroot>
<div></div>
</custom-c>
</shadowroot>
</custom-b>
</custom-b>
or
<div-or-something>
<template attach-shadow shadow-mode="open | closed">
...shadow content...
</template>
</div-or-something>
But wouldn't this confuse us (or the browser) or when to just use <custom-a>
or <custom-b>
as a tag and when to add the shadow root definition of <custom-a>
?
What if this happens while parsing the html document below?
<html>
<head>
</head>
<body>
<custom-a>
<shadowroot>
Hello <slot></slot>!
</shadowroot>
</custom-a>
<custom-a>
<shadowroot>
Hi <slot></slot>!
</shadowroot>
</custom-a>
<custom-a>
World
</custom-a>
</body>
</html>
If we mixed the definition of a custom-element's shadow root in the light child of the custom-element, then would that mean we can replace it while parsing the html (which is good if you want to change the shadowroot), but would at least confuse when is the time the custom-element is being used and when is the time the custom-element is being defined.
Would this be a better case?
<html>
<head>
</head>
<body>
<!-- usage phase -->
<custom-a>
World
</custom-a>
<!-- definition phase -->
<shadowroot tag="custom-a">
Hello <slot></slot>!
</shadowroot>
<!-- or -->
<template shadowroot tag="custom-a">
Hi <slot></slot>!
</template>
</body>
</html>
At least it defines when a custom-element is being used (when it is really being used as a tag) and when it is being defined (when you explicitly want to attach a shadow dom to a particular custom-element by using a defined tag like shadowroot or extend the template tag)
Just my thoughts.
@tjmonsi I don't think anyone is proposing declarative syntax for defining a custom element, that may be where your confusion stems from. Today the only way to define a custom element is to call customElements.define()
. They're discussing how to represent an element (and its shadow root) after it has already been defined. This would benefit use cases where you want to server side render elements with shadow roots. This would benefit things like bots/crawlers who may not run JavaScript and would have no other way of knowing what content lives inside of a <my-app>
element.
@robdodson Ah I see, so it means that we can now write a renderer that renders an output of a computed shadowRoot on the server and write it as a child of a custom element with a clearly defined tag (either shadowroot
or template
) so that it tells the browser that this how it should render the a particular custom-element at that moment (and once JS kicks in, it will eventually update the shadowRoot of that custom-element when the data also updates).
So now I get it when this happens
<html>
<head>
</head>
<body>
<custom-a>
<shadowroot>
Hello <slot></slot>!
</shadowroot>
World
</custom-a>
<custom-a>
<shadowroot>
Hi <slot></slot>!
</shadowroot>
TJ
</custom-a>
...
Because it will pretty much look like
<custom-a>
#shadow-root (open)
Hello <slot></slot>!
World
</custom-a>
<custom-a>
#shadow-root (open)
Hi <slot></slot>!
TJ
</custom-a>
If we can agree on using <shadowroot>
(or whatever new element), what is the next action for us?
- Is it okay to introduce a new element name into HTML, without worrying about conflicts in wild?
- Who can confirm that it is feasible to update HTML Parser in HTML Standard in such a way? I think there is very few people who can update this part of HTML Standard. I guess we need more concrete proposal before that.
- No, we have to worry about conflicts as we'd like to maintain compatibility with existing content: https://www.w3.org/TR/html-design-principles/#support-existing-content.
- Folks from @whatwg/html-parser.
Thanks. Regarding 1, I checked the usage of <shadowroot>
via httparchive.
The usage is zero, as of now.
The query is:
SELECT page, count(*)
FROM [httparchive:har.2017_09_01_chrome_requests_bodies]
WHERE REGEXP_MATCH(body, r'<shadowroot>')
GROUP BY page
I think we should move this to whatwg/html at this point by the way. Everything proposed thus far (apart from CSS encapsulation without DOM encapsulation which there's no clear proposal for as of yet) requires changes to HTML, and I don't think would require changes to DOM at all.
Regarding 2: I'm not quite familiar with Shadow DOM spec, so can someone clarify this for me, please: should <shadowroot>
inherit parent element's parsing context? E.g. if I have a <table>
element can I create a shadow root that will contain some rows of this table?
@inikulin I think that's what should happen. That's also how shadowRoot.innerHTML
works (to be defined still: w3c/DOM-Parsing#21). It gets the context element from the shadow host (<table>
in your example).
@annevk Thanks for the clarification. And another question: does any element can contain <shadowroot>
, e.g. can <html>
, <input>
or <textarea>
has a shadow root inside it?
@inikulin No, the list of builtin elements that can have a shadow is here: https://dom.spec.whatwg.org/#dom-element-attachshadow
Ah indeed, that's probably why we'll need an issue against DOM anyway. We need to share that list somehow and make sure that whenever we add elements to it that's reflected on both sides.
Since the list of allowed contexts doesn't contain any elements that require parser insertion mode adjustment it seems like we don't need <template>
machinery for the <shadowroot>
at all. From the first sight we just need to add amendments to node insertions logic, like we have for foster parenting at the moment: https://html.spec.whatwg.org/multipage/parsing.html#foster-parent, make <shadowroot>
a scoping element, move to it to special category and maintain a stack of insertion points for nested roots.
Note that there's proposed additions for attachShadow()
over at WICG/webcomponents#511 (comment) though there hasn't been much interest in that lately. (And I think all those elements are okay from an insertion mode perspective too.)
And I think all those elements are okay from an insertion mode perspective too
Yes, for <a>
we'll need to insert marker into active formatting element list once we encounter <shadowroot>
to scope formatting element list reconstruction and adoption agency algorithms (like we do for <template>
). Seems like everything else should work without additional modifications.
Does appendChild(document.createElement('shadow-root')) work?
No, this is a parse-time operation. Post-parsing, the shadowroot element is dead, identical to a plain template. If you have JS, just use attachShadow(). ^_^
So what should appendChild(document.createElement("shadowroot"))
do? Throw?
@matthewp I believe it should behave consistently with other cases when elements appended in locations that are not valid during parse phase: just attach an element without any additional machinery (e.g. inserting <div>
in table).
Yeah, that's what I'd expect.
Who can start opening this to whatwg/html?
Can I double-check an expected behavior?
// Construct a dom tree in an imperative way
const p = document.createElement('div');
const s = document.createElement('shadowroot');
p.appendChild(s);
assert(p.hasChildNodes());
assert(!p.shadowRoot);
// Then, we *serialize* and *deserialize* |p|.
const inner_html = p.innerHTML;
assert(inner_html === '<shadowroot></shadowroot>)';
p.innerHTML = inner_html;
// Now, |p| changed.
assert(!p.hasChildNodes());
assert(p.shadowRoot); // Assuming shadow root's mode is "open" here.
Can I assume that this is an acceptable behavior?
@hayatoito looks legit.
@rniwa do you have any thoughts on how mode should work with this feature? Should we require a mode
attribute just like we require mode
dictionary member? In a way that makes sense, but it also seems closed shadow trees might be hard to use when setup in a declarative manner.
@tjmonsi I created whatwg/html#3061.
@hayatoito It's not intended, but it's probably acceptable, yeah. (And if you repeat the p.innerHTML = p.innerHTML
line again, you'll get no children and no shadow root.)
I think this should be considered in conjunction of what declarative syntax for custom elements (WICG/webcomponents#135) as well as template instantiation (whatwg/html#2254) would look like because we surely don't want to haver 3-4 slightly different ways to define & use a template.
@hayatoito can you run it again with REGEXP_MATCH(LOWER(body), r'<shadowroot(>|\s)')
?
I think this should be considered in conjunction of what declarative syntax for custom elements (WICG/webcomponents#135) as well as template instantiation (whatwg/html#2254) would look like because we surely don't want to haver 3-4 slightly different ways to define & use a template.
I agree we don't want multiple slightly different ways. I don't think these collide in any significant way; it seems like they all work together fine?
Auto-filling the shadow DOM of a custom element looks good as it is; you wouldn't want to somehow tie this into shadowroot
. So long as shadowroot
and template
parse their contents the same, which I think they should, these two complement each other; shadowroot
for manually attaching a single shadow root to an arbitrary element, registerElement()
+ template
for automatically attaching a particular shadow root to every element of a given name.
If we add template
variables that work for an arbitrary template
sitting in the DOM, then they should work equally in a shadowroot
. If they're somehow attached to custom elements more explicitly (so there's some scoping of variables, rather than just JS global scope?), then it wouldn't work for either a lone template
or a shadowroot
, still consistent between the two.
Does anything about these two scenarios particularly concern you?
Creating a new element for the sole purpose of creating a shadow tree greatly concerns me.
For example, if we're defining a custom element, you wouldn't want to have another element outside a shadow tree of that custom element so using shadowroot
element inside template
element doesn't make sense. One approach to solve that discrepancy is to use shadowroot
element itself to define a custom element. However, it would mean that depending on whether you'd like to use a shadow root or not, you have to use template
or shadowroot
, and the browser engine have to support the set of attributes and semantics in both elements.
Alternatively, we can define some exotic behaviors like all other nodes but shadowroot
is ignored when defining a custom element. But this would create new behavior difference between when shadowroot
is used standalone and when it's used to define a custom element.
If you're creating a custom element, there's useful, convenient machinery for that which does lots of good things. This is not intended to help with custom elements in general; you already need JS for that, and there's very little we can add declaratively to help there.
This is for the simple cases of wanting to add a shadow DOM to an element for encapsulation purposes, in as light-weight a way as possible. There's a nice big bulleted list of reasons in the original post for why our current method of doing so is inconvenient and bad. The gist of it is that the CSS encapsulation provided by shadow roots is really good, and we'd like to encourage its usage as much as possible to make sites more maintainable, but we don't want to require people to do a lot of JS work for something so simple. (And beyond just "it's inconvenient", there are several other reasons why shoving this into JS is bad, listed in the OP.)
If you do happen to do <custom-element><shadowroot>...</></>
in your page, we don't need to do anything special here - the parser will attach a shadow root to the element at the same time it would have otherwise appended light-dom children. I'm not familiar enough with the intricacies of the CE lifecycle to know if this is observable or not. In any case, it's definitely not intended for this sort of usage; manually adding your shadow root to every CE instance is just doing a ton more work than is necessary.
The trouble is, we'd also need a declarative syntax for custom elements including defining its shadow root, and what you're proposing be distinct from what would be used for custom elements as far as I can tell.
On that basis, I object to the proposal and would not implement it in WebKit in the current form.
The trouble is, we'd also need a declarative syntax for custom elements including defining its shadow root,
I don't understand what you mean by this. Why would we need this?
If I understand correctly, @rniwa has a 2013 proposal to let Javascript write:
class FlagIcon extends HTMLElement {
...
}
customElements.define("flag-icon", FlagIcon, document.getElementById("flag-icon-template"));
which will auto-clone the template and make it the shadow root when a new <flag-icon>
is created, and then the FlagIcon
class can do what it wants with that shadow root.
Then there's a proposal from January to help libraries data-bind templates, with syntax, I think, like:
document.getElementById("flag-icon-template").createInstance({
field1: val1,
field2: val2,
}, (template, parts, params) => {
return parts.forEach((part) => { eval(part); })
});
(BTW, proposals in comment threads are really hard to understand. @rniwa, please create WICG repositories for this kind of thing so that we can see the current state of your thinking instead of having to trace its history and guess.)
Those seem to fit together fairly well, since the FlagIcon
class could provide the processor for the template picked by the third argument to customElements.define()
.
I'm not seeing how they help at all with the problem of injecting the contents of an element into its parent's shadow root. The interesting part of this problem is that you've written some HTML that you want to use as some particular element's shadow root, rather than as the shadow root of all elements with the same name, so the 2013 proposal isn't helpful. The HTML doesn't need data-binding, since it's written out literally, so the January proposal isn't helpful.
Maybe I'm missing something. @rniwa, can you point that out?
Yeah, @rniwa linked to both of those a few comments ago, and I responded about them in #510 (comment) and asked if there was anything in particular he was troubled about.
I'm not seeing how they help at all with the problem of injecting the contents of an element into its parent's shadow root.
Using a template to define a custom element should certainly put the instantiated template into a shadow tree. So it's extremely relevant here. We definitely don't want to have two completely different mechanisms to define the contents of a shadow tree.
The interesting part of this problem is that you've written some HTML that you want to use as some particular element's shadow root, rather than as the shadow root of all elements with the same name, so the 2013 proposal isn't helpful. The HTML doesn't need data-binding, since it's written out literally, so the January proposal isn't helpful.
What's relevant here is that both defines the contents of a shadow root.
In general, we shouldn't have two completely different mechanisms to define the contents of a shadow tree for this use case and declarative custom elements. That's confusing for authors, and makes the Web platform as a whole less coherent & confusing.
@rniwa In your declarative custom element proposal, it looks like the way to define a shadow root is via Javascript: customElements.define(..., ..., here)
. Does that mean you're definitely opposed to any way to attach a shadow root to a non-custom-element in straight HTML?
@rniwa In your declarative custom element proposal, it looks like the way to define a shadow root is via Javascript:
customElements.define(..., ..., here)
. Does that mean you're definitely opposed to any way to attach a shadow root to a non-custom-element in straight HTML?
That was not the design we wanted to have. We proposed that version as a simple enough addition in v1 as opposed to adding the full declarative syntax in v1 which has been discussed numerous times in the standards groups since 2010. The real declarative syntax for custom elements shouldn't involve making customElements.define
call in JS manually as that won't be declarative.
Ah, good. Do you have a link to some updated sketch of the design for the full declarative syntax?
No, we don't. The proposals and discussions are spread across public-webapps mailing list and various Github issues. There is a rough consensus that we would discuss this at TPAC so I'd expect each browser vendor will have an internal discussion before TPAC and perhaps post a proposal in advance but we're not there yet.
Regardless of whether this proposal is a good idea or not, adding this new feature to the spec without having that broader discussion for custom elements and related use cases seem extremely shortsighted given the current proposal involves a HTML/XML parser change, which means this feature would take multiple years to be usable anyway.
@hayatoito can you run it again with
REGEXP_MATCH(LOWER(body), r'<shadowroot(>|\s)')
?
Sure. I used the following query this time, and got zero hit again.
SELECT page, count(*)
FROM [httparchive:har.2017_09_01_chrome_requests_bodies]
WHERE REGEXP_MATCH(LOWER(body), r'<shadowroot(>|\s)')
GROUP BY page
BTW, I will not attend TPAC this year. I would expect that this issue is a good place to discuss Declarative Shadow DOM not only for browser vendors, but also for those who are not browser vendors.
shadow: none|open|closed, default is none
<template shadow="open"></template>
<element shadow="open">
<template></template>
</element>
customElements.define('custom-element', class extends HTMLElement{}, {
shadow: 'open'
});
customElements.define('custom-element', class extends HTMLElement {
static get shadow() { return 'open'; }
});
Thanks @rniwa. It sounds like we'll see progress on this at TPAC, and we should just wait on this use case until then.
Yeah, I'm happy to wait if there is an important proposal that sits in nearby semantic space. So long as we're sure there will be an actual proposal to discuss by TPAC. ^_^
I'm also new to the process.
I think <shadowroot>
makes sense when attaching the shadow DOM directly to the <shadowroot>
's parent element. But I find @sebmarkbage's <template attachto>
suggestion quite interesting and strictly more powerful.
Especially if, as @slightlyoff points out, it would require "extraordinary lift" to define and implement <shadowroot>
, well, so much the worse for <shadowroot>
.
I'm also quite intrigued by @tjmonsi's suggestion to declaratively define custom elements and their shadow DOM with something like <template tag="my-custom">
. That seems really nice in its own right, and would also provide 95% of the value of <template attachto>
. (Just give the attachto
element a custom tag name and use <template tag>
instead.)
I still don't understand why we consider template
as a possible representation for the shadowroot
, whereas the later, as far as I'm aware, has completely different content parsing model.
It depends on how instantiating the shadow root works. For example, if we're going with the route that shadowroot
is some sort of macro syntax in parser which hoists its children into shadow root, and the element itself disappears, it probably doesn't make much sense to use template
element for this because the semantics is so different.
On the other hand, if the semantics of this feature is that shadowroot
simply defines the template from which the actual shadow root is generated, then using template
makes a lot more sense. For example, imagine that we added the support for whatwg/html#2254, then you should imagine we also want to support stamping out parts of the shadow tree using some values in a JS object or from content attributes of the shadow host.
There is a lot of open questions, and the best way to answer these would be have a list of concrete use cases for which this feature would be used.
So could you please list a very concrete use case (e.g. with an actual HTML & CSS for a concrete component, not just hypothetical generic problem) for which this feature is trying to address / useful?
I think @robdodson did a very good comment on what I think the shadowroot is for: and that is your first route: as a macro syntax parser.
A very good concrete use case for this is the ability now for web components that uses shadowDOM is to generate the tag from the server side, as the initial structure of the shadowDOM of a custom element. The idea here is that any web component that uses a shadowDOM will render its first look, and "stamping" the output of that render inside the . That's why you can have this...
<custom-a>
<shadowroot>
Hello <slot></slot>!
</shadowroot>
World
</custom-a>
<custom-a>
<shadowroot>
Hi <slot></slot>!
</shadowroot>
TJ
</custom-a>
This just means that the custom-a
tag has two different shadowroot, because in the server, the generated shadowDOM rendered two different DOM (maybe because of an attribute or some flag).
The shadowroot automatically puts it as the shadow-root of the custom-element without the need or help of JS. This is very essential if we are vying for a progressive enhancement enabled site that uses shadowDOM.
As for having a shadowroot tag to define a custom-element's shadowDOM, this should be relegated to JS only because of the customElements.define API. And besides, it also begs the question, how do you load the definition file of custom-element? If everyone is pulling away from html imports, then how can we define a custom-element without pasting all custom-elements used in all HTML files that will use it?
So I guess, the main use-case of the shadowroot is the first route, not the second one.
The declarative shadow DOM could be very useful in getting shadow DOM work directly in MHTML. Without it, we will have to serialize the shadow DOM content in a template element and then rely on special code to instantiate it at MHTML loading time.
shadow: none|open|closed, default is none
<template shadow="open"></template>
<element shadow="open">
<template></template>
</element>
customElements.define('custom-element', class extends HTMLElement{}, {
shadow: 'open'
});
// or
customElements.define('custom-element', class extends HTMLElement {
static get shadow() { return 'open'; }
});
<the-element>
<shadow mode="open">
<h1>Shadow Contents</h1>
<slot></slot>
</shadow>
<h1>Contents</h1>
</the-element>
<!--parsed as-->
<the-element>
#shadowroot (open)
<h1>Shadow Contents</h1>
<slot></slot>
<h1>Contents</h1>
</the-element>
I really regret I come that late to this discussion.
At Starcounter, we are using some form of declarative Shadow DOM on production for more than a year and a half, now.
So far, to make it work, it requires a specific custom element as a host - that also does client-side include.
I'm currently working on writing an independent <template is="declarative-shadow-dom">
CE, which could be closer to a real prolyfill.
Starcounter is an application platform, so there are even more devs actually using that feature. So far I received only positive feedback, mostly for:
- extremely easy way to separate layout from content,
- well.. declarative, JS-free way to use Shadow DOM,
- an unobtrusive way to style 3rd party content - article
(By style and layout I mean a full composition of HTML+CSS features, not just CSS)
That also plays nice with a server- or client-side includes, as you can share the same set of functional elements, but attach different HTML+CSS composition to each instance.
@treshugart, @bedeoverend
Speaking of SEO problems for Declarative Shadow DOM, I don't think it's a big problem.
Until robots will completely adapt to the new spec, DSD could still be useful just for encapsulated, scoped styling.
So far, putting an actual content to the (imperative) Shadow DOM leads to the problems anyways - a11y and label-form coupling for example. That's why I'd not consider it as a blocking issue for Declarative Shadow DOM.
I believe, the fact that some external party cannot access the thing we wanted to encapsulate in first place into Shadow DOM, is closer to a "feature" rather than a "showstopper"
Speaking of <template shadow>
vs. <shadowroot>
, I don't have a really strong opinion.
I'd just like to mention that shadowroot
nicely correlates to the way dev tools currently shows already attached shadow roots, it's also the syntax used in some articles, tutorials, and examples over the Web, so I believe it would be intuitive and self-explanatory for the developers.
Even though, technically and implementation-wise they could be pretty similar, having a separate name would let them evolve in different directions if needed. Having a single tag with different attributes that changes its behavior completely reminds me <input type="*">
.
I would prefer to remove a
<shadowroot>
element from the light tree after parsing and attaching it as a shadow tree, to save memory.
I'd like to upvote this and behavior speced by @hayatoito giving the developer perspective.
If it would remain there, one (at least me) would try to modify it to actually change the .shadowRoot
, and having a yet another way to imperatively change shadowRoot
, through a ... declarative shadow dom would be extremely confusing and messy.
Also, polluting .children
would be problematic for custom elements that act as decorators, or relies on the number of actual children.
I cannot express how happy I am seeing the progress of this issue. This is the thing I wanted to use since I started to use Web Components ~2013.
Until robots will completely adapt to the new spec
It's naive to think that all robots will eventually adapt. Some many don't execute JS or may parse the document in many different ways.
I believe, the fact that some external party cannot access the thing we wanted to encapsulate in first place into Shadow DOM, is closer to a "feature" rather than a "showstopper"
Shadow DOM isn't a security feature, only encapsulation. DOM for interoperability, styling for limiting side-effects. Not having access to content is a problem.
Frozen realms may be a more suitable way to limit access to content.
It's naive to think that all robots will eventually adapt. Some many don't execute JS or may parse the document in many different ways.
As it is declarative <shadowroot>
it's no longer JS to execute, it's just a new HTML Element.
Demand for all users to adapt is, as already stated here, "general counter-argument against any addition to HTML"
My point is that Declarative Shadow DOM, would work not worse, but even better than, already shipped imperative Shadow DOM. And the fact that there is a problem with Shadow DOM and SEO in general shouldn't block us from moving forward. Especially, given that if robots would adapt to the new element, it could actually solve this issue.
To me, SEO is a general problem of Shadow DOM, not just declarative one, and declarative SD finally gives a chance to all the robots that read just HTML to reason about it.
Shadow DOM isn't a security feature, only encapsulation. DOM for interoperability, styling for limiting side-effects. Not having access to content is a problem.
I agree it's not a security feature. But this encapsulation creates an isolation boundary. Then the fact that somebody hits this boundary because he/she was not prepared to consciously do that, is the actual feature of the Shadow DOM to me.
And the fact that there is a problem with Shadow DOM and SEO in general shouldn't block us from moving forward.
Really?!?! If this is a shared view, this is highly concerning. Other communities are actively ignoring - and advocating against using web components - for partly this reason.
I want to tease out how this might be used in a React component to illustrate my thinking and a question I have. In this example I use <shadowroot>
but it can apply to rest of the proposals, too.
There is a very big use case for using declarative shadow DOM in React to scope styles. They already have their own composition model, so they don't need that part of it, but anyone who is even remotely in touch with that community knows about the droves of CSS-in-JS libraries that have their own encapsulation models.
If using declarative shadow DOM, they might think to use it something like the following.
const Hello = props => (
<div>
<shadowroot>
<style>{`:host { border: 1px solid black; }`}</style>
Hello, {props.name}!
</shadowroot>
</div>
);
It's not clear to me what happens if React updates content in the <shadowroot>
. For example, when that component re-renders if the name is changed, it will update the content in the shadow root (but only the text node where the name is). Will this reflect in the real shadow root?
If not, would it be rewritten as:
const Hello = props => (
<div>
<shadowroot>
<style>{`:host { border: 1px solid black; }`}</style>
Hello, <slot />!
</shadowroot>
{props.name}
</div>
);
It seems like this would play just fine with server rendering? Bots that aren't adapted to declarative shadow DOM would read the content, though it would be out of order. This could be problematic. Browsers that implement the element would upgrade immediately, no? If so then my concerns are probably less important than I originally thought.
Good to see traction on this nonetheless, but would be fantastic to see other community involvement.
I somehow like the idea now of retaining shadowroot
in DOM after parsing to manipulate the shadowDOM of an element. But I thought of custom elements where they have their own templating system or they use their shadowDOM as basis of their behaviors and using shadowroot
would inevitably create chaos within that element (at least within that element and not all of the same element). I think this needs to be addressed.
Although the pattern @treshugart gave is a bit of an anti-pattern, I think a good example here really is using a custom-element
const Hello = props => {
<custom-element>
{props.name}
</custom-element>
}
Now some SSR lib would render the whole thing as this:
<custom-element>
<shadowroot>
<style>{`:host { border: 1px solid black; }`}</style>
Hello, <slot></slot>!
</shadowroot>
Props value
</custom-element>
@tjmonsi sorry, what's the anti-pattern, specifically, and why?
<div>
<shadowroot>
...
</shadowroot>
...
</div>
I think this is an anti-pattern because we are defining a shadowroot
on a standard element (like div
, span
, main
, etc...) which if I remember it right, will not work.
And if we use a custom-element
, we really don't need to put a shadowroot
element because there's already a template mechanism being used that defines the shadowDOM of that custom element. The only thing needed is some SSR library to render that shadowDOM into a shadowroot
, and then append/prepend it.
I think this is an anti-pattern because we are defining a shadowroot on a standard element (like div, span, main, etc...) which if I remember it right, will not work.
The spec explicitly states which elements can accept a shadow root in the attachShadow()
invocation steps here: https://dom.spec.whatwg.org/#dom-element-attachshadow.
And if we use a custom-element, we really don't need to put a shadowroot element because there's already a template mechanism being used that defines the shadowDOM of that custom element. The only thing needed is some SSR library to render that shadowDOM into a shadowroot, and then append/prepend it.
Not always. A custom element may indeed only define behaviour, but no content. If it defines content, the templating mechanism is arbitrary. I think it would be fair to say that custom element authors, if using shadow DOM and in the possible presence of a declared shadow root, the author must only create a shadow root if one is not already provided. It's up to them to template accordingly, too. It's most likely that if a shadow root is declared that they're rendering similar, if not the same things, and are using it for the purposes of server-rendering.
Some of these possible conflicts are why I am proposing an alternative route: https://gist.github.com/treshugart/dafb15f613bb7664d451f582da512f63, even if it's unlikely to get consideration.
even if it's unlikely to get consideration
That's not true, see #510 (comment) and also a following comment by @tabatkins about the problems with your suggestion. As I said then, it would really be better to discuss that change separately from a declarative form for attachShadow()
. A new issue would be appreciated.
The spec explicitly states which elements can accept a shadow root in the attachShadow() invocation steps here: https://dom.spec.whatwg.org/#dom-element-attachshadow.
Yep. So if I read it right, using div
would return an error (or at least the shadowroot
will not work) because you can't attachShadow on it: (See number 2)
It's most likely that if a shadow root is declared that they're rendering similar, if not the same things, and are using it for the purposes of server-rendering.
I think this is a better path on having shadowroot
. I think it should be the SSR system's job to compute the eventual shadowDOM of an element and create a shadowroot
tag and not the author. The author's job is to still use the usual way of defining a shadowDOM which is to use the attachShadow
method.
Since div
is in the list and the exception is only thrown for items not in the list, attachShadow()
would work just fine for div
elements.
And the fact that there is a problem with Shadow DOM and SEO in general shouldn't block us from moving forward [with Declarative SD].
Really?!?! If this is a shared view, this is highly concerning. Other communities are actively ignoring - and advocating against using web components - for partly this reason.
So far this is just my personal view. However, I think we may get into some misunderstanding here. I agree that Shadow DOM has serious problems with SEO that should be addressed somehow. But since this is an issue about Declarative Shadow DOM, I would like to ask:
- Do you agree that imperative Shadow DOM used to create elements as you suggested at #510 (comment) will also cause problems with SEO?
- Does the declarative form of it would introduce any new issue that was already covered via imperative usage?
I think we could focus here to introduce a new feature; useful for a lot of other reasons; make it no worse than imperative in terms of SEO; keep SEO in mind to hopefully improve it a little bit. But I don't think our goal here is to solve all SEO problems with Web Components through Declarative Shadow DOM.
For that, we could create a separate issue.
Speaking of examples.
It's not clear to me what happens if React updates content in the
<shadowroot>
. For example, when that component re-renders if the name is changed, it will update the content in the shadow root (but only the text node where the name is). Will this reflect in the real shadow root?
Given we don't have native support for data binding with {}
yet (#150), and even here we had an idea to make <shadowroot>
element inert after it was parsed and executed (somewhat like <script>
), I'd rather stick to the second sample.
My naive answer here would be, either
- Once
<shadowroot>
was parsed, any change to its content will be ignored,
then React or any other framework/library could:- disallow data binding in
<shadowroot>
(as for<script>
?), - provide a sugar that aliases
.attachShadow + .shadowRoot
or
- disallow data binding in
- We treat
<shadowroot>
as an alias to `parent_div_element.shadowRoot
The second one looks pretty safe to me.
If you have JS, SD, and DSD all is good, If you have JS you can somewhat polyfill SD and DSD.
If you don't have JS, but agent supports DSD, you can SSR as expected.
If you want to improve the order for the browser that does not understand Shadow DOM but still used scoped styles for ones that do, you can achieve it today, w/o declarative SD, by just by moving content to the light DOM.
<div>
#shadowroot
<style>{`:host { border: 1px solid black; }`}</style>
<slot name="greeting">Hello</slot> <slot name="name"/>
#/shadowroot
<span slot="greeting">Hola</span>
<span slot="name">Tomek</span>
</div>
Or if you want to SSR it for the browsers w/o JS, that does not implement Declarative SD yet, you can do the <shadowroot>
+ SD polyfill job by yourself (scoping styles)
I do agree that's problematic. But I think we cannot create such a perfect spec, that will work as desired even in agents that do not implement it ;)
That's not true, see #510 (comment) and also a following comment by @tabatkins about the problems with your suggestion. As I said then, it would really be better to discuss that change separately from a declarative form for attachShadow(). A new issue would be appreciated.
Hey @annevk, yeah sorry, my bad. I just meant that's the feeling I've got when talking to some (outside of this thread, too). Wasn't trying to complain. I'll create an issue to discuss.
I have updated my custom element (https://github.com/tomalec/declarative-shadow-dom) match the ideas from this issue closer. I hope, you can use it for further fiddling and experiment with the idea.
Unfortunately, I could not stamp on the constructor, due to platform limitations, so it stamps on connectedCallback
or when a method is invoked manually.
Behavior:
- It's customized
<template>
element, so for the environment that does not support Custom Elements and Shadow DOM it's inert and does nothing, - When
connected
stamps it's content to theparentElement
s shadow root (creates new if there is none) and removes itself - I could not do that on constructor, asparentElement
isnull
, - There is a method to stamp it manually if you want to do that earlier.
Is there an explanation of the SEO problem? On the face of things, it seems like if Shadow DOM is a SEO problem, "you are holding it wrong". AFAICT, Shadow DOM is supposed to be used to decorate the DOM such that the search-relevant content appears in the non-shadow parse tree when the search crawler doesn't implement JavaScript and Shadow DOM.
If anything, it seems like Shadow DOM could be a vector for black-hat SEO (making users and search engines that don't run a full browser engine see different things).
What am I missing?
I tried to aggregate above discussions, to a slightly more structured proposal at https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Declarative-Shadow-DOM.md I would really appreciate any feedback.
@tomalec Thank you! I didnβt see a declarative option to let styles from outside penetrate into the shadowroot. Hereβs an example
<style>p {font-weight:bold; color:blue}</style>
<shadowroot mode="β¦">
<style>p {color:red}</style>
<p>Foo</p>
</shadowroot>
<p>Bar</p>
I want Foo to be bold and red and Bar to be bold and blue. Does your proposal include something that permits it β did I miss something?
The once specβed <style scoped>
has been dropped in favor of Shadow DOM β a declarative version of Shadow DOM should support an option to achieve this kind of one-way encapsulation (nothing penetrates from inside out, but styles can penetrate from outside in).
@prlbr If I get your use-case correctly, what you are looking was once there as a ::shadow
but was removed in V1. There is alternative proposition, to let you expose from shadow root, the parts to style using part
attribute, then style them from outside using ::part()
and ::theme
, see WICG/webcomponents#300
I would consider that a feature needed for Shadow DOM in general, once it will become available in Shadow DOM created imperatively, it should work in Declarative one as well.
If I get your use-case correctly, what you are looking was once there as a
::shadow
but was removed in V1. There is alternative proposition, to let you expose from shadow root, the parts to style usingpart
attribute, then style them from outside using::part()
and::theme
, see WICG/webcomponents#300
No, not really. That proposal makes things very complicated. The author of the shadow-root has to prepare all the parts that can be restyled from the outside and the author of the main document needs to adhere to the vocabulary defined by the shadow root author and tailor his style sheets specifically for that component.
What I mean is much simpler: An option on the shadowroot (something like mode='let-all-styles-in-but-none-out'
) element that allows all styles from outside take effect inside the shadow dom (but styles that are defined inside the shadow dom for the same properties have higher specifity and would take precedence) without any further syntax requirements. A simple flag that changes shadow doms two-way encapsulation into a one-way encapsulation.
A simple flag that changes shadow doms two-way encapsulation into a one-way encapsulation.
Seems to be an issue and a topic on its own. That applied for Shadow DOM, regardless whether it's imperative or declarative. I suggest starting a new thread for that.
All I can suggest now is
<link rel="stylesheet" href="style.css"><!-- p {font-weight:bold; color:blue} -->
<shadowroot mode="β¦">
<link rel="stylesheet" href="style.css">
<style>p {color:red}</style>
<p>Foo</p>
</shadowroot>
<p>Bar</p>
I don't have ambition for Declarative Shadow DOM to cover all <style scoped>
use cases, but at least those which are solved by imperative JS API, without a need to in fact write CSS-in-JS.
But I'd really prefer to talk about it in a separate thread, as it applies to existing imperative Shadow DOM as well.
This is really great! Other than the new element name <shadowroot>
which is meeeehhh and clumsily packs two words into an element name vs. every other element in HTML (Kinda reads like <iamatemplate>
vs <template>
or <paragraphstart>
vs <p>
or <bodyroot>
vs <body>
), I'm glad there is activity in this direction.
I haven't gone back through the thread to see what discussion there was around this name, but hopefully that won't be a final proposal. The concept itself is good.