Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Light DOM Support #44

Merged
merged 30 commits into from Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cb55a94
RFC: Add Light DOM RFC
abdulsattar Sep 3, 2020
72fe402
chore: remove misleading line
abdulsattar Mar 17, 2021
a306839
fix: html template example
abdulsattar Mar 17, 2021
9bf37b0
chore: simplify language
abdulsattar Mar 17, 2021
bad70a8
fix: address review feedback
abdulsattar Mar 18, 2021
6b6572e
fix: move implementation details out, tweak text
abdulsattar Mar 18, 2021
f791aec
fix: fix grammar
abdulsattar Mar 18, 2021
990bc6a
chore: add section on querying
abdulsattar Mar 22, 2021
dea55cf
Update style section based on feedback
pmdartus Apr 1, 2021
0a87dea
Add open questions section
pmdartus Apr 1, 2021
a9b1c55
chore:
pmdartus Apr 12, 2021
ea27928
Replace MacroElement with static field
pmdartus Apr 12, 2021
3a62c00
chore: update based on discussions
abdulsattar Apr 15, 2021
fbcab6b
chore: minor copyedit tweaks
nolanlawson Apr 15, 2021
b2df06f
chore: add details on slots and update based on latest convo
nolanlawson Apr 15, 2021
36a77b8
fix: add more details on light DOM global styles
nolanlawson Apr 20, 2021
8313490
fix: respond to comments
nolanlawson Apr 21, 2021
340f4b9
chore: fix security section
abdulsattar Apr 26, 2021
a00f719
Simplify RFC structure
pmdartus Apr 26, 2021
653b680
fix: s/shadowDOM/shadow/g
nolanlawson Apr 26, 2021
846f6b9
fix: update text/0000-light-dom.md
nolanlawson Apr 30, 2021
74f6da6
fix: update text/0000-light-dom.md
nolanlawson May 5, 2021
458cc0f
fix: update text/0000-light-dom.md
nolanlawson May 5, 2021
619721a
fix: put lwc:no-shadow on <template>, not on <slot>
nolanlawson May 6, 2021
798268f
fix: add some usecases for light versus shadow
nolanlawson May 6, 2021
a3f6d71
fix: reword section to make it clearer
nolanlawson May 6, 2021
c8a76f0
feat: add render-mode instead of static `shadow`
abdulsattar May 20, 2021
e735d3e
fix: add text explaining need for directive
nolanlawson May 21, 2021
3460e3a
chore: remove references to shadow
abdulsattar Jun 1, 2021
baa79bf
Reflect updated state of Light DOM
pmdartus Sep 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
315 changes: 315 additions & 0 deletions text/0000-light-dom.md
@@ -0,0 +1,315 @@
---
title: Light DOM Component
status: DRAFTED
created_at: 2020-09-10
updated_at: 2020-09-10
champion: Philippe Riand (priand), Ted Conn (tconn)
pr: https://github.com/salesforce/lwc-rfcs/pull/44
---

# RFC: 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
nolanlawson marked this conversation as resolved.
Show resolved Hide resolved

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
<app-container-blue-shadow>
#shadow-root (open)
| <div>
| <b>Blue Shadow:</b>
| <span class="counter">...</span>
| <button type="button">Add one</button>
| </div>
</app-counter-blue-shadow>
```

_Light DOM_

```html
<app-container-blue-light>
<div>
<b>Blue Light:</b>
<span class="counter">...</span>
<button type="button">Add one</button>
</div>
</app-counter-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**

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As Dean said in DRB today, UIP has the same (near-term future) requirements around theming and branding for LEX and LWR apps as Communities.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe the LEX requirements are the same as what Communities is requesting because the number of actors in the system is very different. Let's discuss offline.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hsterlingsfdc Could you make sure to bring the LWC team into the loop when this will be discussed/designed? I would like to better understand the LEX and LWR requirements in this regard.


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 breaks with Shadow DOM and the existing browser APIs (querySelector, ...). Note that the use of Light DOM fixes for Light DOM components, but not for native Shadow DOM ones if the page contains any: Analytics tools, Personalization platforms, Commerce tools like PriceSpider or Honey...
nolanlawson marked this conversation as resolved.
Show resolved Hide resolved

- **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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image content: Remove "Today" and "Where we want to be" label. The RFC should not present points of view but rather preset facts.


