Aangepaste elementen vooraf renderen

Vooraf renderen betekent dat de opmaak van een aangepast element, inclusief de schaduwstructuur, vanaf de server wordt verzonden, zodat de component wordt opgemaakt en gestructureerd voordat JavaScript wordt uitgevoerd. De belangrijkste platformprimitief hiervoor is Declarative Shadow DOM (DSD): de parser voegt de schaduwroot alleen toe vanuit HTML en het element wordt later geüpgraded ('gehydrateerd') door de klasse als er een is geregistreerd. Gebruik dit patroon voor weergave aan de serverzijde, streaming en om een flits van niet-opgemaakte content (FOUC) te voorkomen.

Een declaratieve schaduwwortel maken

Nest een <template> met het kenmerk shadowrootmode als het eerste onderliggende element van de host. De parser voegt de content toe als een shadow root en verwijdert de template. Er is geen script nodig.

<my-card>
  <template shadowrootmode="open">
    <style>
      .card { border: 1px solid; border-radius: 8px; padding: 1rem; }
    </style>
    <div class="card">
      <slot name="header"></slot>
      <div class="body"><slot></slot></div>
    </div>
  </template>

  <!-- Light DOM children are projected into the slots above. -->
  <h2 slot="header">Server-rendered title</h2>
  <p>Visible and styled before JS loads.</p>
</my-card>

Timing van hydratatie

Als de overeenkomende klasse is geregistreerd, voert de browser een upgrade van de host uit. Omdat de shadow root al bestaat, mag de constructor niet blindelings een tweede maken:

class MyCard extends HTMLElement {
  constructor() {
    super();
    // Reuse the server-rendered shadow root if present; only create one if absent.
    if (!this.shadowRoot) {
      this.attachShadow({ mode: 'open' });
      this.shadowRoot.append(template.content.cloneNode(true));
    }
    // Hydrate in place: attach listeners once against existing markup (not unguarded
    // in connectedCallback, which re-runs if the element moves in the DOM), and do
    // NOT re-render; that would discard the server's DOM and flash.
    this.shadowRoot.getElementById('toggle')
      ?.addEventListener('click', () => this.toggleAttribute('open'));
  }
}

Stel shadowrootmode="open" (niet closed) in voor bijna alle gevallen, zodat scripts en tools this.shadowRoot kunnen bereiken om te hydrateren. Als je een bestaande schaduwroot wilt serialiseren naar HTML op de server of in tests, gebruik je getHTML({ serializableShadowRoots: true }) en voeg je de root toe met serializable: true.

FOUC vermijden

DSD wordt alleen correct weergegeven bij de eerste weergave als de stijlen in de declaratieve template worden geleverd. Zo behoud je dat:

  • Plaats de css van de component in de <template shadowrootmode> (een <style>-tag of een <link>), niet in een afzonderlijk blad dat door een script wordt geladen.
  • Koppel alleen met my-card:not(:defined) { … } voor stijlen die moeten wachten op JS. Dit zijn meestal beveiligingsregels op paginaniveau, zoals een fade-out op instanties die aan de clientzijde worden gerenderd en die de SSR-pas missen. Deze bevinden zich in de light-DOM-stylesheet (pagina), omdat de schaduwwortel daar nog niet bestaat en :host-selectors niet van buitenaf van toepassing zijn. Structurele stijlen in de DSD-template hebben geen beveiliging nodig.
  • De schaduwstructuur niet opnieuw renderen bij hydratatie. Behandel de serveropmaak als gezaghebbend en koppel er gedrag aan.

Reservestrategieën

In engines die shadowrootmode niet parseren, is de template inert en wordt de content niet toegevoegd als shadow root. Detecteer dit (Object.hasOwn(HTMLTemplateElement.prototype, 'shadowRootMode')) en voeg, als het ontbreekt, de schaduwwortel imperatief toe vanuit dezelfde opmaak tijdens de upgrade, zodat de component nog steeds wordt gerenderd.