Strumieniowanie HTML poza kolejnością (OOO)

Strumieniowanie HTML w dowolnej kolejności (znane też jako deklaratywne częściowe aktualizacje) to alternatywa dla tradycyjnego, liniowego przetwarzania kodu HTML „od góry do dołu”. Umożliwia deweloperom definiowanie obiektów zastępczych wcześniej w dokumencie i „poprawianie” ich treścią, która pojawia się później w tym samym strumieniu sieciowym lub za pomocą nowych interfejsów API do strumieniowego przesyłania JavaScriptu.

Krótkie omówienie

Funkcja składa się z 2 głównych mechanizmów:

  1. Deklaratywne poprawki (oparte na HTML): używa nowych instrukcji przetwarzania (<?marker>, <?start>, <?end>) i <template for> do zamiany treści w DOM podczas przesyłania strumieniowego z serwera.
  2. Imperatywne ustawianie i strumieniowanie (oparte na JS): zestaw nowych metod (np. streamHTML(), setHTMLUnsafe()), które umożliwiają ustawianie kodu HTML lub przekazywanie strumieni do odczytu bezpośrednio do elementów DOM z natywną wydajnością i opcjonalnym wykonywaniem skryptów.

Sprawdzone metody

  • Nadaj priorytet treściom widocznym na ekranie bez przewijania: najpierw przesyłaj strumieniowo kluczowe elementy interfejsu (nagłówek, główny obraz, główną nawigację, główną treść). Używaj obiektów zastępczych w przypadku „kosztownych” lub mniej ważnych komponentów, takich jak komentarze, reklamy czy powiązane artykuły.
  • Wdróż natywne stany ładowania: użyj znaczników <?start> i <?end>, aby opakować treść zastępczą. Przeglądarka automatycznie zastępuje wszystko między tymi znacznikami, gdy nadejdzie odpowiedni <template for>.
  • Minimalizowanie skumulowanego przesunięcia układu (CLS): zawsze podawaj stabilne wymiary obiektów zastępczych. Jeśli ostateczny rozmiar treści jest nieznany, użyj ekranów szkieletowych lub właściwości CSS aspect-ratio, aby zarezerwować miejsce i zapobiec gwałtownym przesunięciom.
  • Ułatwienia dostępu i zarządzanie fokusem: pamiętaj, że aktualizacje w niewłaściwej kolejności mogą dezorientować użytkowników czytników ekranu. Używaj regionów aria-live w przypadku krytycznych aktualizacji i upewnij się, że fokus nie zostanie utracony, jeśli obiekt zastępczy zawierający aktywny element zostanie zastąpiony.

Szczegóły implementacji

1. Deklaratywne znaczniki HTML

Przeglądarka szuka konkretnych instrukcji przetwarzania, aby określić, gdzie należy załatać treść.

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

Gdy <template> zastąpi punkt wstawienia, ten punkt przestaje istnieć i nie można go ponownie użyć. Jednak <template> może zawierać punkty wstawienia, które można wykorzystać później w transmisji.

 <?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. Imperative JS Streaming

Przeglądarka udostępnia zestaw 24 metod (6 działań, z których każde ma 4 odmiany) do aktualizowania modelu DOM statycznie (z ciągu znaków) lub za pomocą strumienia.

Tabela dokumentacji interfejsu API

Działanie Statyczne (bezpieczne) Strumieniowanie (bezpieczne) Statyczne (niebezpieczne) Strumieniowanie (niebezpieczne)
Ustaw setHTML() streamHTML() setHTMLUnsafe() streamHTMLUnsafe()
Zastąp replaceWithHTML() streamReplaceWithHTML() replaceWithHTMLUnsafe() streamReplaceWithHTMLUnsafe()
Przed beforeHTML() streamBeforeHTML() beforeHTMLUnsafe() streamBeforeHTMLUnsafe()
Dodaj na początku prependHTML() streamPrependHTML() prependHTMLUnsafe() streamPrependHTMLUnsafe()
Dołączanie appendHTML() streamAppendHTML() appendHTMLUnsafe() streamAppendHTMLUnsafe()
Po afterHTML() streamAfterHTML() afterHTMLUnsafe() streamAfterHTMLUnsafe()

