O streaming de HTML fora de ordem (OOO, na sigla em inglês), também conhecido como atualizações parciais declarativas, é uma alternativa ao processamento linear tradicional de HTML de cima para baixo. Ele permite que os desenvolvedores definam marcadores de posição no início de um documento e os "corrijam" com conteúdo que chega mais tarde no mesmo stream de rede ou usando novas APIs de streaming imperativas do JavaScript.
Visão geral rápida
O recurso consiste em dois mecanismos principais:
- Patching declarativo (baseado em HTML): usa novas instruções de processamento (
<?marker>,<?start>,<?end>) e<template for>para trocar conteúdo no DOM conforme ele é transmitido do servidor. - Configuração e streaming imperativos (com base em JS): um conjunto de novos métodos (por exemplo,
streamHTML(),setHTMLUnsafe()) que permitem definir HTML ou canalizar streams legíveis diretamente em elementos DOM com desempenho nativo e execução de script opcional.
Práticas recomendadas
- Priorize o conteúdo acima da dobra: transmita primeiro a interface crítica (cabeçalho, imagem principal, navegação principal, conteúdo principal). Use marcadores de posição para componentes "caros" ou de menor prioridade, como comentários, anúncios ou conteúdo relacionado.
- Implementar estados de carregamento nativos: use os marcadores
<?start>e<?end>para envolver o conteúdo substituto. O navegador substitui automaticamente tudo entre esses marcadores quando o<template for>correspondente chega. - Minimizar a Cumulative Layout Shift (CLS): sempre forneça dimensões estáveis para os marcadores de posição. Se o tamanho final do conteúdo for desconhecido, use telas de esqueleto ou CSS
aspect-ratiopara reservar espaço e evitar mudanças bruscas. - Acessibilidade e gerenciamento de foco: lembre-se de que atualizações fora de ordem podem confundir os usuários de leitores de tela. Use regiões
aria-livepara atualizações críticas e garanta que o foco não seja perdido se um marcador de posição que contém o elemento ativo for substituído.
Detalhes de implementação
1. Marcadores HTML declarativos
O navegador procura instruções de processamento específicas para identificar onde o conteúdo precisa ser corrigido.
<!-- 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>
Depois que um <template> substitui um ponto de inserção, esse ponto deixa de existir e não pode ser reutilizado. No entanto, um <template> pode conter pontos de inserção que podem ser usados posteriormente na transmissão.
<?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 de JS imperativo
O navegador oferece um conjunto de 24 métodos (6 ações, cada uma com 4 variações) para atualizar o DOM de forma estática (com uma string) ou por um stream.
Matriz de referência da API
| Ação | Estático (seguro) | Streaming (seguro) | Estático (não seguro) | Streaming (não seguro) |
|---|---|---|---|---|
| Set | setHTML() |
streamHTML() |
setHTMLUnsafe() |
streamHTMLUnsafe() |
| Substituir | replaceWithHTML() |
streamReplaceWithHTML() |
replaceWithHTMLUnsafe() |
streamReplaceWithHTMLUnsafe() |
| Antes | beforeHTML() |
streamBeforeHTML() |
beforeHTMLUnsafe() |
streamBeforeHTMLUnsafe() |
| Prepend | prependHTML() |
streamPrependHTML() |
prependHTMLUnsafe() |
streamPrependHTMLUnsafe() |
| Anexar | appendHTML() |
streamAppendHTML() |
appendHTMLUnsafe() |
streamAppendHTMLUnsafe() |
| Depois | afterHTML() |
streamAfterHTML() |
afterHTMLUnsafe() |
streamAfterHTMLUnsafe() |
Principais conceitos
- Seguro x não seguro:
- Os métodos seguros (por exemplo,
setHTML()) usam um limpador integrado por padrão para remover conteúdo potencialmente perigoso, como tags<script>. É possível transmitir um objetoSanitizerpersonalizado nas opções para aplicar além do limpador padrão. - Os métodos não seguros (por exemplo,
setHTMLUnsafe()) não limpam por padrão, mas você ainda pode transmitir umSanitizerpersonalizado. Para permitir que os scripts sejam executados no novo conteúdo, é necessário transmitir explicitamente{ runScripts: true }nas opções. IMPORTANTE: use métodos não seguros apenas com conteúdo confiável em que os métodos seguros não funcionariam.
- Os métodos seguros (por exemplo,
- Estático x streaming:
- Os métodos estáticos usam uma string (ou
TrustedHTML) e a aplicam imediatamente. - Os métodos de streaming retornam um
WritableStream. Isso permite que você envie conteúdo (por exemplo, de uma respostafetch) diretamente para o DOM, e o navegador vai renderizá-lo de forma incremental à medida que os blocos chegarem.
- Os métodos estáticos usam uma string (ou
Exemplo: como fazer streaming de uma resposta de busca
É possível canalizar as respostas de busca diretamente para o DOM sem processamento manual de blocos.
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 }))
Matriz de referência de casos de uso
- Arquitetura de ilha: hidrate componentes interativos independentes sobre HTML estático sem um ambiente de execução de framework completo.
- Blocos "caros" adiados: entregue o shell de uma página imediatamente e transmita componentes com muitos dados do banco de dados conforme eles forem disponibilizados.
- Atualizações de aplicativos de página única (SPA): transmita grandes transições de UI em SPAs usando
streamHTML()em vez de esperar por um payload JSON completo e pela construção manual do DOM.
Antipadrões e avisos (NÃO FAÇA ISSO)
- Não use o streaming OOO para tudo. A fragmentação excessiva de uma página pode aumentar a complexidade do stream e atrasar o conteúdo se o navegador gastar muito tempo corrigindo nós pequenos.
- Não ignore os Tipos Confiáveis. Ao usar métodos
*Unsafeem ambientes com Tipos Confiáveis aplicados, verifique se você está transmitindo um objeto de política limpo, se necessário. - Não presuma a ordem de execução do script. Os scripts dentro de um
<template for>serão executados assim que forem corrigidos no documento. Não dependa da execução deles em uma sequência específica em relação a outros modelos adiados. - Não use sem uma estratégia de polyfill. Verifique se você tem um substituto ou use polyfills (
html-setters-polyfilletemplate-for-polyfill) para oferecer suporte aos navegadores necessários até que esse recurso se torne Baseline.
Estratégias de fallback
É possível usar polyfills como template-for-polyfill e html-setters-polyfill para emular a superfície da API em navegadores que não oferecem suporte.
Os navegadores sem suporte a html-processing-instructions analisam <?marker>, <?start> e <?end> como comentários. O template-for-polyfill ainda reconhece esses comentários como marcadores. Por isso, mantenha a mesma sintaxe de instrução de processamento para todos os navegadores. Não reescreva os marcadores como comentários ou elementos HTML para navegadores incompatíveis.