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

[React 19] Support scoped custom element registries (i.e, react with Custom Elements being rendered in a shadow root) #28938

Open
michaelwarren1106 opened this issue Apr 27, 2024 · 1 comment
Labels

Comments

@michaelwarren1106
Copy link

michaelwarren1106 commented Apr 27, 2024

Summary

This is not an issue, but a (hopefully small) feature request/enhancement to the Custom Element support coming in React 19.

The Custom Element support in React 19 is awesome! But even once React 19 lands, there will still be one challenge that Custom Element component library maintainers face. There is no way to inform React to use a scoped custom element registry when creating elements.

Scoped Custom Element Registries

Proppsal

The above proposal implements a new CustomElementRegistry() api where Custom Element authors can register custom elements to a registry that is not the global registry. An implementation of scoped custom element registries is in Chrome Canary under a flag at the moment, and the Web Components Community Group is working to see them implemented in all browsers.

There are also several polyfills that add this behavior to browsers in the meantime. Here is the one I use:

Polyfill

Use case: Micro-frontend application

Micro-frontend application architectures are very common. This pattern is where a host application will use module federation (webpack, vite etc) to dynamically fetch JS modules containing separate "remote applications" that the host app then assembles into a single seamless application for the user. The architecture is popular because it enables autonomy of releases between remote applications.

For Custom Elements though, the problem is that with only the single global Custom Element registry in browsers, remotes can't scope their Custom Elements efectively and so there can be version conflicts when remote applications depend on different version of the same custom element. Whichever version gets registered first "wins" and the other remotes break.

Scoped element registries solve this. If a remote application is rendered inside a shadow root (custom element, or just a div with an attached shadow root) then all Custom Element definitions in that remote can be pulled from a scoped registry instead of the global one. Bye bye version conflicts! Every remote gets its own version of the Custom Elements it uses.

Feature Request

If the remote application uses a framework like React where document.createElement is used. In order to create a scoped Custom Element using the scoped definition in the custom registry, shadowRoot.createElement() is the mechanism.

React (and other frameworks also) have no way to be informed that they are being rendered inside a shadow root with a scoped registry attached. And therefore have no mechanism to switch from document.createElement to shadowRoot.createElement. React can already render inside a shadow root just fine. React's root can be an element inside a shadow root with no issues. This feature is about detecting whether or not React's render root has a parent element that is a shadow root and switching to shadowRoot.createElement() accordingly.

I hope this is enough detail to describe the ask. I would think that this might not be a huge feature or change to existing React fundamentals, but I'm not an expert on what the exact implementation approach should be. I'm just familiar with the current problem landscape and our workarounds that we use to get around it being cumbersome.

This comment in a thread about the topic has a rough idea of what the implementation might look like.

I would think that with an implementation similar to the above, document.createElement() would be used in applications where there aren't any scoped element registry polyfills, or where the feature hasn't yet landed natively in browsers, so there may be low risk to a check for "is the root node in a shadow root".

@mayank99
Copy link

mayank99 commented May 3, 2024

Correct me if I'm wrong, but your current suggestion wouldn't fully handle all cases. The main assumption you've made is that React's root is created inside the shadow-root, which is not always desirable. More often than not, I want a single React root, so that I can share context between shadow-roots.

One way to fix this would be by treating createPortal() similarly to createRoot() and hydrateRoot(). In all cases, React should automatically use getRootNode(el).createElement().

This should probably be done before React 19 ships as stable, otherwise we might have to wait several years because of behavioral differences/risks with making this change in a minor release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants