HTML-streaming in onjuiste volgorde (OOO)

HTML-streaming die niet op volgorde is (ook wel declaratieve gedeeltelijke updates genoemd) is een alternatief voor de traditionele lineaire verwerking van HTML van boven naar beneden. Hiermee kunnen ontwikkelaars tijdelijke aanduidingen eerder in een document definiëren en deze 'patchen' met content die later in dezelfde netwerkstream of via nieuwe imperatieve JavaScript-streaming-API's binnenkomt.

Snel overzicht

De functie bestaat uit 2 primaire mechanismen:

  1. Declarative Patching (HTML-based): Uses new processing instructions (<?marker>, <?start>, <?end>) and <template for> to swap content in the DOM as it streams in from the server.
  2. Imperatieve instelling en streaming (op basis van JS): Een reeks nieuwe methoden (bijv. streamHTML(), setHTMLUnsafe()) waarmee je HTML kunt instellen of leesbare streams rechtstreeks naar DOM-elementen kunt leiden met systeemeigen prestaties en optionele scriptuitvoering.

Praktische tips

  • Geef prioriteit aan content boven de vouw: Stream eerst de essentiële UI (koptekst, hero, primaire navigatie, primaire content). Gebruik tijdelijke aanduidingen voor 'dure' componenten of componenten met een lagere prioriteit, zoals reacties, advertenties of gerelateerde content.
  • Implementeer native laadstatussen: Gebruik de markeringen <?start> en <?end> om reservecontent te verpakken. De browser vervangt automatisch alles tussen deze markeringen als de bijbehorende <template for> binnenkomt.
  • Minimaliseer de Cumulative Layout Shift (CLS): Zorg altijd voor stabiele afmetingen voor tijdelijke aanduidingen. Als de uiteindelijke contentgrootte onbekend is, gebruik je skeletonschermen of CSS aspect-ratio om ruimte te reserveren en schokkende verschuivingen te voorkomen.
  • Toegankelijkheid en focusbeheer: Houd er rekening mee dat updates die niet op volgorde staan, verwarrend kunnen zijn voor gebruikers van schermlezers. Gebruik aria-live-regio's voor kritieke updates en zorg dat de focus niet verloren gaat als een tijdelijke aanduiding met het actieve element wordt vervangen.

Implementatiegegevens

1. Declaratieve HTML-markeringen

De browser zoekt naar specifieke verwerkingsinstructies om te bepalen waar content moet worden gepatcht.

<!-- A simple insertion point -->
<div id="sidebar">
  <?marker name="dynamic-comments">
</div>

<!-- A replacement range with loading state -->
<main>
  <?start name="main-content">
    <div class="skeleton">Loading content...</div>
  <?end>
</main>

... (other content) ...

<!-- Later in the same stream, provide the content -->
<template for="dynamic-comments">
  <div class="comments">...</div>
</template>

<template for="main-content">
  <article>
    <h1>The Real Content</h1>
    <p>This replaces the skeleton div automatically.</p>
  </article>
</template>

Nadat een <template> een invoegpunt heeft vervangen, bestaat dat invoegpunt niet meer en kan het niet opnieuw worden gebruikt. Een <template> kan wel invoegpunten bevatten die later in de stream kunnen worden gebruikt.

 <?start name="replaced-content">
  <div class="skeleton">
<?end>
<!-- Later in the same stream, provide initial content -->
<template for="replaced-content">
  <?start name="replaced-content">
    <div>Quickly computed result</div>
  <?end>
</template>
<!-- Later in the same stream, provide full content -->
<template for="replaced-content">
  <?start name="replaced-content">
    <div>Full result</div>
  <?end>
</template>

2. Imperatieve JS-streaming

De browser biedt een reeks van 24 methoden (6 acties, elk met 4 varianten) om de DOM statisch (vanuit een tekenreeks) of via een stream te updaten.

API-referentiematrix

Actie Statisch (veilig) Streaming (veilig) Statisch (onveilig) Streaming (onveilig)
Instellen setHTML() streamHTML() setHTMLUnsafe() streamHTMLUnsafe()
Vervangen replaceWithHTML() streamReplaceWithHTML() replaceWithHTMLUnsafe() streamReplaceWithHTMLUnsafe()
Voor beforeHTML() streamBeforeHTML() beforeHTMLUnsafe() streamBeforeHTMLUnsafe()
Voorvoegen prependHTML() streamPrependHTML() prependHTMLUnsafe() streamPrependHTMLUnsafe()
Toevoegen appendHTML() streamAppendHTML() appendHTMLUnsafe() streamAppendHTMLUnsafe()
Na afterHTML() streamAfterHTML() afterHTMLUnsafe() streamAfterHTMLUnsafe()

