diff --git a/text/0114-light-dom.md b/text/0114-light-dom.md new file mode 100644 index 00000000..7f91550a --- /dev/null +++ b/text/0114-light-dom.md @@ -0,0 +1,342 @@ +--- +title: Light DOM Component +status: APPROVED +created_at: 2020-09-10 +updated_at: 2021-09-09 +champion: Nolan Lawson (nlowason) | Mohammed Abdul Sattar (abdulsattar) +rfc: https://github.com/salesforce/lwc-rfcs/pull/44 +--- + +# Light DOM Component + +## Summary + +As of today, all the LWC components inheriting from `LightningElement` render their content to the shadow DOM. This proposal introduces a new kind of component which renders its content as children in the Light DOM. + +## Basic example + +When the Shadow DOM is turned off for a component, its content is not attached to its shadow-root, but to the element itself. Here is an example, showing whenever Shadow DOM is on or off: + +_Shadow DOM_ + +```html + + #shadow-root (open) + |
+ | Blue Shadow: + | ... + | + |
+ +``` + +_Light DOM_ + +```html + +
+ Blue Light: + ... + +
+ +``` + +As a result, when the content of a component resides in the Light DOM, it can be accessed like any other content in the Document host, and thus behave like any other content (styling, APIs, accessibility, third party tooling...). + +## Motivation + +Consumer applications require DOM traversal and observability of an application’s anatomy from the document root. Without this, theming becomes hard and 3rd party applications do not run properly: + +- **Theming and branding** + + Because of the CSS isolation, theming is made harder. Typically, theming is done via APIs (CSS properties) and/or CSS theming parts (`::part`). Both come with caveats and issues. Theming is critical for consumer apps, where custom branding is a must have. Some new APIs, like `::theme`, are investigated but they won’t be pervasively available before years. + [Styling is critical to web component reuse, but may prove difficult in practice](https://component.kitchen/blog/posts/styling-is-critical-to-web-component-reuse-but-may-prove-difficult-in-practice) + +- **Third party integrations** + + Third party tools need to traverse the DOM, which doesn't work with Shadow DOM and standard browser query APIs such as `querySelector` and `querySelectorAll`. This affects analytics tools, personalization platforms, commerce tools, etc. + +- **Testing software** + + Tools like Selenium, Cypress etc. face the same issues as third party tools when it comes to traversing the DOM. + +Introducing components rendering to the Light DOM opens new possibilities where applications can render most of their content in the Light DOM, but also keep individual widgets (e.g. `select` tag) and leaf components that render in the Shadow DOM. This approach combines the best of both worlds by solving the issues presented above and offering strong encapsulation when needed. +![shadow spectrum](./shadow-spectrum.png?raw=true "Shadow Spectrum") + +### Prior Art + +Most of the libraries designed to support Shadow DOM also propose a Light DOM option, with a variety of Shadow DOM features (slots, scoped styles, etc.). It includes: + +- [StencilJS](https://stenciljs.com/docs/styling#shadow-dom-in-stencil) +- [LitElement](https://lit-element.polymer-project.org/api/classes/_lit_element_.litelement.html#createrenderroot) +- [MS Fast Element](https://fast.design/docs/fast-element/working-with-shadow-dom#shadow-dom-configuration) + +## Detailed design + +### `LightningElement.renderMode` + +Toggle between light DOM and shadow DOM is done via a new `renderMode` static property on the `LightningElement` constructor. `renderMode` is an enum with `shadow` (default) and `light` as values. When the value is `shadow`, the LightningElement creates a shadow root on the host element and renders the component content in the shadow root. When set to `light` the component renders its content directly in the host element light DOM. + +```js +import { LightningElement } from "lwc"; + +// Example of a shadow DOM component +class ShadowDOMComponent extends LightningElement { + static renderMode = `shadow`; // can be omitted since it's default +} + +// Example of a light DOM component +class LightDOMComponent extends LightningElement { + static renderMode = `light`; +} +``` + +The `renderMode` property is looked up by the LWC engine when the component is instantiated to determine how it should render. Changing the value of the `renderMode` static property after the instantiation doesn't influence whether components are rendered in the light DOM or in the shadow DOM. + +It also means that developers should be careful not to override the `renderMode` static property value when inheriting from another component. Switching a component mode from shadow DOM to light DOM (and vice-versa) in the child class would certainly break logic in the base class. + +```js +import { LightningElement } from "lwc"; + +class Base extends LightningElement {} + +class Child extends Base { + // ⚠️ Changing the shadow static property value in a component class inheriting from a base + // will certainly class some unexpected issue. + static renderMode = `light`; +} +``` + +### `LightningElement.prototype.template` + +When rendering to the shadow DOM, `LightningElement.prototype.template` returns the component associated `ShadowRoot`. When rendering to the light DOM, `LightningElement.prototype.template` value is `null`. + +### DOM querying + +This proposal doesn't change the way component authors query the light DOM. Component can query their children elements via `LightningElement.prototype.querySelector` and `LightningElement.prototype.querySelectorAll`. It means that turning a shadow DOM component to a light DOM one, all the occurrences of `this.template.querySelector` have to replaced `this.querySelector`. + +### Template + +Any template associated with a light DOM component should use `lwc:render-mode="light"` to indicate that it is a light DOM template: + +```html + +``` + +Similarly, shadow DOM components can use `lwc:render-mode="shadow"`, but it's not necessary since it's the default value for that attribute. + +For light DOM components, this directive is needed to allow the compiler to behave differently for light DOM components versus shadow DOM components. For example, the `lwc:dom` directive is disallowed within light DOM templates (since it's only needed for synthetic shadow DOM). + +### Styles + +Until now styles used in LWC components were scoped to the component thanks to shadow DOM (or synthetic shadow DOM) style scoping. In the light DOM, component styles naturally leak out of the component; LWC doesn't do any style scoping out of the box. Developers are in charge of writing selectors that are specific enough to target the intended element or pseudo-element. + +To support the cases where a shadow DOM element composes a light element, light DOM styles are required to be injected to the closest root node. For a given light DOM element, if all the ancestor components are also light DOM components, the component style sheet will be injected in the document ``. Otherwise, if any of the ancestors is a shadow DOM component, the style has to be injected into the closest shadow root. Upon insertion of a light DOM element, LWC does the following steps: + +1. look up for the closest root node (`Node.prototype.getRootNode()`) +1. insert the stylesheet if not already present: + - if the root node is the HTML document, the style sheet is inserted as a ` + + +``` + +In this case, `` is able to style `` via `:host`. Unlike shadow components, `:host` does not refer to the component but instead to its actual shadow host. + +This behavior is according to the specification – `:host` anywhere in the DOM refers to the nearest containing shadow root. At the top (document) level, it does nothing. + +#### Synthetic shadow + +In the case of synthetic shadow, no attempt is made to scope light DOM styles as described above. The styles are simply inserted as-is, and within a synthetic shadow root they will bleed into the rest of the page, including components using synthetic shadow DOM. + +This has some precedent – synthetic shadow always allowed global styles to leak into components (although styles could not leak out of those components). So in a synthetic shadow world, light DOM components' CSS will essentially act like +`