Streaming HTML Out-of-Order (OOO)

Streaming HTML Out-of-Order (OOO) (juga dikenal sebagai Update Parsial Deklaratif) adalah alternatif untuk pemrosesan linear HTML "dari atas ke bawah" yang tradisional. Dengan demikian, developer dapat menentukan placeholder lebih awal dalam dokumen dan "menambalnya" dengan konten yang tiba kemudian dalam aliran jaringan yang sama, atau melalui API streaming JavaScript imperatif baru.

Ringkasan Singkat

Fitur ini terdiri dari dua mekanisme utama:

  1. Patching Deklaratif (berbasis HTML): Menggunakan petunjuk pemrosesan baru (<?marker>, <?start>, <?end>) dan <template for> untuk menukar konten di DOM saat di-streaming dari server.
  2. Setelan dan streaming imperatif (berbasis JS): Serangkaian metode baru (misalnya, streamHTML(), setHTMLUnsafe()) yang memungkinkan setelan HTML atau pengalihan aliran data yang dapat dibaca langsung ke elemen DOM dengan performa native dan eksekusi skrip opsional.

Praktik Terbaik

  • Prioritaskan Konten Paruh Atas: Streaming UI penting (header, hero, navigasi utama, konten utama) terlebih dahulu. Gunakan placeholder untuk komponen "mahal" atau berprioritas lebih rendah seperti komentar, iklan, atau konten terkait.
  • Terapkan Status Pemuatan Native: Gunakan penanda <?start> dan <?end> untuk menggabungkan konten pengganti. Browser akan otomatis mengganti semua yang ada di antara penanda ini saat <template for> yang sesuai tiba.
  • Minimalkan Cumulative Layout Shift (CLS): Selalu berikan dimensi yang stabil untuk placeholder. Jika ukuran konten akhir tidak diketahui, gunakan layar skeleton atau aspect-ratio CSS untuk menyediakan ruang dan mencegah pergeseran yang mengganggu.
  • Aksesibilitas & Pengelolaan Fokus: Perhatikan bahwa pembaruan yang tidak berurutan dapat membingungkan pengguna pembaca layar. Gunakan region aria-live untuk update penting dan pastikan fokus tidak hilang jika placeholder yang berisi elemen aktif diganti.

Detail Implementasi

1. Penanda HTML Deklaratif

Browser mencari petunjuk pemrosesan tertentu untuk mengidentifikasi bagian konten yang harus di-patch.

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

Setelah <template> menggantikan titik penyisipan, titik penyisipan tersebut tidak ada lagi, dan tidak dapat digunakan kembali. Namun, <template> dapat berisi titik penyisipan yang dapat digunakan nanti dalam streaming.

 <?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 JS Imperatif

Browser menyediakan rangkaian 24 metode (6 tindakan, masing-masing dengan 4 variasi) untuk memperbarui DOM, baik secara statis (dari string) maupun melalui streaming.

Matriks Referensi API

Tindakan Statis (Aman) Streaming (Aman) Statis (Tidak Aman) Streaming (Tidak Aman)
Set setHTML() streamHTML() setHTMLUnsafe() streamHTMLUnsafe()
Ganti replaceWithHTML() streamReplaceWithHTML() replaceWithHTMLUnsafe() streamReplaceWithHTMLUnsafe()
Sebelum beforeHTML() streamBeforeHTML() beforeHTMLUnsafe() streamBeforeHTMLUnsafe()
Tambahkan di awal prependHTML() streamPrependHTML() prependHTMLUnsafe() streamPrependHTMLUnsafe()
Penambahan appendHTML() streamAppendHTML() appendHTMLUnsafe() streamAppendHTMLUnsafe()
Setelah afterHTML() streamAfterHTML() afterHTMLUnsafe() streamAfterHTMLUnsafe()

Konsep Utama

  • Aman vs. Tidak Aman:
    • Metode aman (misalnya, setHTML()) menggunakan pembersih bawaan secara default untuk menghapus konten yang berpotensi berbahaya seperti tag <script>. Anda dapat meneruskan objek Sanitizer kustom dalam opsi untuk diterapkan selain pembersih default.
    • Metode Unsafe (misalnya, setHTMLUnsafe()) tidak melakukan sanitasi secara default, tetapi Anda masih dapat meneruskan Sanitizer kustom. Untuk mengizinkan skrip dijalankan di konten baru, Anda harus meneruskan { runScripts: true } secara eksplisit dalam opsi. PENTING: Hanya gunakan metode yang tidak aman dengan konten tepercaya jika metode yang aman tidak akan berfungsi.
  • Statis vs. Streaming:
    • Metode statis mengambil string (atau TrustedHTML) dan langsung menerapkannya.
    • Metode Streaming menampilkan WritableStream. Hal ini memungkinkan Anda menyalurkan konten (misalnya, dari respons fetch) langsung ke DOM, dan browser akan merendernya secara bertahap saat potongan data tiba.

Contoh: Melakukan Streaming Respons Pengambilan

Anda dapat menyalurkan respons pengambilan langsung ke DOM tanpa penanganan potongan 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 }))

Matriks Referensi Kasus Penggunaan

  • Island Architecture: Menghidrasi komponen interaktif independen di atas HTML statis tanpa runtime framework penuh.
  • Blok "Berat" yang Ditangguhkan: Menayangkan shell halaman dengan segera dan menstreaming komponen yang berat database saat tersedia.
  • Update Aplikasi Web Satu Halaman (SPA): Streaming transisi UI besar di SPA menggunakan streamHTML(), bukan menunggu payload JSON lengkap dan konstruksi DOM manual.

Anti-Pola & Peringatan (JANGAN LAKUKAN INI)

  • Jangan gunakan streaming OOO untuk semuanya. Fragmentasi halaman yang berlebihan dapat meningkatkan kompleksitas streaming dan berpotensi menunda konten jika browser menghabiskan terlalu banyak waktu untuk melakukan patching pada node kecil.
  • Jangan abaikan Jenis Tepercaya. Saat menggunakan metode *Unsafe di lingkungan dengan Jenis Tepercaya yang diterapkan, pastikan Anda meneruskan objek kebijakan yang telah dibersihkan jika diperlukan.
  • Jangan berasumsi tentang urutan eksekusi skrip. Skrip di dalam <template for> akan dieksekusi segera setelah di-patch ke dalam dokumen. Jangan mengandalkan eksekusi template yang ditangguhkan dalam urutan tertentu dibandingkan dengan template yang ditangguhkan lainnya.
  • Jangan gunakan tanpa strategi polyfill. Pastikan Anda memiliki penggantian atau menggunakan polyfill (html-setters-polyfill dan template-for-polyfill) untuk mendukung browser yang diperlukan hingga fitur ini menjadi Baseline.

Strategi penggantian

Anda dapat menggunakan polyfill seperti template-for-polyfill dan html-setters-polyfill untuk mengemulasi platform API bagi browser yang tidak mendukung.

Browser tanpa dukungan html-processing-instructions akan mengurai <?marker>, <?start>, dan <?end> sebagai komentar. template-for-polyfill masih mengenali komentar ini sebagai penanda, jadi pertahankan sintaksis petunjuk pemrosesan yang sama untuk semua browser. Jangan menulis ulang penanda sebagai komentar atau elemen HTML untuk browser yang tidak mendukung.