I wrote earlier this year about WebC, the new framework for templating HTML in 11ty. WebC's standards-aligned approach is refreshing and encouraging, and my conclusion was that it's support for the real-deal web components standards needs some polishing.
Since then I've been working on porting the docs site for Apollo Elements
to WebC. Some of WebC's design choices make it harder to use web components APIs
as they are "intended", or at least as I interpret the spec's intent. In
particular, support for Declarative Shadow DOM is still in what I'd call a
preliminary state. WebC provides some examples of DSD components on their github account, but the
examples provided don't cover normal DSD use with <slot>
elements.
WebC's "slot" vs Web's "slot"
As of WebC version 0.11.4, the framework provides two different kinds of
<slot>
elements to developers that work in very different ways. Developers
will have to choose between using WebC's non-standard <slot>
component, which
transfers content from component children to the shadow root, and native
<slot>
elements, which project content from the light DOM to the shadow root.
In order to get the native behaviour, developers will have to add the
webc:keep
attribute to their <slot>
elements; if they don't, instead of
getting a native slot, they'll get a "compiler portal" which moves content from
the host document to the element's shadow DOM.
The distinction is subtle but critical, and might lead to developer confusion. So consider this WebC component:
<!--- web-see.webc --->
<template shadowrootmode="open">
<style>
p { color: red; }
::slotted(*) { color: blue; }
</style>
<p>Shadow!</p>
<slot></slot>
</template>
Now let's apply this component to a page and slot in some content.
<web-see>
<p>Light!</p>
</web-see>
What colour should the word "Shadow!" be? What colour the word "Light!"?
MDN leads us to believe that our slotted content would appear in the
document, and that our ::slotted
rule would apply to the
slotted content. "Shadow!" should be red, "Light!" should be blue.
WebC's <slot>
server component (remember: not the same as the web platform's
<slot>
element) will however cause the light content to be moved from the
document into the shadow root. The ::slotted
selector will no longer apply,
the content will not be accessible from the light DOM. "Shadow!" will be red.
<web-see>
<template shadowrootmode="open">
<style>
p { color: red; }
::slotted(*) { color: blue; }
</style>
<p>Shadow!</p>
<p>Light!</p>
</template>
</web-see>
More importantly, I was unable to use the native <slot>
element at all in DSD
templates. The 11ty build hung and eventually crashed with a heap overflow. WebC
provides some features like webc:raw
, but that doesn't work consistently here.
Solving the problem
I'm certain that WebC's DSD support will improve over time, and I'm confident
that it will eventually take a more standards-first approach to <template>
and
<slot>
elements, especially once Mozilla implements the spec. In the mean
time though, what can we do in userland to solve the problem? Well, we can
side-step the framework altogether:
<!--- web-see.webc --->
<template shadowrootmode="open">
<style>
p { color: red; }
::slotted(*) { color: blue; }
</style>
<p>Shadow!</p>
<!--- <slot></slot> --->
<webc-dsd-slot-workaround></webc-dsd-slot-workaround>
</template>
By replacing our <slot>
element with this silly tagname, we can go back after
the fact in eleventy's addTransform
API, and use parse5 to replace all those
workarounds with native slots:
And so, with one ugly hack, we restore the native behaviour. When WebC's support improves, we'll have to do a project find-and-replace and remove our 11ty transform, but that's it.
I hope this workaround is helpful to you and I look forward to updating this post with news of improved DSD support in WebC.