-
Notifications
You must be signed in to change notification settings - Fork 384
Proposal for changes to manage Shadow DOM content distribution
This document considers real-world usage of Shadow DOM in three large production component libraries, and identifies a number of issues with the current Shadow DOM design. This document proposes to address those issues by updating the Shadow DOM spec to:
- Allow elements to identify insertion points by name rather than a CSS selector.
- Support standard subclassing semantics by which a subclass can populate a named base class’ insertion point, and optionally expose an insertion point with the same name.
Significantly, these changes are compatible with the notion of supporting a maximum of one shadow root per element.
This document is not intended to be a complete, final proposal for a spec change. Rather, it is intended primarily to stimulate discussion at the Web Components Face-to-Face meeting on April 24, 2015.
Thanks to early work by Google’s Blink and Polymer teams, developers have now had 3 years of opportunity to create web components. Generally speaking, Shadow DOM is showing itself to be a useful way of encapsulating functionality that can be easily added to a web page without fear of interference between the component and page. That said, a number of issues have become apparent as people have attempted to use the spec’ed design to create non-trivial component libraries.
The following comments are based on analysis of three public web component libraries: Polymer’s core- elements, Polymer’s paper- elements, and the Basic Web Components’ collection of basic- elements. Collectively, these libraries contain over 100 non-trivial web components that attempt to deliver meaningful functionality to a broad audience, and they represent the state of the art in web component design.
-
The spec’ed ability to include default content in a element is never used. In the three component libraries described above, not one component intended for public use uses this feature of
<shadow>
. -
The
<shadow>
element is optimized for wrapping a base class, not filling it in. In practice, no subclass ever wants to wrap their base class with additional user interface elements. A subclass is a specialization of a base class, and specialization of UI generally means adding specialized elements in the middle of a component, not wrapping new elements outside some inherited core.
In the three component libraries described above, the only cases where a subclass uses <shadow>
is if the subclass wants to add additional styling. That is, a subclass wants to override base class styling, and can do so via:
<template>
<style>subclass styles go here</style>
<shadow></shadow>
</template>
One rare exception is core-menu, which does add some components in a wrapper around a <shadow>
. However, even in that case, the components in question are instances of <core-a11y-keys>
, a component which defines keyboard shortcuts. That is, the component is not using this wrapper ability to add visible user interface elements, so the general point stands.
As with the above point, the fact that no practical component has need for this ability to wrap an older shadow tree suggests the design is solving a problem that does not, in fact, exist in practice.
- Components rarely (never?) take advantage of the ability to use CSS selectors to distribute non-contiguous content to insertion points. The
<content>
element permits aselect="h1"
attribute that gathers all<h1>
elements (perhaps interleaved with other elements) and distributes that set to that insertion point. In practice, this is not especially useful.
In the three libraries, the most common select
clause is referencing a CSS class with the intention of using that CSS class name as a property name. In such cases, the select
clause takes a CSS class name, not necessarily to take advantage of CSS features, but to implement a convention. The convention is working around the limitation that content cannot be selected by name.
Alternatively, a number of core- and paper- elements use a select clause with a named attribute, again with the intention of trying to designate a name for an insertion point. E.g., core-drawer-panel uses <content select="[drawer]">
pulls a single light DOM child element with a plain drawer
attribute into the drawer panel. Here we see a competing convention trying to achieve the same result as using a CSS class name — both are trying to specify to stick an element into an insertion point by name.
Using CSS selectors to manage content distribution may support use cases that do not appear to come up often in practice. Meanwhile, the very flexibility of offering CSS selectors is producing competing conventions that are trying to fulfill an underlying need that is not being met directly.
- A component cannot define a specific insertion points with
select
clauses that distributes nodes after a general<content>
insertion point with noselect
clause. E.g., a page template component wants to define page header and footer elements that appear respectively above and below a general main region:
<template>
<content select=".header"></content>
<content></content>
<content select=".footer"></content>
</template>
Unfortunately, a page template cannot be constructed this way: the general <content>
element with no select
attribute will pick up all content not previously distributed — including the footer. (Discussion on a Shadow DOM bug has proposed a means of addressing this problem.)
This issue has come up multiple times in practice, and places non-trivial constraints on the UI which can be constructed with web components. While DOM content can be moved out of document order using, e.g., CSS Regions, that spec is not yet widely supported, and in any event is a cumbersome solution to a common need.
- Component subclasses cannot fill in insertion points defined by their base classes. This hinders the creation of well-factored component class libraries. This issue is examined in an example below. As a practical effect of this limitation, none of the three of the component class libraries discussed here make significant use of subclassing. In nearly all interesting cases, the libraries are forced to extend a base class’ behavior by composing an instance of it, rather than inheriting from the base class.
This goes against long experience in client user interface class library design (Windows Presentation Foundation, Apple Cocoa, to name just two), where subclassing is a fundamental means of carefully separating UI concerns.
Because Shadow DOM does not support this feature, the three existing web component libraries discussed cannot be used as a reference point. Since the desired level of subclassing isn’t supported, it’s not possible to count how many times these libraries would have taken advantage of it.
To provide some concrete data regarding this feature, an earlier general-purpose component library called QuickUI was analyzed. This library, the predecessor to the Basic Web Components library, had ample support for subclassing. QuickUI’s class library contained 94 components, of which 36 are subclasses of another class. Of those 36, 12 components were subclasses that populated insertion points defined by a base class. So approximately ⅓ of the components used subclasses, and ⅓ of those filled in base class insertion points. QuickUI’s base class library contained only general-purpose components, and it is typically proprietary specializations of general components that most need the ability to fill in base class insertion points. Applications building on top of general-purpose components will likely see a higher portion of their app-specific components use subclassing.
It would be ideal if some of these issues could be addressed while preserving the core value of Shadow DOM.