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

[scoped-registries] Describe interaction with declarative shadow DOM #915

Open
wants to merge 3 commits into
base: gh-pages
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions proposals/Scoped-Custom-Element-Registries.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,51 @@ As a result, it must limit constructors by default to only looking up registrati

This poses a limitation for authors trying to use the constructor to create new elements associated to scoped registries but not registered as global. More flexibility can be analyzed post MVP, for now, a user-land abstraction can help by keeping track of the constructor and its respective registry.

## Interaction with Declarative Shadow DOM

[Declarative shadow DOM](https://github.com/mfreed7/declarative-shadow-dom/blob/master/README.md) allows HTML to construct shadow roots for elements from `<template>` elements with a `shadowrootmode` attribute.

Since these shadow roots are not created by a host calling `attachShadow()`, the host doesn't have a chance to pass in a scoped custom element registry. If a host is using a scoped registry, we need to force the declarative shadow root to not use the global registry and instead leave custom elements un-upgraded until the host can create and assign the correct registry.

To do this we add a `shadowrootregistry` attribute, according to the [declarative shadow root explainer section on additional `attachShadow()` options](https://github.com/mfreed7/declarative-shadow-dom/blob/master/README.md#additional-arguments-for-attachshadow). This attribute causes the declarative shadow root to have no registry associated with it at all:

```html
<my-element>
<template shadowrootmode="open" shadowrootregistry="">
<some-scoped-element></some-scoped-element>
</template>
</my-element>
```

The shadow root created by this HTML will have a `null` registry, and be in a "awaiting scoped registry" state that allows the registry to be set once after creation.

To identify this "no registry, but awaiting one" state, ShadowRoot will have a `scopedRegistry` boolean property. `scopedRegistry` will set to `true` for all ShadowRoots with a scoped registry, or awaiting a scoped registry. Code can tell that ShadowRoot has an assignable `registry` property if `root.registry === null && root.scopedRegistry === true`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should probably have "shadowRoot.customElements" instead of "shadowRoot.scopedRegistry" to be consistent with the global registry.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that would be shadowRoot.customElements instead of shadwoRoot.registry?

Copy link
Collaborator

Choose a reason for hiding this comment

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

right.

Copy link

Choose a reason for hiding this comment

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

Hmm, I find shadowRoot.customElements a bit harder to understand, as it's defined. In other words, this boolean is true "when the shadowRoot has a scoped registry, or is waiting for a scoped registry". I feel like "registry" should be at least somewhere in the name of the property, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rniwa meant that registry should be named customElements to match the global window.customElements.

shadowRoot.scopedRegistry would still be the boolean, if that's agreeable. (I don't think we do is or has prefixes in the DOM, correct?)

Copy link

Choose a reason for hiding this comment

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

Oh! Thanks, I missed that. I agree with renaming shadowRoot.registry to shadowRoot.customElements, which is exactly what you both wrote, but I failed to read correctly.

The DOM does use is sometimes, e.g. isConnected, isTrusted, isMap, isContentEditable, etc. The has prefix (which seems like what we'd want here) is also used, though less so. For example, hasBeenActive. If has is acceptable, I'd definitely lobby for renaming shadowRoot.scopedRegistry to shadowRoot.hasScopedRegistry.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll change to hasScopedRegistry in this PR and we can see what other reviewers think.

As for registry vs customElements: that effects the argument to attachShadow() too. I think I should make a separate PR for both so the change is more obvious?


Host elements with declarative scoped registries can assign the correct registry during upgrades, like so:

```ts
const registry = new CustomElementRegistry();

class MyElement extends HTMLElement {
#internals = null;
constructor() {
super();
this.#internals = this.attachInternals();
let shadowRoot = this.#internals.shadowRoot;
if (shadowRoot !== null) {
if (shadowRoot.registry === null &&
shadowRoot.scopedRegistry) {
this.#internals.shadowRoot.registry = registry;
} else {
console.error(`Expected shadowRoot.scopedRegistry to be true`);
}
} else {
this.attachShadow({mode: 'open', registry});
}
}
}
```

## API

### CustomElementRegistry
Expand All @@ -143,6 +188,10 @@ ShadowRoot adds element creation APIs that were previously only available on Doc

These are added to provide a root and possible registry to look up a custom element definition.

ShadowRoot also adds a `registry` property:

* `registry`: `CustomElementRegistry`

## Open Questions

### Adopted elements and Scoped Registry
Expand All @@ -154,13 +203,6 @@ There are concern about what happens when an element with a custom registry move
3. create a new callback that can receive the new registry when moved. This is problematic as well because what happen when the registries are coming from another library, already created by someone else?
4. find ways for implementers to preserve the original registry (ideal).

### Intersection with Declarative Shadow Root

Although these two proposals are in early stages, we need to solve the intersection semantics. There are two main issues:

1. if a declarative shadow root is created, elements inside that shadow should not be upgraded with the global registry. a possible solution is to add a new attribute, similar to `mode` to indicate to the parser that a custom registry is going to be eventually associated to this shadow.
2. if the component is planning to reuse the instance of the declarative shadow root (which is ideal), how can the component associate that instance with a registry? this indicates that maybe the association between ShadowRoot and CustomElementRegistry cannot be defined via `attachShadow()`, and instead, something like `ElementInternals` is much more flexible.

## Alternatives to allowing multiple definitions

### More robust registration patterns
Expand Down