Kluczowe pojęcia

  • Bezpieczne i niebezpieczne:
    • Bezpieczne metody (np. setHTML()) domyślnie używają wbudowanego mechanizmu czyszczenia, aby usuwać potencjalnie niebezpieczne treści, takie jak tagi <script>. W opcjach możesz przekazać niestandardowy obiekt Sanitizer, który zostanie zastosowany oprócz domyślnego modułu czyszczącego.
    • Metody niebezpieczne (np. setHTMLUnsafe()) domyślnie nie oczyszczają danych, ale nadal możesz przekazać niestandardowy obiekt Sanitizer. Aby zezwolić na wykonywanie skryptów w nowych treściach, musisz wyraźnie przekazać { runScripts: true } w opcjach. WAŻNE: metod niebezpiecznych używaj tylko w przypadku zaufanych treści, gdy metody bezpieczne nie działają.
  • Statyczne a strumieniowe:
    • Metody statyczne przyjmują ciąg znaków (lub TrustedHTML) i natychmiast go stosują.
    • Metody strumieniowe zwracają obiekt WritableStream. Dzięki temu możesz przesyłać treści (np. z odpowiedzi fetch) bezpośrednio do DOM, a przeglądarka będzie je renderować stopniowo w miarę docierania fragmentów.

Przykład: przesyłanie strumieniowe odpowiedzi na żądanie pobrania

Możesz przekazywać odpowiedzi na żądania pobrania bezpośrednio do DOM bez ręcznego obsługiwania fragmentów.

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

Tabela referencyjna przypadków użycia

  • Architektura wyspowa: nawadniaj niezależne komponenty interaktywne na podstawie statycznego kodu HTML bez pełnego środowiska wykonawczego platformy.
  • Odłożone „kosztowne” bloki: natychmiast dostarczaj szkielet strony i przesyłaj strumieniowo komponenty wymagające dużej ilości danych z bazy danych, gdy tylko staną się dostępne.
  • Aktualizacje aplikacji jednostronicowych (SPA): przesyłaj strumieniowo duże przejścia interfejsu w aplikacjach SPA za pomocą streamHTML(), zamiast czekać na pełny ładunek JSON i ręczne tworzenie DOM.

Negatywne wzorce i ostrzeżenia (NIE RÓB TEGO)

  • Nie używaj strumieniowania OOO do wszystkiego. Nadmierna fragmentacja strony może zwiększyć złożoność strumienia i potencjalnie opóźnić wyświetlanie treści, jeśli przeglądarka poświęca zbyt dużo czasu na łatanie małych węzłów.
  • Nie ignoruj typów zaufanych. Jeśli używasz metod *Unsafe w środowiskach, w których wymuszane są typy zaufane, upewnij się, że w razie potrzeby przekazujesz oczyszczony obiekt zasad.
  • Nie zakładaj, że skrypty będą wykonywane w określonej kolejności. Skrypty wewnątrz elementu <template for> zostaną wykonane, gdy tylko zostaną załatane w dokumencie. Nie polegaj na tym, że będą wykonywane w określonej kolejności względem innych odroczonych szablonów.
  • Nie używaj bez strategii polyfill. Upewnij się, że masz kreację zastępczą, lub użyj kodu polyfill (html-setters-polyfill i template-for-polyfill), aby obsługiwać wymagane przeglądarki, dopóki ta funkcja nie stanie się częścią Baseline.

Strategie zastępcze

Możesz używać polyfilli takich jak template-for-polyfill i html-setters-polyfill, aby emulować interfejs API w przeglądarkach, które go nie obsługują.

Przeglądarki bez obsługi html-processing-instructions analizują <?marker>, <?start> i <?end> jako komentarze. template-for-polyfill nadal rozpoznaje te komentarze jako znaczniki, więc zachowaj tę samą składnię instrukcji przetwarzania we wszystkich przeglądarkach. Nie przepisuj znaczników jako komentarzy ani elementów HTML dla przeglądarek, które ich nie obsługują.