Belangrijkste concepten

  • Veilig versus onveilig:
    • Veilige methoden (bijvoorbeeld setHTML()) gebruiken standaard een ingebouwde sanitizer om potentieel gevaarlijke content, zoals <script>-tags, te verwijderen. Je kunt een aangepast Sanitizer-object doorgeven in de opties om toe te passen naast de standaard sanitizer.
    • Onveilige methoden (bijvoorbeeld setHTMLUnsafe()) worden standaard niet opgeschoond, maar je kunt nog steeds een aangepaste Sanitizer doorgeven. Als je wilt dat scripts worden uitgevoerd in de nieuwe content, moet je { runScripts: true } expliciet doorgeven in de opties. BELANGRIJK: Gebruik alleen onveilige methoden met vertrouwde content als veilige methoden niet werken.
  • Statisch versus streaming:
    • Statische methoden nemen een tekenreeks (of TrustedHTML) en passen deze onmiddellijk toe.
    • Streaming-methoden retourneren een WritableStream. Zo kun je content (bijvoorbeeld uit een fetch-reactie) rechtstreeks naar de DOM sturen. De browser rendert de content dan incrementeel naarmate er chunks binnenkomen.

Voorbeeld: Een Fetch-reactie streamen

Je kunt fetch-reacties rechtstreeks naar de DOM doorsluizen zonder handmatige verwerking van segmenten.

const main = document.querySelector('main');
const response = await fetch('/api/partial-update');

// Pipe the stream directly into the element
await response.body
  .pipeThrough(new TextDecoderStream())
  .pipeTo(main.streamHTMLUnsafe({ runScripts: true }));

// Or use the `textStream()` convenience method which streams directly without needing the intermediate `TextDecoderStream()` step
await response
  .textStream()
  .pipeTo(main.streamHTMLUnsafe({ runScripts: true }))

Referentiematrix voor use cases

  • Eilandarchitectuur: Hydrateer onafhankelijke interactieve componenten bovenop statische HTML zonder een volledige framework-runtime.
  • Uitgestelde 'dure' blokken: Lever de shell van een pagina onmiddellijk en stream database-intensieve componenten zodra ze beschikbaar komen.
  • Updates voor apps met één pagina (SPA): Stream grote UI-overgangen in SPA's met streamHTML() in plaats van te wachten op een volledige JSON-payload en handmatige DOM-constructie.

Anti-patronen en waarschuwingen (DOE DIT NIET)

  • Gebruik OOO-streaming niet voor alles. Als je een pagina te veel fragmenteert, kan de stream complexer worden en kan de content worden vertraagd als de browser te veel tijd besteedt aan het patchen van kleine nodes.
  • Negeer Trusted Types niet. Als je *Unsafe-methoden gebruikt in omgevingen waarin Trusted Types worden afgedwongen, moet je een opgeschoond beleidsobject doorgeven als dat vereist is.
  • Ga niet uit van de uitvoeringsvolgorde van scripts. Scripts in een <template for> worden uitgevoerd zodra ze in het document zijn gepatcht. Vertrouw er niet op dat ze in een specifieke volgorde worden uitgevoerd ten opzichte van andere uitgestelde templates.
  • Gebruik deze niet zonder een polyfill-strategie. Zorg dat je een reserveoptie hebt of gebruik polyfills (html-setters-polyfill en template-for-polyfill) om de vereiste browsers te ondersteunen totdat deze functie Baseline wordt.

Reservestrategieën

Je kunt polyfills zoals template-for-polyfill en html-setters-polyfill gebruiken om het API-oppervlak te emuleren voor browsers die dit niet ondersteunen.

Browsers zonder ondersteuning voor html-processing-instructions parseren <?marker>, <?start> en <?end> als reacties. template-for-polyfill herkent deze reacties nog steeds als markeringen, dus gebruik dezelfde syntaxis voor verwerkingsinstructies voor alle browsers. Herschrijf de markeringen niet als HTML-opmerkingen of -elementen voor browsers die deze niet ondersteunen.