Benutzerdefinierte Elemente vorab rendern

Webfunktionen: Deklaratives Shadow-DOM

Beim Vorrendern wird das Markup eines benutzerdefinierten Elements, einschließlich seines Shadow-Trees, vom Server gesendet, sodass die Komponente formatiert und strukturiert wird, bevor JavaScript ausgeführt wird. Das wichtigste Plattform-Primitiv dafür ist das deklarative Shadow-DOM (DSD): Der Parser hängt die Shadow-Root nur aus HTML an und das Element wird später durch seine Klasse aktualisiert („hydriert“), falls eine registriert ist. Verwenden Sie dieses Muster für serverseitiges Rendering, Streaming und um ein Aufblitzen von unformatierten Inhalten (FOUC) zu vermeiden.

Deklarative Shadow-Root erstellen

Verschachteln Sie ein <template> mit dem Attribut shadowrootmode als erstes untergeordnetes Element des Hosts. Der Parser hängt den Inhalt als Shadow-Root an und entfernt die Vorlage. Es ist kein Skript erforderlich.

<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>

Zeitpunkt der Flüssigkeitszufuhr

Wenn die passende Klasse registriert ist, aktualisiert der Browser den Host. Da die Shadow-Root bereits vorhanden ist, darf der Konstruktor nicht einfach eine zweite erstellen:

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'));
  }
}

Legen Sie in fast allen Fällen shadowrootmode="open" (nicht closed) fest, damit Skripts und Tools this.shadowRoot für die Hydratisierung erreichen können. Wenn Sie ein vorhandenes Shadow Root auf dem Server oder in Tests wieder in HTML serialisieren möchten, verwenden Sie getHTML({ serializableShadowRoots: true }) und hängen Sie das Root mit serializable: true an.

FOUC vermeiden

DSD wird beim ersten Rendern nur dann korrekt dargestellt, wenn die Stile in der deklarativen Vorlage enthalten sind. So behalten Sie diese bei:

  • Fügen Sie das CSS der Komponente in das <template shadowrootmode> (ein <style>-Tag oder ein <link>) ein, nicht in ein separates, per Skript geladenes Stylesheet.
  • Kombiniere my-card:not(:defined) { … } nur mit Stilen, die auf JS warten müssen – in der Regel Regeln auf Seitenebene, z. B. ein Ausblenden bei clientseitig gerenderten Instanzen, die den SSR-Durchlauf verpassen. Diese befinden sich im Stylesheet des Light-DOM (Seite), da der Shadow-Root dort noch nicht existiert und :host-Selektoren nicht von außen angewendet werden. Strukturstile in der DSD-Vorlage benötigen keinen solchen Schutz.
  • Der Shadow Tree wird bei der Hydratation nicht neu gerendert. Das Server-Markup wird als maßgeblich behandelt und das Verhalten wird daran angehängt.

Fallback-Strategien

In Engines, die shadowrootmode nicht parsen, ist die Vorlage inaktiv und ihr Inhalt wird nicht als Shadow-Root angehängt. Erkenne dies (Object.hasOwn(HTMLTemplateElement.prototype, 'shadowRootMode')) und hänge, falls nicht vorhanden, die Shadow-Root während des Upgrades zwingend aus demselben Markup an, damit die Komponente weiterhin gerendert wird.