Потоковая передача HTML-кода с нарушением порядка

Потоковая передача HTML-кода с нарушением порядка (также известная как декларативные частичные обновления) – это альтернатива традиционной линейной обработке HTML-кода сверху вниз. Она позволяет разработчикам определять плейсхолдеры в начале документа и заполнять их контентом, который поступает позже в том же сетевом потоке или через новые императивные API потоковой передачи JavaScript.

Краткий обзор

Функция состоит из двух основных механизмов:

  1. Декларативное исправление (на основе HTML). Использует новые инструкции по обработке (<?marker>, <?start>, <?end>) и <template for> для замены контента в DOM по мере его поступления с сервера.
  2. Императивная настройка и потоковая передача (на основе JS). Набор новых методов (например, streamHTML(), setHTMLUnsafe()), которые позволяют настраивать HTML или передавать читаемые потоки непосредственно в элементы DOM с нативной производительностью и необязательным выполнением скриптов.

Рекомендации

  • Приоритет контента в верхней части страницы. Сначала загружайте важные элементы интерфейса (заголовок, главное изображение, основную навигацию, основной контент). Используйте плейсхолдеры для "тяжелых" или менее приоритетных компонентов, таких как комментарии, реклама или статьи по теме.
  • Реализуйте нативные состояния загрузки. Используйте маркеры <?start> и <?end>, чтобы заключить в них резервный контент. Браузер автоматически заменяет все, что находится между этими маркерами, когда поступает соответствующий <template for>.
  • Минимизируйте совокупное смещение макета (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

Браузер предоставляет набор из 24 методов (6 действий, каждое с 4 вариациями) для обновления DOM либо статически (из строки), либо через поток.

Справочная матрица API

Действие Статическое (безопасное) Потоковая передача (безопасно) Статический (небезопасный) Потоковая передача (небезопасно)
Задать 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, и браузер будет отображать его постепенно по мере поступления фрагментов.

Пример: потоковая передача ответа на запрос Fetch

Вы можете передавать ответы на запросы fetch напрямую в 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 без полной среды выполнения фреймворка.
  • Отложенная загрузка "тяжелых" блоков. Сначала загружается оболочка страницы, а затем по мере готовности – компоненты, требующие большого количества запросов к базе данных.
  • Обновления одностраничных приложений. Передавайте крупные переходы интерфейса в одностраничных приложениях с помощью streamHTML(), не дожидаясь полной полезной нагрузки JSON и ручного создания DOM.

Антипаттерны и предупреждения (НЕ ДЕЛАЙТЕ ЭТОГО)

  • Не используйте потоковую передачу OOO для всего. Чрезмерная фрагментация страницы может усложнить поток и потенциально задержать контент, если браузер тратит слишком много времени на исправление небольших узлов.
  • Не игнорируйте доверенные типы. При использовании методов *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 или элементы для неподдерживаемых браузеров.