สตรีมมิง HTML แบบไม่เรียงตามลำดับ (OOO)

สตรีมมิง HTML แบบไม่เรียงลำดับ (OOO) (หรือที่เรียกว่าการอัปเดตบางส่วนแบบประกาศ) เป็นทางเลือกแทนการประมวลผล HTML เชิงเส้นแบบ "จากบนลงล่าง" แบบดั้งเดิม ซึ่งช่วยให้นักพัฒนาแอปกำหนดตัวยึดตำแหน่งไว้ก่อนหน้าในเอกสารและ "แพตช์" ตัวยึดตำแหน่งเหล่านั้นด้วยเนื้อหาที่มาถึงภายหลังในสตรีมเครือข่ายเดียวกัน หรือผ่าน API การสตรีม JavaScript แบบบังคับใหม่

ภาพรวมโดยย่อ

ฟีเจอร์นี้ประกอบด้วยกลไกหลัก 2 อย่าง ได้แก่

  1. การแพตช์แบบประกาศ (อิงตาม HTML): ใช้คำสั่งการประมวลผลใหม่ (<?marker>, <?start>, <?end>) และ <template for> เพื่อสลับเนื้อหาใน DOM ขณะที่สตรีมจากเซิร์ฟเวอร์
  2. การตั้งค่าและการสตรีมแบบบังคับ (อิงตาม JS): ชุดเมธอดใหม่ (เช่น streamHTML(), setHTMLUnsafe()) ที่ช่วยให้ตั้งค่า HTML หรือส่งสตรีมที่อ่านได้ไปยังองค์ประกอบ DOM โดยตรงด้วยประสิทธิภาพแบบเนทีฟและการเรียกใช้สคริปต์ที่ไม่บังคับ

แนวทางปฏิบัติแนะนำ

  • จัดลำดับความสำคัญของเนื้อหาครึ่งหน้าบน: สตรีม UI ที่สำคัญ (ส่วนหัว, Hero, การนำทางหลัก, เนื้อหาหลัก) ก่อน ใช้ตัวยึดตำแหน่งสำหรับคอมโพเนนต์ที่ "มีค่าใช้จ่ายสูง" หรือมีลำดับความสำคัญต่ำกว่า เช่น ความคิดเห็น โฆษณา หรือเนื้อหาที่เกี่ยวข้อง
  • ใช้สถานะการโหลดแบบเนทีฟ: ใช้เครื่องหมาย <?start> และ <?end> เพื่อรวมเนื้อหาทางเลือก เบราว์เซอร์จะแทนที่ทุกอย่างระหว่างเครื่องหมายเหล่านี้โดยอัตโนมัติเมื่อ <template for> ที่เกี่ยวข้องมาถึง
  • ลด Cumulative Layout Shift (CLS): ระบุขนาดที่แน่นอนสำหรับตัวยึดตำแหน่งเสมอ หากไม่ทราบขนาดเนื้อหาสุดท้าย ให้ใช้หน้าจอโครงร่างหรือ CSS aspect-ratio เพื่อจองพื้นที่และป้องกันการเปลี่ยนแปลงที่ทำให้เกิดการกระตุก
  • การช่วยเหลือพิเศษและการจัดการโฟกัส: โปรดทราบว่าการอัปเดตที่ไม่เป็นไปตามลำดับอาจทำให้ผู้ใช้โปรแกรมอ่านหน้าจอสับสนได้ ใช้ aria-live regions สำหรับการอัปเดตที่สำคัญ และตรวจสอบว่าโฟกัสไม่หายไปหากมีการแทนที่ตัวยึดตำแหน่งที่มีองค์ประกอบที่ใช้งานอยู่

รายละเอียดการใช้งาน

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

การดำเนินการ คงที่ (ปลอดภัย) สตรีมมิง (ปลอดภัย) คงที่ (ไม่ปลอดภัย) สตรีมมิง (ไม่ปลอดภัย)
Set setHTML() streamHTML() setHTMLUnsafe() streamHTMLUnsafe()
แทนที่ replaceWithHTML() streamReplaceWithHTML() replaceWithHTMLUnsafe() streamReplaceWithHTMLUnsafe()
ก่อน beforeHTML() streamBeforeHTML() beforeHTMLUnsafe() streamBeforeHTMLUnsafe()
Prepend prependHTML() streamPrependHTML() prependHTMLUnsafe() streamPrependHTMLUnsafe()
ต่อท้าย appendHTML() streamAppendHTML() appendHTMLUnsafe() streamAppendHTMLUnsafe()
หลัง afterHTML() streamAfterHTML() afterHTMLUnsafe() streamAfterHTMLUnsafe()

