|
| 1 | +# Defer Hydration Protocol |
| 2 | + |
| 3 | +An open protocol for controlling hydration on the client. |
| 4 | + |
| 5 | +Author: Justin Fagnani |
| 6 | + |
| 7 | +Status: Proposal |
| 8 | + |
| 9 | +Last update: 2021-06-24 |
| 10 | + |
| 11 | +# Background |
| 12 | + |
| 13 | +In server-side rendered (SSR) applications, the process of a component running code to re-associate its template with the server-rendered DOM is called "hydration". The defer-hydration protocol is design to allow controler over hydration to solve two related problems: |
| 14 | + |
| 15 | +1. Interoperable incremental hydration across web components. Componentents should not automatically hydrate upon being defined, but wait for a signal from their parent or other coordinator. |
| 16 | +2. Hydration ordering independent of definition order. Because components usually depend on data from their parent, and the parent won't usually set data on a child until it's hydrated, we need hydration to occur in a top-down order. |
| 17 | + |
| 18 | +`defer-hydration` enables us to decouple loading the code for web component definitions from starting the work of hydration, and enables top-down ordering and sophisticated coordination of hydration, including triggering hydration only on interaction or data changes for specific components. |
| 19 | + |
| 20 | +# Overview |
| 21 | + |
| 22 | +The Defer Hydration Protocol specifies an attribute named `defer-hydration` that is placed on elements, usually during server rendering, to tell them not to hydrate when they are upgraded. Removing this attribute is a signal that the element should hydrate. Elements can observe the attribute being removed via `observedAttribute` and `attributeChangedCallback`. |
| 23 | + |
| 24 | +When an element hydrates it can remove the `defer-hydration` attribute from its shadow children to hydrate them, or keep the attribute if itself can determine a more optimal time to hydrate all or certain children. By making the parent responsible for removing the `defer-hydration` attribute from it's children, we ensure top-down ordering. |
| 25 | + |
| 26 | +## Use case 1: Auto-hydration with top-down odering |
| 27 | + |
| 28 | +In this use case we want to page to hydrate as soon as elements are defined, but we want to force top-down ordering to avoid invalid child states. Here we configure the server-rendering step to add the `defer-hydration` attribute to all elements _except_ the top-most defer-hydration-aware elements in the document. |
| 29 | + |
| 30 | +When the top-most elements are defined, they will run their hydrations steps since they don't have a `defer-hydration` attribute, and will trigger their subtrees to hydrate by removing `defer-hydration` from children. |
| 31 | + |
| 32 | +Example HTML: |
| 33 | + |
| 34 | +```html |
| 35 | +<!doctype html> |
| 36 | +<html> |
| 37 | + <head> |
| 38 | + <script type="module" src="./app.js"></script> |
| 39 | + </head> |
| 40 | + <body> |
| 41 | + <x-nav> |
| 42 | + <template shadowroot="open"> |
| 43 | + <x-header defer-hydration> |
| 44 | + <template shadowroot="open"> |
| 45 | + <h1>Example</h1> |
| 46 | + </template> |
| 47 | + </header> |
| 48 | + </template> |
| 49 | + <x-article> |
| 50 | + <template shadowroot="open"> |
| 51 | + <x-figure defer-hydration>...</x-figure> |
| 52 | + </template> |
| 53 | + </x-article> |
| 54 | + </body> |
| 55 | +<html> |
| 56 | +``` |
| 57 | + |
| 58 | +## Use case 2: On-demand hydration |
| 59 | + |
| 60 | +Hydration can be deferred until some data or user-driven signal, such as interacting with an element. In this case server-rendering is configured to add `defer-hydration` to all elements so that nothing will automatically hydrate. |
| 61 | + |
| 62 | +An app-level coordinator may implement an event delegation/buffering/replay system to detect user-events within an element and remove `defer-hydration` on demand before replaying events. |
| 63 | + |
| 64 | +*TODO: do we need to have an event that signals that hydration is complete before replaying events?* |
| 65 | + |
| 66 | +## Hydrating children |
| 67 | + |
| 68 | +```ts |
| 69 | +class MyElement extends HTMLElement { |
| 70 | + static observedAttributes = ['defer-hydration']; |
| 71 | + |
| 72 | + attributeChangedCallback(name, oldValue, newValue) { |
| 73 | + if (name === 'defer-hydration' && newValue === null) { |
| 74 | + this._hydrate(); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + _hydrate() { |
| 79 | + // do template hydrate work |
| 80 | + // ... |
| 81 | + |
| 82 | + // hydrate children |
| 83 | + const deferredChildren = |
| 84 | + this.shadowRoot.querySelectorAll('[defer-hydration]'); |
| 85 | + for (const child of deferredChildren) { |
| 86 | + child.removeAttribute('defer-hydration'); |
| 87 | + } |
| 88 | + } |
| 89 | +} |
| 90 | +``` |
0 commit comments