-
Notifications
You must be signed in to change notification settings - Fork 378
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
Consider future expansion to allow using a registry without new API #1043
Comments
I think a better solution will be to have |
as a web component library author, i think a big part of this scoping use case is how to design/implement scoping so that frameworks don’t necessarily have to specifically opt in? if framework updates are required to support scoping, that means that only future applications or app updates can benefit. is it possible to implement a solution that works with the current i work on a web component design system library for primarily react consumers. that particular combination of framework + custom elements has been particularly difficult and consumers of the web components often use these kinds of things “not working in react” as negative feedback for web components instead perhaps of being negative feedback for react. the more features that browser can implement seamlessly without framework adoption the better imo. i don’t have any idea how that could work. but i definitely second the idea of removing the shadowroot restriction on custom registries. |
I don't see how this is possible without new API that frameworks use. Ideally frameworks would be flexible enough to show customization of what object/method they use create elements. The fundamental problem is that existing I've been able to get React working by patching |
out of the box thought, if it would work like a
this also doesn't get in the way of the frameworks and could help solve the problem when you don't have control of the creation. with the creation using createElement you still have the finer gained control. |
This is not the case – many frameworks such as Solid, Vue Vapor, Svelte v5, and Lit instead use this pattern: const template = document.createElement('template')
template.innerHTML = htmlString
return template.content.cloneNode(true) If the I mentioned this in the call, but our (Salesforce's) implementation of scoped registries solves this by conceptually tying a scoped registry to a ShadowRealm, not a ShadowRoot. This indeed requires a lot of global patching, but the upside is that the script can use
Maybe a v2 of the scoped registries spec could do this instead of boiling the ocean, but for our case at least, we would still need to patch globals to route the global APIs to the right scoped APIs. |
In the call, I wondered whether we might find value in adding something like this: const template = document.createElement('template');
registry.run(() => template.innerHTML = htmlString);
return template.content.cloneNode(true); I think there was agreement that we didn't want to block moving forward with the current proposal, but something like this might be added in round two. |
Do what exactly? |
One major difference is that in React we can figure out that the framework only creates elements with one specific API, and that it's not reentrant at the point of the patch. Here we'd have to make sure that all element creation APIs invoke the Find a Custom Element Registry step, and that we keep a stack of registries, and we probably want to make this work across async DOM creation operations, so we may need to wait for something like Async Context. There may be bad cases where undefined elements are upgraded in the global scope because they're not defined in the custom scope and get upgraded in the adopt steps. |
I mean "expose explicit scoped APIs that the framework must use." E.g. |
This proposal already does that in the form of const el = (container.getRootNode().createElement ?? document.createElement)(tagName); or const fragment = (container.getRootNode().importNode ?? document.importNode)(template.content, true); |
Just to clarify, I believe in general if there is no global definition, you won't have to use I believe this behavior follows from the Finding a custom element definition section of the proposal and it would also be consistent with how iframes work. But note, the prototype implemented in Chrome 120 + experimental web platform features does not yet implement this section of the proposal at all, so this doesn't currently work. This is why I was focusing on a way to preserve the |
This would work for the React MFE use case then I think? Possibly the |
@justinfagnani Your code example may be a bit contentious for some framework authors, since I imagine it would tank
@sorvell This is an interesting solution. I think it might actually solve our use case, since there is no chance that The downside is that you wouldn't be able to have any top-level custom elements, unless you are willing to expose them globally (to disconnected |
OK, I just chatted with @rwaldron (who knows way more about this stuff than I do). Basically, our use case can only be solved if it's possible for legacy code (which uses global In our world, something more like @EisenbergEffect's |
What if this is what was on offer for users of w3ctag/design-reviews#334 (or possibly via a new type, e.g. |
This is a nice idea, and I'm thinking it probably should be an explicit choice. The html modules proposal already includes I don't think that 100% addresses the needs here, but it is a step in the right direction for sure. |
i don’t think HTML modules goes very far to supporting scoping in MFE use cases does it? it might if each MFE remote app was itself an html module, but i’m not sure how feasible that is. things like webpack and vite module federation come into play. HTML modules is a complementary solution i think, it the “how does a framework MFE app/component come to use a registry” would still be an issue i think. |
If it made sense to use the HTML module boundary as a registry scoping boundary then it would be something that a bundler would be required to manage, much like they are required to manage colliding |
Few notes:
|
Revisiting this after feedback from @ryansolid on how WCs add extra work for framework authors… This seems like a case where we may want to reconsider the current approach. This part of the spec would effectively force frameworks to replace current usages of Meanwhile, Salesforce has (I believe) the largest deployment of a scoped custom element registry polyfill, and our approach is not based on this design at all but rather on something closer to |
Some points that were raised about SCER in the Discord:
|
To clarify this a bit, it's not impossible, but SSR requires a way to tell some elements to not use the global registry. The current proposal is to use a flag on This could probably still be done in coordination with shadow realms, but it still needs some kind of scoping within HTML that turns off global upgrades. Shadow roots are our natural scoping boundary here. |
I take this to mean that as the HTML parser runs, it would use registry to lookup the element names it comes across? This seems similar to the idea in #1040 (comment) of decoupling shadow trees and registries. An alternative to that which I would prefer would be that each registry can create built-in elements as well and that the element "remembers" the registry it was created with. This enables you to do: x = customRegistry.createElement("div");
x.innerHTML = something; // uses customRegistry |
Background
As currently spec'd, registries are coupled to Shadow DOM and require a new opt-in API to facilitate scoped element creation (
shadowRoot.createElement/importNode
). While this makes good sense for the first version of the API, there are some additional use cases that could be addressed by expanding the API in the future.Use case
In particular, the micro-frontend architecture (MFE) is a natural fit for web components because of their native interoperability but fundamentally requires scoped registries.
However, MFE's often exist in complex environments where various web frameworks are used together. For MFEs to effectively use scoped registries, ALL frameworks used in them must be aware of and use scoped API for creating DOM. Currently, of course, none of them do. While this can be addressed in the long run, it's sure to be a point of friction and it'd be great to expand the API to more seamlessly support this use case.
Example
An MFE feature is implemented with React. The feature itself (inside the React components) uses custom elements. Currently, this means the React MFE feature should itself be wrapped in a Shadow DOM which uses a scoped registry to define those custom elements. So far so good, but now there's a problem: React doesn't know about
shadowRoot.createElement
so it doesn't create the correctly scoped elements. An un-upgraded element connected to a shadowRoot with a registry will upgrade with the registry's definition so the only practical problem is a conflict with a globally registered element.Possible solution
It's tempting to propose adding a way to apply a registry to a DOM subtree that doesn't use Shadow DOM. However, while this might be useful for frameworks that don't expect Shadow DOM for other reasons (e.g. reliance on global styling), it wouldn't typically help solve this problem. That's because it's a very common pattern for frameworks to create elements via
document.createElement
, and any globally registered element will immediately upgrade on that call.This should be verified, but I believe the most common pattern generally used by most web frameworks to create DOM is
document.createElement
+ "append." So if there were a general custom elements feature to specify "for this name, only customize the element via upgrade and not creation." Ideally, this could be done independent of the global registration but this could arguably be too powerful. Some API options for how to do addupgradeOnly
feature:customElements.define('x-foo', XFoo, {upgradeOnly: true});
customElements.customizeWhen('x-foo', {upgrade: true})
The text was updated successfully, but these errors were encountered: