Open trusktr opened 3 years ago
This is quite a bit like svg use element. Ironically, WebKit & Blink implement svg use element using shadow roots and replicating DOM tree in each instance of use element. FWIW, this seems like something you can easily implement in the user land. You just need to "mirror" DOM tree for each instance using MutationObserver. That's effectively what WebKit/Blink does except it gets updated whenever style gets updated so the timing is slightly different. Trident/Edge used to implement svg use element at more of rendering layer so they avoided replicating the DOM tree but it leads to many architectural challenges in other engines so that's not really practical.
this seems like something you can easily implement in the user land. You just need to "mirror" DOM tree for each instance using MutationObserver.
It is possible, yes, just like we can polyfill Custom Elements, which comes with its can of worms.
Performance aside (either the browser replicates internal trees or it chooses a fast render-only path), the <mirror>
idea would improve developer experience and development simplicity in a standard and powerful way.
If a user writes their own duplicate-DOM-and-mirror-changes-with-MutationObserver system, it can open up the surface area for bugs. It can interfere with the end users DOM representation. For example, imagine the user installs a 3rd-party jQuery plugin, and when the user instantiates the plugin, it accidentally also applies itself onto the duplicated DOM trees and causes unintended side-effects. The user is expecting to manipulate only one DOM tree as the source of truth. But due to the plugin also manipulating the duplicate DOM trees, the MutationObserver mechanism could break in some unexpected way, or the jQuery APIs may return multiple states when there should only be one and it could confuse the user code. Of course there would always be some way to fix such an issue, but it would complicate the application code base.
Performance aside (either the browser replicates internal trees or it chooses a fast render-only path), the
<mirror>
idea would improve developer experience and development simplicity in a standard and powerful way.
I mean... it's true that all new APIs will improve developer ergonomics for developers who need them but adding a very complex feature like this requires a really good reason to do so.
If a user writes their own duplicate-DOM-and-mirror-changes-with-MutationObserver system, it can open up the surface area for bugs. It can interfere with the end users DOM representation. For example, imagine the user installs a 3rd-party jQuery plugin, and when the user instantiates the plugin, it accidentally also applies itself onto the duplicated DOM trees and causes unintended side-effects. The user is expecting to manipulate only one DOM tree as the source of truth. But due to the plugin also manipulating the duplicate DOM trees, the MutationObserver mechanism could break in some unexpected way, or the jQuery APIs may return multiple states when there should only be one and it could confuse the user code. Of course there would always be some way to fix such an issue, but it would complicate the application code base.
How does jQuery plugin start working on / gain access to a duplicated DOM tree? Just put it inside a closed shadow tree and nobody else will find the duplicated instances.
Just put it inside a closed shadow tree and nobody else will find the duplicated instances.
Yeah, there are workarounds of course. Also using unique selectors would help (f.e. .left-eye > .foo
).
Considering performance, isn't crossing JS-native boundaries more expensive than keeping things on the native side (f.e. keeping the duplicates as native trees)?
The complexity is a little more simple than ShadowRoot and <slot>
. I imagine various parts of the internal code would be re-used.
This kind of functionality has seemed necessary to me when building custom previews, selects, trees, tables, and multitracks. I suspect it’s applicable to experiences that clone DOM trees for the purpose of mirroring content, or custom elements that require nesting deeper than a single parent and child.
Cloning a DOM tree and mirroring it with a MutationObserver would be insufficient when the source elements contain events or styles. For the web components I’ve been working on, our users plan to slot in their own content with its own functionality.
Without something like this mirror proposal, my currently alternative is to create some kind of proxy-like prototype for everything from EventTarget to HTMLElement for functionality cloning, and/or observers chained to a cached CSSStyleDeclaration
from getComputedStyle
for style cloning. Normally, I wouldn’t entertain this, but I already have to do something very similar to emulate constructible stylesheets. Still, the idea of messing around with native classes beyond polyfilling gives me MooTools vibes, so I’m hoping some of the linked examples will help illuminate a need for this.
can cross pages be considered in this scope? would it be feasible to mirror from 1 page to another same as GH do with permalinks?
https://github.com/whatwg/html/blob/823a14b4353266c7885c7b06da0d6ba1e4f1bb20/README.md?plain=1#L5
Having the ability to render an element relative to other places in the DOM would alleviate overhead from situations where an element should be rendered more than once in different places of a web app (f.e. implementing CSS-based VR, and would provide the machinery for new patterns like what virtual component systems currently do with "portals".
The
<mirror>
element would be similar to ShadowDOM<slot>
elements, except that<mirror>
can come from anywhere (not restricted to a shadow host element's children like with<slot>
s), and no hierarchy requirements<mirror>
element does not have to be anywhere specifically (f.e. not required to be in a ShadowRoot)<mirror>
are still rendered in their original location plus in the new locations where<mirror>
elements are located. This is in contrast to ShadowRoot<slot>
elements where distributed nodes no longer render in their original light-tree position.There may be both an imperative way to assign nodes to a
<mirror>
, and a declarative way.A declarative example:
would render as if we had written
Affecting the style of the mirrored
<span>
element causes the effect to be mirrored to all locations. For example,would render as if we had written
An imperative example:
would render as if we had written
The
assign()
method name is borrowed from https://github.com/whatwg/html/issues/3534.Use Case 1: CSS-based VR (f.e. phones inside binoculars)
The content to be displayed in 3D only needs to have a single DOM for manipulation (a single source of truth) so as not to worry about having to copy all modifications from one tree (f.e. left eye) to the other (f.e. right eye).
Then the user only ever has to modify the left eye content, and it is mirrored to the right eye with the proper right-eye transform.
Use Case 2: Render useful things in multiple places
This is an e-commerce website:
Bike sheddable: perhaps mirrors can have the same name, and something assigned to that mirror name renders at all locations.
Use Case 3: "portals"
On its own, it is similar to the portal concepts in other virtual component systems like React, Vue, etc.
Here's the Svelte Portal example. The part with
<div use:createPortal={'foo'}>
is the<mirror>
, and<div use:portal={'foo'}>
is an element in another component being mounted to that position in the other component.The difference with those concepts and this new
<mirror>
concept is that in this<mirror>
concept the element being mirrored can be mirrored to multiple locations as well as render in its original location.A library author could implement their own "portal" concept on top of the
<mirror>
system in order to enforce certain restrictions like an element mirroring only to one outside location (f.e. using a unique naming, or some form of mirror reference passing), or enforcing that the portaled item does not render in its original location with some CSS styling.Most common use case
Probably the most common use case will be duplicating content in various places, like with the shopping cart example. The following are sometimes seen in multiple locations of web apps:
Additional API ideas
mirrorchange
event that is similar to theslotchange
even that we know.mirror.assignedNodes()
works very similar toslot.assignedNodes()
<mirror name="..." scoped>
could perhaps make it scoped to a ShadowRoot, just like a<slot>
is, and would allow any child node or grandchild node in a shadow host to be distributed to the mirror (provides the concept of distributing indirect children of a host, mentioned in https://github.com/whatwg/html/issues/3534 IIRC), effectively making it behave just like a<slot>
but with added benefit of indirect child distribution. Satisfies @joelrich's ask in https://github.com/WICG/webcomponents/issues/574Other benefits:
By having only one source of truth for things that should be rendered in multiple places,