You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It would be very cool for WCDT to visualize which components on the page were hydrated and which ones were not, perhaps by putting colored borders around custom elements in the page, with different colors for different hydration states. This would help developers understand when and where their page is hydrating and give better insights into page performance. It can also help devs fix bugs caused by components not hydrating, since it would help easily identify which components failed to hydrate as expect. An extra feature could be denoting (whether in color or another means) why a component has not hydrated. Is it because the component is not defined, or because hydration was deferred?
This is most useful for pages using an "islands" architecture, where there are multiple components which can independently hydrate based on their own defined conditions.
Unfortunately there is no browser spec for what "hydration" means or how to identify hydrated and dehydrated components. However, there is a proposed community protocol which specifies defer-hydration as an attribute which is used to prevent hydration on specific components. I think the right behavior here is to defined "hydrated" elements as those which are 1) defined and 2) are missing defer-hydration. By contrast, "dehydrated" elements are either 1) not defined or 2) have defer-hydration set as an attribute.
Based on this, the feature can likely be implemented by injecting two styles into the page:
/* Highlight hydrated elements in red. */:host-context(.wcdt-show-hydration-state) :defined:not([defer-hydration]) {
border: solid 1px red !important;
}
/** Highlight dehydrated elements in green. */:host-context(.wcdt-show-hydration-state) :is(:not(:defined), [defer-hydration]) {
border: solid 1px green !important;
}
Which color should indicate which state is a UX choice I don't feel that strongly about. I'm personally of the opinion that every hydrated element has a negative cost to the page and we want to defer or avoid hydrating as many components as possible for as long as possible, and therefore hydrated components should be red to signify that. I get that others will likely disagree with that assessment. However I also believe code diffs should have added code in red and removed code in green, so clearly I'm insane.
Unfortunately this CSS is a bit oversimplified because:
Shadow DOM exists, so these styles need to be in every shadow root.
Pretty much every element on the page will match :not(:defined), meaning they will all be highlighted green, which would be far too noisy. To my knowledge, there is no selector which restricts itself to custom elements such that you could highlight my-component but not highlight div. There are some potential workarounds, but they are not pretty.
If a component has defer-hydration removed and hydrates itself, then has defer-hydration added back, the styles will show the component as being dehydrated, even though it actually did hydrate. Adding defer-hydration does not "unhydrate" an element, so technically this is a stateful change. This can probably be overlooked for an MVP implementation, but it would be great if there's a way to handle that.
An alternative approach might be to track this with JavaScript. There are three ways the page can observe a component's hydration state change:
A component is added to the DOM already in a hydrated or dehydrated state. We can observe this with a MutationObserver with subtree: true.
A component is defined when defer-hydration is not set. We can observe this with customElements.whenDefined.
A component has defer-hydration removed while it is defined. We can observe this with a MutationObserver with attributes: true.
We can watch for new elements and state changes to automatically update their styles.
functionisCustomElement(el: Element): boolean{returnel.tagName.includes('-');}functionisHydrated(el: Element): boolean{constisDefined=customElements.get(el.tagName.toLowerCase());constisDeferred=el.getAttribute('defer-hydration');returnisDefined&&!isDeferred;}functionstyleComponent(el: Element): void{if(isHydrated(el)){el.classList.remove('wcdt-dehydrated');el.classList.add('wcdt-hydrated');}else{el.classList.remove('wcdt-hydrated');// Technically a change from hydrated -> dehydrated should never happen.el.classList.add('wcdt-dehydrated');}}newMutationObserver((records)=>{for(constrecordofrecords){// Check components newly added to the DOM.for(constnodeofrecord.addedNodes){if(!isCustomElement(node))continue;// Ignore non-custom elements.// Style newly discovered component.styleComponent(node);// If it is currently not defined, wait for it to be defined and restyle.constisDefined=customElements.get(el.tagName.toLowerCase());if(!isDefined){customElements.whenDefined(el.tagName.toLowerCase()).then(()=>{styleComponent(el);});}}// Check components with `defer-hydration` updates.if(!isCustomElement(record.target))continue;// Ignore non-custom elements.if(record.attributeName!=='defer-hydration')continue;// Ignore other attributes.styleComponent(record.target);}}).observe(document.documentElement,{attributes: true,childList: true,subtree: true,});
I haven't tested this and it isn't a perfect script, but hopefully good enough. I'm not sure how MutationObserver plays with shadow DOM, but if it doesn't pierce that boundary then you'll still have that problem. The CSS for .wcdt-dehydrated and .wcdt-hydrated also needs to be applied to all shadow roots, which can be tricky. Maybe a constructable style sheet which gets added to all shadow roots? Not sure if an attached shadow root can be observed (maybe patch Element.prototype.attachShadow?). This approach at least avoids the custom element selector problem. You'll also need to either inject this script before HTML is parsed or scan the document immediately on load for components which are appended prior to the .observe() call.
I made a Stackblitz demoing all the cases I could think of. This should serve as a good test for the feature to make sure it paints the borders correctly.
Such a thorough explanation with examples will surely get me started with this. There are some parts that will be reworked a bit under the surface and with those I'll keep this in mind.
I'll come back to you if I find something I need more examples on. Thanks!
Why is this Lit only? defet-hydration is a proposed community protocol. Any components/frameworks which implement that protocol should be compatible with this feature.
It would be very cool for WCDT to visualize which components on the page were hydrated and which ones were not, perhaps by putting colored borders around custom elements in the page, with different colors for different hydration states. This would help developers understand when and where their page is hydrating and give better insights into page performance. It can also help devs fix bugs caused by components not hydrating, since it would help easily identify which components failed to hydrate as expect. An extra feature could be denoting (whether in color or another means) why a component has not hydrated. Is it because the component is not defined, or because hydration was deferred?
This is most useful for pages using an "islands" architecture, where there are multiple components which can independently hydrate based on their own defined conditions.
Unfortunately there is no browser spec for what "hydration" means or how to identify hydrated and dehydrated components. However, there is a proposed community protocol which specifies
defer-hydration
as an attribute which is used to prevent hydration on specific components. I think the right behavior here is to defined "hydrated" elements as those which are 1) defined and 2) are missingdefer-hydration
. By contrast, "dehydrated" elements are either 1) not defined or 2) havedefer-hydration
set as an attribute.Based on this, the feature can likely be implemented by injecting two styles into the page:
Which color should indicate which state is a UX choice I don't feel that strongly about. I'm personally of the opinion that every hydrated element has a negative cost to the page and we want to defer or avoid hydrating as many components as possible for as long as possible, and therefore hydrated components should be red to signify that. I get that others will likely disagree with that assessment. However I also believe code diffs should have added code in red and removed code in green, so clearly I'm insane.
Unfortunately this CSS is a bit oversimplified because:
:not(:defined)
, meaning they will all be highlighted green, which would be far too noisy. To my knowledge, there is no selector which restricts itself to custom elements such that you could highlightmy-component
but not highlightdiv
. There are some potential workarounds, but they are not pretty.defer-hydration
removed and hydrates itself, then hasdefer-hydration
added back, the styles will show the component as being dehydrated, even though it actually did hydrate. Addingdefer-hydration
does not "unhydrate" an element, so technically this is a stateful change. This can probably be overlooked for an MVP implementation, but it would be great if there's a way to handle that.An alternative approach might be to track this with JavaScript. There are three ways the page can observe a component's hydration state change:
MutationObserver
withsubtree: true
.defer-hydration
is not set. We can observe this withcustomElements.whenDefined
.defer-hydration
removed while it is defined. We can observe this with aMutationObserver
withattributes: true
.We can watch for new elements and state changes to automatically update their styles.
I haven't tested this and it isn't a perfect script, but hopefully good enough. I'm not sure how
MutationObserver
plays with shadow DOM, but if it doesn't pierce that boundary then you'll still have that problem. The CSS for.wcdt-dehydrated
and.wcdt-hydrated
also needs to be applied to all shadow roots, which can be tricky. Maybe a constructable style sheet which gets added to all shadow roots? Not sure if an attached shadow root can be observed (maybe patchElement.prototype.attachShadow
?). This approach at least avoids the custom element selector problem. You'll also need to either inject this script before HTML is parsed or scan the document immediately on load for components which are appended prior to the.observe()
call.I made a Stackblitz demoing all the cases I could think of. This should serve as a good test for the feature to make sure it paints the borders correctly.
https://stackblitz.com/edit/typescript-yryatj?file=index.ts,index.html
The text was updated successfully, but these errors were encountered: