Streaming HTML dans le désordre (OOO)

Le streaming HTML Out-of-Order (OOO) (également appelé "mises à jour partielles déclaratives") est une alternative au traitement linéaire traditionnel "de haut en bas" du code HTML. Il permet aux développeurs de définir des espaces réservés plus tôt dans un document et de les "corriger" avec du contenu qui arrive plus tard dans le même flux réseau, ou via de nouvelles API de streaming JavaScript impératives.

Présentation rapide

Cette fonctionnalité repose sur deux mécanismes principaux :

  1. Application de correctifs déclarative (basée sur HTML) : utilise de nouvelles instructions de traitement (<?marker>, <?start>, <?end>) et <template for> pour échanger du contenu dans le DOM à mesure qu'il est diffusé depuis le serveur.
  2. Définition et streaming impératifs (basés sur JS) : suite de nouvelles méthodes (par exemple, streamHTML(), setHTMLUnsafe()) qui permettent de définir du code HTML ou de rediriger des flux lisibles directement vers des éléments DOM avec des performances natives et une exécution de script facultative.

Bonnes pratiques

  • Priorisez le contenu au-dessus de la ligne de flottaison : diffusez d'abord l'UI critique (en-tête, image principale, navigation principale, contenu principal). Utilisez des espaces réservés pour les composants "coûteux" ou de priorité inférieure, comme les commentaires, les annonces ou les contenus associés.
  • Implémenter des états de chargement natifs : utilisez les repères <?start> et <?end> pour encapsuler le contenu de remplacement. Le navigateur remplace automatiquement tout ce qui se trouve entre ces repères lorsque le <template for> correspondant arrive.
  • Réduisez le Cumulative Layout Shift (CLS) : fournissez toujours des dimensions stables pour les espaces réservés. Si la taille finale du contenu est inconnue, utilisez des écrans squelettes ou la propriété CSS aspect-ratio pour réserver de l'espace et éviter les décalages brusques.
  • Accessibilité et gestion du focus : n'oubliez pas que les mises à jour désordonnées peuvent perturber les utilisateurs de lecteurs d'écran. Utilisez des régions aria-live pour les mises à jour critiques et assurez-vous que le curseur n'est pas perdu si un espace réservé contenant l'élément actif est remplacé.

Détails de mise en œuvre

1. Repères HTML déclaratifs

Le navigateur recherche des instructions de traitement spécifiques pour identifier l'emplacement où le contenu doit être corrigé.

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

Une fois qu'un <template> remplace un point d'insertion, ce point d'insertion n'existe plus et ne peut pas être réutilisé. Toutefois, un <template> peut contenir des points d'insertion qui pourront être utilisés plus tard dans le flux.

 <?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. Streaming JS impératif

Le navigateur fournit une suite de 24 méthodes (6 actions, chacune avec 4 variantes) pour mettre à jour le DOM de manière statique (à partir d'une chaîne) ou via un flux.

Matrice de référence des API

Action Statique (sûr) Streaming (Safe) Statique (non fiable) Streaming (non sécurisé)
Ensemble setHTML() streamHTML() setHTMLUnsafe() streamHTMLUnsafe()
Remplacer replaceWithHTML() streamReplaceWithHTML() replaceWithHTMLUnsafe() streamReplaceWithHTMLUnsafe()
Avant beforeHTML() streamBeforeHTML() beforeHTMLUnsafe() streamBeforeHTMLUnsafe()
Ajouter au début prependHTML() streamPrependHTML() prependHTMLUnsafe() streamPrependHTMLUnsafe()
Ajouter appendHTML() streamAppendHTML() appendHTMLUnsafe() streamAppendHTMLUnsafe()
Après afterHTML() streamAfterHTML() afterHTMLUnsafe() streamAfterHTMLUnsafe()

Concepts clés

  • Sûr ou non sûr :
    • Les méthodes Safe (par exemple, setHTML()) utilisent par défaut un outil de nettoyage intégré pour supprimer les contenus potentiellement dangereux, tels que les balises <script>. Vous pouvez transmettre un objet Sanitizer personnalisé dans les options à appliquer en plus du nettoyeur par défaut.
    • Les méthodes non sécurisées (par exemple, setHTMLUnsafe()) n'effectuent pas de nettoyage par défaut, mais vous pouvez toujours transmettre un Sanitizer personnalisé. Pour autoriser l'exécution de scripts dans le nouveau contenu, vous devez transmettre explicitement { runScripts: true } dans les options. IMPORTANT : N'utilisez des méthodes non sécurisées qu'avec du contenu de confiance lorsque les méthodes sécurisées ne fonctionnent pas.
  • Statique ou streaming :
    • Les méthodes statiques prennent une chaîne (ou TrustedHTML) et l'appliquent immédiatement.
    • Les méthodes de streaming renvoient un WritableStream. Cela vous permet de transférer du contenu (par exemple, à partir d'une réponse fetch) directement dans le DOM, et le navigateur l'affichera de manière incrémentielle à mesure que les fragments arrivent.

Exemple : Diffuser une réponse Fetch en streaming

Vous pouvez rediriger les réponses de récupération directement vers le DOM sans gestion manuelle des fragments.

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

Matrice de référence des cas d'utilisation

  • Architecture en îlots : hydratez des composants interactifs indépendants au-dessus du code HTML statique sans environnement d'exécution de framework complet.
  • Blocs "coûteux" différés : diffusez immédiatement l'interface d'une page et diffusez en streaming les composants lourds de la base de données dès qu'ils sont disponibles.
  • Mises à jour des applications monopages (SPA) : diffusez des transitions d'UI volumineuses dans les SPA à l'aide de streamHTML() au lieu d'attendre une charge utile JSON complète et une construction manuelle du DOM.

Antimodèles et avertissements (À NE PAS FAIRE)

  • N'utilisez pas le streaming OOO pour tout. Une fragmentation excessive d'une page peut augmenter la complexité du flux et potentiellement retarder le contenu si le navigateur passe trop de temps à corriger de petits nœuds.
  • N'ignorez pas les Trusted Types. Lorsque vous utilisez des méthodes *Unsafe dans des environnements où les Trusted Types sont appliqués, assurez-vous de transmettre un objet de stratégie nettoyé si nécessaire.
  • Ne présumez pas de l'ordre d'exécution des scripts. Les scripts d'un <template for> s'exécutent dès qu'ils sont intégrés au document. Ne vous fiez pas à leur exécution dans un ordre spécifique par rapport aux autres modèles différés.
  • Ne l'utilisez pas sans stratégie de polyfill. Assurez-vous de disposer d'une solution de remplacement ou d'utiliser des polyfills (html-setters-polyfill et template-for-polyfill) pour prendre en charge les navigateurs requis jusqu'à ce que cette fonctionnalité devienne Baseline.

Stratégies de remplacement

Vous pouvez utiliser des polyfills tels que template-for-polyfill et html-setters-polyfill pour émuler la surface de l'API pour les navigateurs non compatibles.

Les navigateurs qui ne sont pas compatibles avec html-processing-instructions analysent <?marker>, <?start> et <?end> en tant que commentaires. template-for-polyfill reconnaît toujours ces commentaires comme des repères. Conservez donc la même syntaxe d'instruction de traitement pour tous les navigateurs. Ne réécrivez pas les repères sous forme de commentaires ou d'éléments HTML pour les navigateurs non compatibles.