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 · 5 comments
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.

Copy link

github-actions bot commented Aug 1, 2024

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

@github-actions github-actions bot added the Resolution: Stale Automatically closed due to inactivity label Aug 1, 2024
@mayank99

This comment was marked as outdated.

@michaelwarren1106
Copy link
Author

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.

Im definitely assuming a few things and not covering all use cases. I'm not an expert on the differences between createRoot and hydrateRoot tbh.

If react maintainers would chime in here with their thoughts I'm sure a robust solution can be found that covers the use cases correctly. I just made the issue to represent the main challenge that web component design systems with scoping have had with consumers in micro-front-end style apps.

Also since React 19 seems a bit delayed while the team irons out details with concurrent data fetches in RSCs, I wonder if there's still time to maybe get this into React 19?

@github-actions github-actions bot removed the Resolution: Stale Automatically closed due to inactivity label Aug 1, 2024
@michaelwarren1106
Copy link
Author

not stale :)

Is there a possibility of discussing this and getting it addressed before react 19 ships? The inability to tell react that its root is inside a shadow root is a definite blocker for micro-frontend applications where each remote app has web components in it that need scoping.

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