### 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list of invariants and constraints is missing. From the live discussion on this RFC today it became obvious that there are many you're working with.

  • Support all standard Light DOM APIs at runtime
  • What the developer codes in the template is what they see in the rendered DOM
  • Align with standards where possible; when no standard exist align with pre-existing LWC concepts, and finally other frameworks
  • A constraint is that the LWC compiler operates on 1 file at a time, not one component (eg HTML + Javascript)

There are many others that need to get captured here. This'll bring visibility to the rationale driving this design.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still fuzzy about:

What the developer codes in the template is what they see in the rendered DOM

what I see in the PR is that the content of the slot is flatten, so the slot element itself never gets rendered.


### `LightningElement.shadow`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this remind me of a presentation of a good friend (https://www.youtube.com/watch?v=HYl7ReNB5TA), basically, this API is wrong from the design point of view, but it is also wrong when you look at it with the backward compatibility lenses (which I understand is very edge case, but still problematic).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's too late to change this, the API is not considered stable today. We debated between @nolanlawson and @abdulsattar this couple of weeks ago. I purposefully made the API name generic because I had in mind to reuse the same shadow static field in the future to developers configure the shadow root:

// default - shadow DOM (mode: open)
class extends LightningElement {} 

// shadow DOM (mode: open)
class extends LightningElement {
  static shadow = true;
}

// light DOM
class extends LightningElement {
  static shadow = false;
}

// shadow DOM (mode: closed)
class extends LightningElement {
  static shadow = {
    mode: 'closed'
}

@caridy I know that you tinkered with something similar in the past. Where did your previous research lead you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another argument in favor of shadow = false over light = true: web developers tend to think of "shadow DOM" as a fancy new thing, and "light DOM" as the default. In fact, "light DOM" was just "the DOM" for decades.

So developers are more likely to immediately understand shadow = false compared to light = true, since they understand shadow as this weird new bleeding-edge-standards thing that they need to have a separate mental model for, as opposed to light, which is just how the web worked for decades.

One counter-argument to that, though, is that developers working with LWC have probably had to acquaint themselves with shadow already, as it's the default in LWC. So I see good arguments both ways.


Toggle between light DOM and shadow DOM is done via a new `shadow` static property on the `LightningElement` constructor. This accepts a `boolean` value and is `true` by default. When set to `true`, the LightningElement creates a shadow root on the host element and renders the component content in the shadow root. When set to `false` the component renders its content directly in the host element light DOM.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When dealing with boolean values, as a best practice, the default should be false. It looks like this was maybe modeled after Stencil's API which disables shadow DOM by default, whereas we enable it by default. I would prefer to go with something like static shadowDisabled = true or static disableShadow = true.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. The idea was to make the shadow field accept more complex objects in the future. For example, we would also use the shadow field to override the shadow DOM mode:

export default class extends LightningElement {
  static shadow = {
    mode: 'closed'
  };
}

FYI @caridy as you were working on something similar to configure LWC components.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that defaulting to true is weird, but to me the false versions feel weirder:

static light = false // "light" what? "light" is an overloaded word
static lightDom = false // verbose, also we now need to choose between lightDom and lightDOM
static noShadow = false // double negative

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with @nolanlawson on the point above. If we were to change the flag name and use a different mechanism to configure the shadow root, I would vote for @caridy's static renderToLightDOM: true.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really only care that the default is false, but I'll just point out that the double-negative usually isn't a concern because you don't need to set the property to get the default behavior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about this some more, I'm ambivalent about renderToLightDOM. Although the light DOM doesn't exist in synthetic shadow, both light DOM and shadow DOM do exist in native shadow. We want to indicate that we're going to take what would normally have been rendered in the light DOM and slotted into the shadow DOM, and do some magic without attaching a shadow root. renderToLightDOM doesn't seem to capture that, but I don't have any other suggestions at the moment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point. shadow = false implies "no shadow at all", which is true. Whereas light = false implies "no light at all," which is not true because it's actually a mix of light and shadow (due to slots).


```js
import { LightningElement } from "lwc";

// Example of a shadow DOM component
class ShadowDOMComponent extends LightningElement {}

// Example of a light DOM component
class LightDOMComponent extends LightningElement {
static shadow = false;
}

// Default value
console.log(LightningElement.shadow); // true
```

The `shadow` property is looked up by the LWC engine when the component is instantiated to determine how it should render. Changing the value of the `shadow` static property after the instantiation doesn't influence whether components are rendered in the light DOM or in the shadow DOM.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it influence components created after the shadow property's value is changed?

Should this value be made non-writable at runtime?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of how we're handling Babel loose mode, I'm not sure it's possible to enforce this. I could be wrong though.

As-is though, if shadow is redefined after the constructor() is called, then the change would have no effect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, so it looks like it's possible, but we have two options:

  1. Disallow setting shadow a second time.
  2. If any instance of FooElement runs its constructor(), disallow setting shadow again.

The downside of the first is that users may legitimately want to set shadow more than once (complex app logic) before calling the constructor. The downside of the second is that it's a bit odd than a single instance of a class would have an impact on the entire class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current implementation updating the shadow attribute after the component is created has no effect. The attribute is read during the construction phase and then cached in the VM for the lifetime of the component.

Making the static field non-writable doesn't provide much guaranty if the field is defined as a getter:

import { LightningElement } from 'lwc';

export default class FooElement extends LightingElement {
  static get shadow() {
    return Math.random() > 0.5;
  }
}

We can for example cache the value shadow value after the FooElement instantiation and reuse it for all the future FooElement instantiations. I am not personally in favor of making the first component instantiation special.


It also means that developers should be careful not to override the `shadow` 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 shadow = false;
}
```

### `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`.

### 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 `<head>`. 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 `<style>` element in the `<head>`.
- if the root node is a shadow root, the stylesheet is inserted as a `<style>` element as the first shadow root child.

> As an optimization, the above algorithm may be substituted by the equivalent usage of [constructable stylesheets](https://developers.google.com/web/updates/2019/02/constructable-stylesheets) in supporting browsers. This means that developers should not rely on the `<style>` elements being inserted at a specific place in the DOM; it's an implementation detail.

It is important to notice that the order in which light DOM components are rendered impacts the order in which stylesheets are injected into the root node and directly influences CSS rule specificity.

#### `:host` and `:host-context()`

Also worth noting is that `:host` and `host-context()` are inserted as-is. Therefore, a light DOM component can style its shadow host. For example:

```css
/* x-light.css */
:host {
background: blue;
}
```

```html
<x-shadow>
#shadow-root
<style>
:host {
background: blue;
}
</style>
<x-light></x-light>
</x-shadow>
```

In this case, `<x-light>` is able to style `<x-shadow>` 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
`<style>`s inserted into the document `<head>`.

#### Scoped styles

Different approaches to layer style scoping on top have been discussed while designing Light DOM, such as introducing a new file extension for automatic style scoping (`.scoped.css`) or using a `<style scoped>` element in the template. This can be addressed in a future RFC.

### Slots

As mentioned before, the component composition model in LWC is provided by slots. Light DOM will provide the same mental model for developers building Light DOM components.

In Light DOM, `<slot>` will denote the place where the slotted component will be attached. The `<slot>` element itself won't be rendered. The slotted content (or the fallback content) will be flattened to the parent element at runtime.

Since the `<slot>` element itself isn't rendered, adding attributes or event listeners to the `<slot>` element in the template will throw a compiler error.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This affects builders if it is not a real slot. This may mean every single builder (LAB is only one of the many builders Salesforce has) has to add special code to process components dragged into another component's light dom slot.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately it would be impossible to use real slots for light DOM, because slots as defined by the web platform are a part of shadow DOM.

Whatever we decide on for "light DOM slots," it will not be the same as shadow DOM slots.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Follow-up question: Today, we have internal LWC container components like tabset and accordion that are exposed in LAB (via an aura wrapper). ISV LWC container components (which are just components that contain slots that the builder understands) are on the roadmap. It sounds like the recommendation is for ISVs to build these with Light DOM, since they are general purpose and just meant to house other content.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's really up to each use case. I think we would need to draft a longer document about the strengths and weaknesses of light DOM versus shadow DOM.

For instance, even a tabset component may want to protect its shadow content from being unintentionally styled by the consumer, so shadow DOM may be an appropriate choice there. Also a tabset will inevitably use slots, meaning that there could be performance or timing differences between shadow and light DOM. (E.g. eager versus lazy – a tabset containing 10 slots that only renders 1 might still pay the cost of rendering the other 9, if slots are eager, as they are in native shadow DOM.)

But yes, in general, I think your intuition is correct. Tabsets and accordions don't themselves contain sensitive content, so they could be good candidates for light DOM.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the issue is broader than builders: any system that generates LWC components needs to be updated if the LWC syntax differs in how slotted content is provided to a Light DOM-enabled component.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other thoughts:

  1. Given the styling/perf differences, we should consider whether some components are configurable by the admin
  2. We could use guardrails in the builder to guide admins towards choosing the correct configuration by a) visually showing the difference in preview and b) predicting perf characteristics

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any system that generates LWC components needs to be updated

Side note: any system that generates LWC components will need to be updated to respect the no-shadow directive anyway.


Due to the above differences from regular `<slot>`, LWC compiler will enforce the presence of `<slot lwc:no-shadow>` on Light DOM slots to make it explicit to the user that these are different slots.

In terms of timing, slots in light DOM will behave similarly to the current synthetic shadow slots.

<details>
<summary>Light DOM vs. shadow DOM timing comparison</summary>
For example:

```html
<!-- x/slottable.html -->
<template lwc:no-shadow>
<slot lwc:no-shadow name="foo"></slot>
nolanlawson marked this conversation as resolved.
Show resolved Hide resolved
<slot lwc:no-shadow></slot>
<slot lwc:no-shadow name="bar"></slot>
</template>
```

```html
<!-- app.html -->
<template>
<x-slottable>
<x-baz></x-baz>
<x-foo slot="foo"></x-foo>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note:

On the consumer side in the app.html, the example below is using the standard slot attribute. From the compiler standpoint, it is impossible to know if the <x-slottable> uses the light DOM or the shadow DOM slotting algorithm.

I was under the original impression that we could also ask developers to use a custom directive to indicate to the template compiler to use the light DOM slotting algorithm to avoid this branching at runtime. For example using slot:no-shadow instead of slot. That said I came to the conclusion that it is not possible.

While this custom directive works well with named slots I don't see how this would work with the default slot. In the example below I don't see how it is possible to indicate that the text content should go to the light DOM instead of the shadow DOM.

<template>
  <x-slottable>
    Some content
  </x-slottable>
</template>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point. I don't know how to resolve it, so perhaps slots should be moved to a separate RFC entirely.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussing this in a meeting, it seems the only way to resolve the above example is to move the logic to the runtime. I.e. we have to figure out at runtime rather than compile time whether a slot is shadow or light.

The only workaround I can think of would be to make a hard requirement on light DOM slots that they have some top-level element that we can mark:

<template>
  <x-slottable>
    <div lwc:slot> <!-- required -->
      Some content
    </div>
  </x-slottable>
</template>

This isn't great for developer ergonomics, but it would allow us to figure this out at compile time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We decided that whether slots are light or shadow will be determined at runtime.

<x-bar slot="bar"></x-bar>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about this, I don't think the slot attribute should be rendered at runtime in the light DOM.

We decided not to render the <slot> element at runtime to prevent potential issues where the light DOM <slot> connects to the parent shadow root. If we were to keep the slot attribute at runtime for the light DOM we could also run into unexpected slotting behaviors at runtime when mixing shadow DOM slot and light DOM slots.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this run into the same problem where the consumer component needs to know whether <x-slottable> is a light DOM element or not? If so, it would maybe argue for no-shadow:slot="foo" or lwc:slot="foo" or something like that.

Then again as you said above, that does not solve default slots. Unless we force something like lwc:slot="default".

