Use defer-hydration
in your Web Components for… well, deferred hydration.
By now you may be familiar with <is-land>
, the Eleventy utility for islands and partial hydration.
One of the tricks <is-land>
uses to delay initialization of Web Components’ custom elements nested as child content is that it iterates over any :not(:defined)
nodes and renames the elements to a non-upgradable tag name (<my-component>
becomes <is-land--my-component>
before <my-component>
is defined in the custom element registry via customElements.define
).
This works pretty well: it doesn’t require knowledge of which web components exist or may exist at some point in the future and leaves other HTML as is. Though I will admit it is a tad bit unexpected to folks that have tied their pre-hydrated CSS to the original tag name (I’d suggest using a class
instead!).
However, some component frameworks have started adopting a community-agreed-upon draft standard: using a defer-hydration
attribute on a component to denote that the component code will instead handle lazy initialization (when the attribute is removed).
Personally, I’d love to see this as part of a web standard first-party supported in browsers without requiring any userland component code changes. The first step to make this a viable formal standard in the future is to encourage folks to adopt the community protocol in their component code today!
In fact, <is-land>
v3.0.0 now supports defer-hydration
to skip the custom element tag rename and let the component code handle deferred hydration itself. Here’s how it might work:
<is-land on:visible>
<my-component defer-hydration>
</is-land>
And then your component JavaScript:
class MyComponent extends HTMLElement {
connectedCallback() {
this.hydrate();
}
static get observedAttributes() {
return ["defer-hydration"];
}
attributeChangedCallback(name, old, value) {
// when defined it triggers an attribute change from `null` to `""`
if(name ==="defer-hydration" && value === null) {
this.hydrate();
}
}
hydrate() {
if(this.hasAttribute("defer-hydration")) {
return;
}
// Run the initialization code.
}
}
if("customElements" in window) {
customElements.define("my-component", MyComponent);
}
When the <is-land>
loading conditions are met and the island hydrates, it removes all defer-hydration
attributes from nested child elements. This is provided for-free by the <is-land>
utility.
The defer-hydration
attribute removal then triggers the attributeChangedCallback
which runs the hydrate()
method.
17 Comments
Matthew Phillips
@zachleat I still don't understand the use-case for wanting to delay hydration (as I stated in the proposal) 😀
Zach Leatherman :11ty:
@matthewp I’m guessing you’re all-in on SSR?I think some PE scenarios require heftier DOM changes—I’ve talked a bit about the tension between SSR and PE before—I think this assists nicely there
Matthew Phillips
@zachleat I like to think I'm a pragmatist so I'm not really all-in on anything.But when I say I don't understand I really do mean I don't understand, not that I disagree with it. I don't understand what scenario exists that you would want a component to not h… Truncated
Zach Leatherman :11ty:
@matthewp I’m confused—isn’t that the point of islands? Arbitrary conditions on “hydrating” child content?
Matthew Phillips
@zachleat Ok, so you are using defer-hydration so that if you have a on:idle and on:visible on the same page, the visible one will continue to be delayed. To me, I think of the directives as "fastest wins" and I don't necessarily want 2 of the same components to hydra… Truncated
Matthew Phillips
@zachleat It's more of wanting to delay the loading and JS execution. Once that's happened then you might as well hydrate *all* of the component instances. I haven't thought of a reason to not hydrate a component who's code is loaded.
Zach Leatherman :11ty:
@matthewp ahh, I see. Yeah I’m not sure I agree with that (yet?)The implementation I added for `<is-land on:idle on:visible>` is that it won’t remove `defer-hydration` attribute until both conditions are met—I’d absolutely want the island to be in full control over its chil… Truncated
Matthew Phillips
@zachleat From my perspective, a web component should only control its shadow dom and <slot>. If it is controlling what is inside of that slot there's a coupling problem. There are some rare exceptions.
Zach Leatherman :11ty:
@matthewp I guess I’m making the case that `<is-land>` as a web component is an exception to that rule!
Matthew Phillips
@zachleat Yeah, for sure. I still don't see the use-case though 😆 What is the scenario where you want to hydrate <my-component> in one island but delay its hydration in a different island?
Matthew Phillips
@zachleat I think one difference is you are thinking about it from the perspective of the author of <is-land> and I'm thinking about it from the perspective of the <my-component> author.As a component author, conforming to the <is-land> wishes isn't enou… Truncated
Matthew Phillips
@zachleat As a nit, the article calls this a "community-agreed-upon draft standard", but it's not community agreed upon. It's an open proposal with some (but not a lot) of feedback. Not yet merged and agreed upon.
Zach Leatherman :11ty:
@matthewp it’s ok! We can disagree! 👍🏻
Matthew Phillips
@zachleat Of course, but I'm not sure what we're disagreeing on. Is the reason to delay hydration just because that's what <is-land>'s API wants to happen? I'm genuinely just trying to understand.I like to think about WCs in terms of "what would nati… Truncated
@isAdrisal
This sounds good but also somewhat similar to the data-src image lazy-loading tricks of old. Would it make sense for there to be a browser-provided heuristic instead, like we have now for loading=lazy on images?
Zach Leatherman :11ty:
@matthewp I don’t think it’s much different than defer for script or loading for img!
@thomasreggi
good job on the social image 🔥