สตรีมมิง HTML แบบไม่เรียงลำดับ (OOO) (หรือที่เรียกว่าการอัปเดตบางส่วนแบบประกาศ) เป็นทางเลือกแทนการประมวลผล HTML เชิงเส้นแบบ "จากบนลงล่าง" แบบดั้งเดิม ซึ่งช่วยให้นักพัฒนาแอปกำหนดตัวยึดตำแหน่งไว้ก่อนหน้าในเอกสารและ "แพตช์" ตัวยึดตำแหน่งเหล่านั้นด้วยเนื้อหาที่มาถึงภายหลังในสตรีมเครือข่ายเดียวกัน หรือผ่าน API การสตรีม JavaScript แบบบังคับใหม่
ภาพรวมโดยย่อ
ฟีเจอร์นี้ประกอบด้วยกลไกหลัก 2 อย่าง ได้แก่
- การแพตช์แบบประกาศ (อิงตาม HTML): ใช้คำสั่งการประมวลผลใหม่ (
<?marker>,<?start>,<?end>) และ<template for>เพื่อสลับเนื้อหาใน DOM ขณะที่สตรีมจากเซิร์ฟเวอร์ - การตั้งค่าและการสตรีมแบบบังคับ (อิงตาม JS): ชุดเมธอดใหม่ (เช่น
streamHTML(),setHTMLUnsafe()) ที่ช่วยให้ตั้งค่า HTML หรือส่งสตรีมที่อ่านได้ไปยังองค์ประกอบ DOM โดยตรงด้วยประสิทธิภาพแบบเนทีฟและการเรียกใช้สคริปต์ที่ไม่บังคับ
แนวทางปฏิบัติแนะนำ
- จัดลำดับความสำคัญของเนื้อหาครึ่งหน้าบน: สตรีม UI ที่สำคัญ (ส่วนหัว, Hero, การนำทางหลัก, เนื้อหาหลัก) ก่อน ใช้ตัวยึดตำแหน่งสำหรับคอมโพเนนต์ที่ "มีค่าใช้จ่ายสูง" หรือมีลำดับความสำคัญต่ำกว่า เช่น ความคิดเห็น โฆษณา หรือเนื้อหาที่เกี่ยวข้อง
- ใช้สถานะการโหลดแบบเนทีฟ: ใช้เครื่องหมาย
<?start>และ<?end>เพื่อรวมเนื้อหาทางเลือก เบราว์เซอร์จะแทนที่ทุกอย่างระหว่างเครื่องหมายเหล่านี้โดยอัตโนมัติเมื่อ<template for>ที่เกี่ยวข้องมาถึง - ลด Cumulative Layout Shift (CLS): ระบุขนาดที่แน่นอนสำหรับตัวยึดตำแหน่งเสมอ หากไม่ทราบขนาดเนื้อหาสุดท้าย ให้ใช้หน้าจอโครงร่างหรือ CSS
aspect-ratioเพื่อจองพื้นที่และป้องกันการเปลี่ยนแปลงที่ทำให้เกิดการกระตุก - การช่วยเหลือพิเศษและการจัดการโฟกัส: โปรดทราบว่าการอัปเดตที่ไม่เป็นไปตามลำดับอาจทำให้ผู้ใช้โปรแกรมอ่านหน้าจอสับสนได้ ใช้
aria-liveregions สำหรับการอัปเดตที่สำคัญ และตรวจสอบว่าโฟกัสไม่หายไปหากมีการแทนที่ตัวยึดตำแหน่งที่มีองค์ประกอบที่ใช้งานอยู่
รายละเอียดการใช้งาน
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 ได้โดยตรง และเบราว์เซอร์จะแสดงผลเนื้อหาทีละส่วนเมื่อได้รับข้อมูล
- เมธอด Static จะใช้สตริง (หรือ
ตัวอย่าง: การสตรีมการตอบกลับการดึงข้อมูล
คุณสามารถส่งการตอบกลับการดึงข้อมูลไปยัง 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 สำหรับเบราว์เซอร์ที่ไม่รองรับ