</x-slottable>
</template>
```

(Note that the ordering of the named slots and default slot is different between the slottable component and the component using it.)

This results in the timing:

- `slottable` `constructor`
- `slottable` `connectedCallback`
- `foo` `constructor`
- `foo` `connectedCallback`
- `foo` `renderedCallback`
- `baz` `constructor`
- `baz` `connectedCallback`
- `baz` `renderedCallback`
- `bar` `constructor`
- `bar` `connectedCallback`
- `bar` `renderedCallback`
- `slottable` `renderedCallback`

Note that this timing is different from the equivalent for slots in native shadow DOM:

- `slottable` `constructor`
- `slottable` `connectedCallback`
- `baz` `constructor`
- `baz` `connectedCallback`
- `baz` `renderedCallback`
- `foo` `constructor`
- `foo` `connectedCallback`
- `foo` `renderedCallback`
- `bar` `constructor`
- `bar` `connectedCallback`
- `bar` `renderedCallback`
- `slottable` `renderedCallback`

In native slots, the ordering is based on the order in the _consumer_ component, whereas in synthetic and light DOM slots, it's based on the ordering in the _slotted_ component.
</details>

#### Lazy slots

The main reason for this difference is that synthetic shadow slots and light DOM slots are both _lazy_. For example:

```html
<!-- x/slottable.html -->
<template lwc:no-shadow>
<template if:true="{foo}">
<slot lwc:no-shadow></slot>
</template>
</template>
```

```js
// slottable.js
import { LightningElement, track } from "lwc";
export default class Slottable extends LightningElement {
foo = false;
}
```

```html
<!-- app.html -->
<template>
<x-slottable>
<x-foo></x-foo>
</x-slottable>
</template>
```

In this case, `<x-foo>` will never render at all, because it's within a `<template if:true>` block where the condition is false. Similarly, the slot contents would not render if there was no receiving slot for a given slot name, or if the target component contained no slots at all.

These are all differences with native shadow slots, which render eagerly. However, these differences align with the current implementation of synthetic shadow slots. This behavior is maintained in light DOM slots for simplicity's sake in the LWC engine, as well as for the performance benefit of lazy slots.

#### Events and other slot features

The `slotchange` event and `::slotted` CSS pseudo-selector are both unsupported for light DOM slots.

In the case of `slotchange`, it's not clear what `event.target` would be. For `::slotted`, it's not really relevant or useful since light DOM slots can be styled the same as any other light DOM content.

### Security

Shadow DOM (in combination with Lightning Locker) encapsulates components and prevents unauthorized access into the shadow tree. With Light DOM though, the DOM is open for traversal by other components. Since Light DOM is not the default, the component author has to opt-in to it, the burden of security falls on them. Developers need to understand that they are "opening" their component for access from outside when they opt in to Light DOM.

Light DOM will be behind a feature flag that can be set for the runtime. It can be turned on/off on a per container basis. The LWC engine will throw at runtime is a component attempts to render in the light DOM when the feature flag is disabled.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be turned on/off on a per container basis.

Please define container.


It's important to note that there isn't a way to enable light DOM for a restricted set of namespaces. When the feature flag is turned on, the capability is made available to all components on the page.

### Server Side Rendering

The engine-server module should provide the SSR capability to seamlessly render Shadow DOM or Light DOM. It should include the component children, as well as generated styles.

## Adoption strategy

This new feature does not break any existing components, it simply adds a new feature that developers have to opt for.

There is no automated migration of the existing components. As discussed above, Light DOM components differs from Shadow DOM components from the API and runtime standpoint. If a component author wishes to use Light DOM for any of their existing components, they will have to make the relevant changes manually.

## How we teach this

Shadow DOM and Light DOM are already names accepted by the industry, see: [Terminology: light DOM vs. shadow DOM](https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=en). We need to provide the proper documentation to educate the LWC developers:

- What are the differences between Shadow DOM and Light DOM
- We need a guide on when to use one or the other
nolanlawson marked this conversation as resolved.
Show resolved Hide resolved

## Open questions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to think more about the low-code use cases and the admin persona.

  1. How would we protect an admin from using light DOM components that may unknowingly compromise the experience they are building?
  2. Does the component schema itself need to indicate whether it is using light DOM? We have talked about using JSON schema to describe slots, maybe this is part of it
  3. How we teach this needs to also address the admin who is building experiences with a mix of light and shadow DOM components.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Do we require top-level components exposed in LAB to use shadow DOM? I don't think we can, supporting LWC container components like tabset seems like a dealbreaker. Need to think about this more


**How to deal with different namespaced packages claiming the same custom element name?**

As of today on the Salesforce platform, all the LWC components are authored under the `c` namespace. This namespace dictates the prefix for of the custom elements rendered in the DOM. When delivered via a namespaced package, the components are referenced using their actual namespace (eg. `lightning`, `my_ns`, etc.). However internal component references in the package is done using the `c` namespace.

Because of this if 2 managed packages publish a `button` component, the 2 components will be rendered as `c-button` in DOM. This is problematic because a custom element name can only be bound to a single element constructor. Even if this is not a new issue, the [scoped custom element registry proposal](https://github.com/WICG/webcomponents/issues/716) would solve this issue for shadow DOM components. This is still an issue for light DOM components because scoped custom elements registry can only be attached to a shadow root.
Binary file added text/shadow-spectrum.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.