順不同(OOO)HTML ストリーミング

順不同(OOO)HTML ストリーミング(宣言型部分更新とも呼ばれます)は、従来の「上から下」への HTML の線形処理に代わるものです。これにより、デベロッパーはドキュメントの早い段階でプレースホルダを定義し、同じネットワーク ストリームで後から届くコンテンツや、新しい命令型 JavaScript ストリーミング API を介して届くコンテンツで「パッチ」を適用できます。

概要

この機能は、主に次の 2 つのメカニズムで構成されています。

  1. 宣言型パッチ(HTML ベース): 新しい処理命令(<?marker>、<?start>、<?end>)と <template for> を使用して、サーバーからストリーミングされる DOM 内のコンテンツをスワップします。
  2. 命令型の設定とストリーミング(JS ベース): HTML の設定や、読み取り可能なストリームを DOM 要素に直接パイプ処理できる一連の新しいメソッド(streamHTML()、setHTMLUnsafe() など)。ネイティブ パフォーマンスとオプションのスクリプト実行が可能です。

ベスト プラクティス

  • スクロールせずに見える範囲のコンテンツを優先する: 重要な UI(ヘッダー、ヒーロー、メイン ナビゲーション、メイン コンテンツ)を最初にストリーミングします。コメント、広告、関連コンテンツなど、「負荷の高い」コンポーネントや優先度の低いコンポーネントにはプレースホルダを使用します。
  • ネイティブの読み込み状態を実装する: <?start> マーカーと <?end> マーカーを使用して、代替コンテンツをラップします。対応する <template for> が到着すると、ブラウザはこれらのマーカー間のすべてを自動的に置き換えます。
  • Cumulative Layout Shift(CLS)を最小限に抑える: プレースホルダには常に安定したサイズを指定します。最終的なコンテンツ サイズが不明な場合は、スケルトン画面または CSS aspect-ratio を使用してスペースを確保し、不快なずれを防ぎます。
  • ユーザー補助機能とフォーカス管理: 順序が不適切な更新は、スクリーン リーダーを使用するユーザーを混乱させる可能性があることに注意してください。重要な更新には aria-live リージョンを使用し、アクティブな要素を含むプレースホルダが置き換えられた場合でもフォーカスが失われないようにします。

実装の詳細

1. 宣言型 HTML マーカー

ブラウザは、特定の処理命令を探して、コンテンツをパッチする場所を特定します。

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

<template> が挿入ポイントを置き換えると、その挿入ポイントは存在しなくなり、再利用できなくなります。ただし、<template> には、ストリームの後半で使用できる挿入ポイントが含まれている場合があります。

 <?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. 命令型 JS ストリーミング

ブラウザには、DOM を静的に(文字列から)またはストリーム経由で更新するための 24 個のメソッド(6 つのアクション、それぞれ 4 つのバリエーション)が用意されています。

API リファレンス マトリックス

アクション Static(Safe) Streaming(Safe) 静的(安全でない) ストリーミング(安全でない)
セット setHTML() streamHTML() setHTMLUnsafe() streamHTMLUnsafe()
置き換える replaceWithHTML() streamReplaceWithHTML() replaceWithHTMLUnsafe() streamReplaceWithHTMLUnsafe()
変更前 beforeHTML() streamBeforeHTML() beforeHTMLUnsafe() streamBeforeHTMLUnsafe()
先頭に追加 prependHTML() streamPrependHTML() prependHTMLUnsafe() streamPrependHTMLUnsafe()
追加 appendHTML() streamAppendHTML() appendHTMLUnsafe() streamAppendHTMLUnsafe()
変更後 afterHTML() streamAfterHTML() afterHTMLUnsafe() streamAfterHTMLUnsafe()

主な概念

  • 安全と危険:
    • 安全なメソッド(setHTML() など)は、デフォルトで組み込みのサニタイザーを使用して、<script> タグなどの危険な可能性のあるコンテンツを削除します。オプションでカスタムの Sanitizer オブジェクトを渡して、デフォルトのサニタイザーに加えて適用できます。
    • 安全でないメソッド(setHTMLUnsafe() など)は、デフォルトではサニタイズを行いませんが、カスタムの Sanitizer を渡すことはできます。新しいコンテンツでスクリプトを実行できるようにするには、オプションで { runScripts: true } を明示的に渡す必要があります。重要: 安全でないメソッドは、安全なメソッドが機能しない信頼できるコンテンツでのみ使用してください。
  • 静的とストリーミング:
    • 静的メソッドは、文字列(または TrustedHTML)を受け取り、すぐに適用します。
    • ストリーミング メソッドは WritableStream を返します。これにより、コンテンツ(fetch レスポンスなど)を DOM に直接パイプ処理でき、ブラウザはチャンクが到着するたびに段階的にレンダリングします。

例: フェッチ レスポンスのストリーミング

手動でチャンクを処理することなく、フェッチ レスポンスを DOM に直接パイプできます。

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

ユースケース参照マトリックス

  • アイランド アーキテクチャ: 完全なフレームワーク ランタイムを使用せずに、静的 HTML の上に独立したインタラクティブ コンポーネントをハイドレートします。
  • 「高負荷」ブロックの遅延: ページのシェルをすぐに配信し、データベースに負荷のかかるコンポーネントは、利用可能になった時点でストリーミングします。
  • シングルページ アプリケーション(SPA)の更新: 完全な JSON ペイロードと手動の DOM 構築を待つ代わりに、streamHTML() を使用して SPA で大規模な UI 遷移をストリーミングします。

アンチパターンと警告(行わないでください)

  • すべてのケースで OOO ストリーミングを使用しないでください。ページを過度に断片化すると、ストリームの複雑さが増し、ブラウザが小さなノードのパッチ適用に時間をかけすぎると、コンテンツが遅延する可能性があります。
  • Trusted Types を無視しないでください。Trusted Types が適用されている環境で *Unsafe メソッドを使用する場合は、必要に応じてサニタイズされたポリシー オブジェクトを渡していることを確認してください。
  • スクリプトの実行順序を想定しないでください。<template for> 内のスクリプトは、ドキュメントにパッチが適用されるとすぐに実行されます。他の遅延テンプレートに対して特定の順序で実行されることを前提にしないでください。
  • ポリフィル戦略なしで使用しないでください。この機能が Baseline になるまで、代替を用意するか、ポリフィル(html-setters-polyfill と template-for-polyfill)を使用して、必要なブラウザをサポートしてください。

代替戦略

template-for-polyfill や html-setters-polyfill などのポリフィルを使用すると、サポートされていないブラウザの API サーフェスをエミュレートできます。

html-processing-instructions をサポートしていないブラウザは、<?marker>、<?start>、<?end> をコメントとして解析します。template-for-polyfill は引き続きこれらのコメントをマーカーとして認識するため、すべてのブラウザで同じ処理命令構文を維持します。サポートされていないブラウザ用に、マーカーを HTML コメントや要素として書き換えないでください。