La transmisión de HTML desordenada (OOO) (también conocida como actualizaciones parciales declarativas) es una alternativa al procesamiento lineal tradicional "de arriba abajo" del HTML. Permite que los desarrolladores definan marcadores de posición antes en un documento y los "parcheen" con contenido que llega más tarde en el mismo flujo de red o a través de nuevas APIs de transmisión imperativas de JavaScript.
Descripción general rápida
La función consta de dos mecanismos principales:
- Parcheo declarativo (basado en HTML): Usa nuevas instrucciones de procesamiento (
<?marker>,<?start>,<?end>) y<template for>para intercambiar contenido en el DOM a medida que se transmite desde el servidor. - Configuración y transmisión imperativas (basadas en JS): Es un conjunto de métodos nuevos (p.ej.,
streamHTML(),setHTMLUnsafe()) que permiten configurar HTML o canalizar transmisiones legibles directamente a elementos del DOM con un rendimiento nativo y una ejecución de secuencias de comandos opcional.
Prácticas recomendadas
- Prioriza el contenido de la mitad superior de la página: Transmite primero la IU crítica (encabezado, imagen principal, navegación principal y contenido principal). Usa marcadores de posición para componentes "costosos" o de menor prioridad, como comentarios, anuncios o contenido relacionado.
- Implementa estados de carga nativos: Usa los marcadores
<?start>y<?end>para unir el contenido alternativo. El navegador reemplaza automáticamente todo lo que se encuentra entre estos marcadores cuando llega el<template for>correspondiente. - Minimiza el Cumulative Layout Shift (CLS): Proporciona siempre dimensiones estables para los marcadores de posición. Si se desconoce el tamaño final del contenido, usa pantallas esqueleto o CSS
aspect-ratiopara reservar espacio y evitar cambios bruscos. - Accesibilidad y administración del enfoque: Ten en cuenta que las actualizaciones desordenadas pueden confundir a los usuarios de lectores de pantalla. Usa regiones
aria-livepara las actualizaciones importantes y asegúrate de que no se pierda el enfoque si se reemplaza un marcador de posición que contiene el elemento activo.
Detalles de la implementación
1. Marcadores HTML declarativos
El navegador busca instrucciones de procesamiento específicas para identificar dónde se debe aplicar el parche al contenido.
<!-- 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 vez que un <template> reemplaza un punto de inserción, este deja de existir y no se puede volver a usar. Sin embargo, un <template> puede contener puntos de inserción que se pueden usar más adelante en la transmisión.
<?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. Transmisión imperativa de JS
El navegador proporciona un conjunto de 24 métodos (6 acciones, cada una con 4 variaciones) para actualizar el DOM de forma estática (desde una cadena) o a través de una transmisión.
Matriz de referencia de la API
| Acción | Estática (segura) | Transmisión (segura) | Estática (no segura) | Transmisión (no segura) |
|---|---|---|---|---|
| Conjunto | setHTML() |
streamHTML() |
setHTMLUnsafe() |
streamHTMLUnsafe() |
| Reemplazar | replaceWithHTML() |
streamReplaceWithHTML() |
replaceWithHTMLUnsafe() |
streamReplaceWithHTMLUnsafe() |
| Antes | beforeHTML() |
streamBeforeHTML() |
beforeHTMLUnsafe() |
streamBeforeHTMLUnsafe() |
| Anteponer | prependHTML() |
streamPrependHTML() |
prependHTMLUnsafe() |
streamPrependHTMLUnsafe() |
| Agregar | appendHTML() |
streamAppendHTML() |
appendHTMLUnsafe() |
streamAppendHTMLUnsafe() |
| Después | afterHTML() |
streamAfterHTML() |
afterHTMLUnsafe() |
streamAfterHTMLUnsafe() |
Conceptos clave
- Seguro vs. no seguro:
- Los métodos seguros (p.ej.,
setHTML()) usan un limpiador integrado de forma predeterminada para quitar el contenido potencialmente peligroso, como las etiquetas<script>. Puedes pasar un objetoSanitizerpersonalizado en las opciones para que se aplique además del limpiador predeterminado. - Los métodos no seguros (p.ej.,
setHTMLUnsafe()) no se limpian de forma predeterminada, pero puedes pasar unSanitizerpersonalizado. Para permitir que se ejecuten secuencias de comandos en el contenido nuevo, debes pasar{ runScripts: true }de forma explícita en las opciones. IMPORTANTE: Solo usa métodos no seguros con contenido de confianza en el que los métodos seguros no funcionarían.
- Los métodos seguros (p.ej.,
- Estático vs. transmisión:
- Los métodos estáticos toman una cadena (o
TrustedHTML) y la aplican de inmediato. - Los métodos de transmisión devuelven un
WritableStream. Esto te permite canalizar contenido (p.ej., desde una respuesta defetch) directamente al DOM, y el navegador lo renderizará de forma incremental a medida que lleguen los fragmentos.
- Los métodos estáticos toman una cadena (o
Ejemplo: Transmite una respuesta de recuperación
Puedes canalizar las respuestas de recuperación directamente al DOM sin tener que controlar los fragmentos de forma manual.
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 referencia de casos de uso
- Arquitectura de islas: Hidrata componentes interactivos independientes sobre HTML estático sin un entorno de ejecución de framework completo.
- Bloques "costosos" diferidos: Entrega el shell de una página de inmediato y transmite los componentes con un uso intensivo de la base de datos a medida que estén disponibles.
- Actualizaciones de aplicaciones de una sola página (SPA): Transmite transiciones de IU grandes en SPA con
streamHTML()en lugar de esperar una carga útil de JSON completa y la construcción manual del DOM.
Antipatrones y advertencias (NO HAGAS ESTO)
- No uses la transmisión fuera de orden para todo. La fragmentación excesiva de una página puede aumentar la complejidad de la transmisión y, posiblemente, retrasar el contenido si el navegador dedica demasiado tiempo a aplicar parches en nodos pequeños.
- No ignores los Trusted Types. Cuando uses métodos
*Unsafeen entornos con Trusted Types aplicados, asegúrate de pasar un objeto de política saneado si es necesario. - No asumas el orden de ejecución de las secuencias de comandos. Las secuencias de comandos dentro de un
<template for>se ejecutarán en cuanto se inserten en el documento. No dependas de que se ejecuten en una secuencia específica en relación con otras plantillas diferidas. - No lo uses sin una estrategia de polyfill. Asegúrate de tener un resguardo o usar polyfills (
html-setters-polyfillytemplate-for-polyfill) para admitir los navegadores que necesitas hasta que esta función se convierta en Baseline.
Estrategias de resguardo
Puedes usar polyfills como template-for-polyfill y html-setters-polyfill para emular la superficie de la API en navegadores no compatibles.
Los navegadores que no admiten html-processing-instructions analizan <?marker>, <?start> y <?end> como comentarios. template-for-polyfill sigue reconociendo estos comentarios como marcadores, por lo que debes mantener la misma sintaxis de instrucciones de procesamiento para todos los navegadores. No vuelvas a escribir los marcadores como comentarios o elementos HTML para los navegadores que no los admiten.