หัวข้อสำคัญ

  • ปลอดภัยเทียบกับไม่ปลอดภัย:
    • เมธอดที่ปลอดภัย (เช่น setHTML()) จะใช้เครื่องมือทำความสะอาดในตัวโดยค่าเริ่มต้นเพื่อตัดเนื้อหาที่อาจเป็นอันตราย เช่น แท็ก <script> คุณสามารถส่งออบเจ็กต์ Sanitizer ที่กำหนดเองในตัวเลือกเพื่อใช้เพิ่มเติมจากเครื่องมือทำความสะอาดเริ่มต้นได้
    • เมธอดที่ไม่ปลอดภัย (เช่น setHTMLUnsafe()) จะไม่ล้างข้อมูลโดยค่าเริ่มต้น แต่คุณยังคงส่ง Sanitizer ที่กำหนดเองได้ หากต้องการอนุญาตให้สคริปต์ทำงานในเนื้อหาใหม่ คุณต้องส่ง { runScripts: true } ในตัวเลือกอย่างชัดเจน สำคัญ: ใช้วิธีที่ไม่ปลอดภัยกับเนื้อหาที่เชื่อถือได้เท่านั้นในกรณีที่วิธีที่ปลอดภัยใช้ไม่ได้ผล
  • แบบคงที่เทียบกับแบบสตรีมมิง:
    • เมธอด Static จะใช้สตริง (หรือ 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): สตรีมการเปลี่ยนผ่าน UI ขนาดใหญ่ใน SPA โดยใช้ streamHTML() แทนการรอเพย์โหลด JSON แบบเต็มและการสร้าง DOM ด้วยตนเอง

รูปแบบที่ควรหลีกเลี่ยงและคำเตือน (อย่าทำเช่นนี้)

  • อย่าใช้สตรีมมิงแบบ OOO กับทุกอย่าง การกระจาย Fragment ของหน้าเว็บมากเกินไปอาจเพิ่มความซับซ้อนของสตรีมและอาจทำให้เนื้อหาล่าช้าหากเบราว์เซอร์ใช้เวลามากเกินไปในการแพตช์โหนดขนาดเล็ก
  • อย่าละเลยประเภทที่เชื่อถือได้ เมื่อใช้เมธอด *Unsafe ในสภาพแวดล้อมที่มีการบังคับใช้ประเภทที่เชื่อถือได้ โปรดตรวจสอบว่าคุณส่งออบเจ็กต์นโยบายที่ผ่านการตรวจสอบแล้ว หากจำเป็น
  • อย่าคาดเดาลำดับการเรียกใช้สคริปต์ สคริปต์ภายใน <template for> จะทำงานทันทีที่มีการแพตช์ลงในเอกสาร อย่าพึ่งพาการดำเนินการตามลำดับที่เฉพาะเจาะจงซึ่งเกี่ยวข้องกับเทมเพลตที่เลื่อนเวลาอื่นๆ
  • อย่าใช้โดยไม่มีกลยุทธ์ Polyfill ตรวจสอบว่าคุณมีทางเลือกสำรองหรือใช้ Polyfill (html-setters-polyfill และ template-for-polyfill) เพื่อรองรับเบราว์เซอร์ที่จำเป็นจนกว่าฟีเจอร์นี้จะกลายเป็น Baseline

กลยุทธ์สำรอง

คุณสามารถใช้ Polyfill เช่น template-for-polyfill และ html-setters-polyfill เพื่อจำลองแพลตฟอร์ม API สำหรับเบราว์เซอร์ที่ไม่รองรับ

เบราว์เซอร์ที่ไม่มีการรองรับ html-processing-instructions จะแยกวิเคราะห์ <?marker>, <?start> และ <?end> เป็นความคิดเห็น template-for-polyfill ยังคงจดจำความคิดเห็นเหล่านี้เป็นเครื่องหมาย ดังนั้นให้ใช้ไวยากรณ์คำสั่งการประมวลผลเดียวกันสำหรับเบราว์เซอร์ทั้งหมด อย่าเขียนเครื่องหมายใหม่เป็นความคิดเห็นหรือองค์ประกอบ HTML สำหรับเบราว์เซอร์ที่ไม่รองรับ