Lo streaming HTML fuori ordine (OOO), noto anche come aggiornamenti parziali dichiarativi, è un'alternativa alla tradizionale elaborazione lineare "dall'alto verso il basso" dell'HTML. Consente agli sviluppatori di definire i segnaposto in un punto precedente di un documento e di "correggerli" con contenuti che arrivano in un secondo momento nello stesso stream di rete o tramite nuove API di streaming JavaScript imperative.
Breve panoramica
La funzionalità è costituita da due meccanismi principali:
- Patching dichiarativo (basato su HTML): utilizza nuove istruzioni di elaborazione (
<?marker>,<?start>,<?end>) e<template for>per scambiare i contenuti nel DOM durante lo streaming dal server. - Impostazione e streaming imperativi (basati su JS): una suite di nuovi metodi (ad es.
streamHTML(),setHTMLUnsafe()) che consentono di impostare l'HTML o di inviare stream leggibili direttamente agli elementi DOM con prestazioni native ed esecuzione facoltativa degli script.
Best practice
- Dai la priorità ai contenuti above the fold: trasmetti in streaming prima la UI critica (intestazione, hero, navigazione principale, contenuti principali). Utilizza i segnaposto per i componenti "costosi" o con priorità più bassa, come commenti, annunci o contenuti correlati.
- Implementa gli stati di caricamento nativi: utilizza gli indicatori
<?start>e<?end>per aggregare i contenuti di riserva. Il browser sostituisce automaticamente tutto ciò che si trova tra questi indicatori quando arriva il<template for>corrispondente. - Riduci al minimo il Cumulative Layout Shift (CLS): fornisci sempre dimensioni stabili per i segnaposto. Se le dimensioni finali dei contenuti non sono note, utilizza schermate skeleton o CSS
aspect-ratioper riservare spazio ed evitare spostamenti bruschi. - Accessibilità e gestione dello stato attivo: tieni presente che gli aggiornamenti non ordinati possono confondere gli utenti di screen reader. Utilizza le regioni
aria-liveper gli aggiornamenti critici e assicurati che lo stato attivo non venga perso se un segnaposto contenente l'elemento attivo viene sostituito.
Dettagli di implementazione
1. Marcatori HTML dichiarativi
Il browser cerca istruzioni di elaborazione specifiche per identificare dove applicare la patch ai contenuti.
<!-- 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>
Una volta che un <template> sostituisce un punto di inserimento, quest'ultimo non esiste più e non può essere riutilizzato. Tuttavia, un <template> può contenere punti di inserimento che possono essere utilizzati in un secondo momento nello stream.
<?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 imperativo
Il browser fornisce una suite di 24 metodi (6 azioni, ciascuna con 4 varianti) per aggiornare il DOM in modo statico (da una stringa) o tramite uno stream.
Matrice di riferimento API
| Azione | Statico (sicuro) | Streaming (sicuro) | Statico (non sicuro) | Streaming (non sicuro) |
|---|---|---|---|---|
| Set | setHTML() |
streamHTML() |
setHTMLUnsafe() |
streamHTMLUnsafe() |
| Sostituisci | replaceWithHTML() |
streamReplaceWithHTML() |
replaceWithHTMLUnsafe() |
streamReplaceWithHTMLUnsafe() |
| Prima | beforeHTML() |
streamBeforeHTML() |
beforeHTMLUnsafe() |
streamBeforeHTMLUnsafe() |
| Prepend | prependHTML() |
streamPrependHTML() |
prependHTMLUnsafe() |
streamPrependHTMLUnsafe() |
| Append | appendHTML() |
streamAppendHTML() |
appendHTMLUnsafe() |
streamAppendHTMLUnsafe() |
| Dopo | afterHTML() |
streamAfterHTML() |
afterHTMLUnsafe() |
streamAfterHTMLUnsafe() |
Concetti principali
- Sicuro e non sicuro:
- I metodi sicuri (ad es.
setHTML()) utilizzano per impostazione predefinita un sanitizer integrato per rimuovere contenuti potenzialmente pericolosi come i tag<script>. Puoi passare un oggettoSanitizerpersonalizzato nelle opzioni da applicare in aggiunta al sanitizer predefinito. - I metodi non sicuri (ad es.
setHTMLUnsafe()) non eseguono la sanitizzazione per impostazione predefinita, ma puoi comunque passare unSanitizerpersonalizzato. Per consentire l'esecuzione degli script nei nuovi contenuti, devi passare esplicitamente{ runScripts: true }nelle opzioni. IMPORTANTE: utilizza i metodi non sicuri solo con contenuti attendibili in cui i metodi sicuri non funzionerebbero.
- I metodi sicuri (ad es.
- Statico e in streaming:
- I metodi statici accettano una stringa (o
TrustedHTML) e la applicano immediatamente. - I metodi di streaming restituiscono un
WritableStream. In questo modo, puoi trasferire i contenuti (ad esempio da una rispostafetch) direttamente nel DOM e il browser li visualizzerà in modo incrementale man mano che arrivano i blocchi.
- I metodi statici accettano una stringa (o
Esempio: streaming di una risposta di recupero
Puoi inviare le risposte di recupero direttamente al DOM senza gestione manuale dei blocchi.
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 di riferimento dei casi d'uso
- Island Architecture: idrata componenti interattivi indipendenti su HTML statico senza un runtime del framework completo.
- Blocchi "costosi" differiti: fornisci immediatamente la struttura di una pagina e trasmetti in streaming i componenti che richiedono un uso intensivo del database non appena diventano disponibili.
- Aggiornamenti delle applicazioni a pagina singola (SPA): trasmetti in streaming transizioni UI di grandi dimensioni nelle SPA utilizzando
streamHTML()anziché attendere un payload JSON completo e la costruzione manuale del DOM.
Anti-pattern e avvisi (DA EVITARE)
- Non utilizzare lo streaming OOO per tutto. La frammentazione eccessiva di una pagina può aumentare la complessità dello stream e potenzialmente ritardare i contenuti se il browser impiega troppo tempo per applicare patch a piccoli nodi.
- Non ignorare i Tipi attendibili. Quando utilizzi i metodi
*Unsafein ambienti con Trusted Types applicati, assicurati di passare un oggetto policy sanitizzato, se necessario. - Non dare per scontato l'ordine di esecuzione degli script. Gli script all'interno di un
<template for>verranno eseguiti non appena verranno inseriti nel documento. Non fare affidamento sulla loro esecuzione in una sequenza specifica rispetto ad altri template differiti. - Non utilizzare senza una strategia di polyfill. Assicurati di avere un comportamento di riserva o di utilizzare i polyfill (
html-setters-polyfilletemplate-for-polyfill) per supportare i browser richiesti finché questa funzionalità non diventa Baseline.
Strategie di riserva
Puoi utilizzare polyfill come template-for-polyfill e html-setters-polyfill per emulare la superficie dell'API per i browser che non la supportano.
I browser che non supportano html-processing-instructions analizzano <?marker>, <?start> e <?end> come commenti. template-for-polyfill riconosce comunque questi commenti come indicatori, quindi mantieni la stessa sintassi delle istruzioni di elaborazione per tutti i browser. Non riscrivere gli indicatori come commenti o elementi HTML per i browser che non li supportano.