Declarative Shadow DOM
Declarative Shadow DOM คืออะไร
หัวข้อที่มีชื่อว่า “Declarative Shadow DOM คืออะไร”Declarative Shadow DOM (DSD) ช่วยให้คุณแนบ shadow root เข้ากับ element โดยใช้แค่ HTML โดยไม่ต้องใช้ JavaScript เลย สิ่งนี้เหมาะอย่างยิ่งสำหรับ server-side rendering (SSR) ที่ shadow tree สามารถถูกรวมไว้ใน HTML response เริ่มต้น และ browser จะแนบ shadow root ให้ก่อนที่ JavaScript ใด ๆ จะทำงาน
attribute shadowrootmode
หัวข้อที่มีชื่อว่า “attribute shadowrootmode”การวาง <template shadowrootmode="open"> (หรือ "closed") เป็น child โดยตรงของ element จะทำให้ browser แนบ shadow root เข้ากับ element แม่โดยอัตโนมัติ และย้ายเนื้อหาของ template เข้าไปในนั้น
<my-card> <template shadowrootmode="open"> <style> p { color: #0070f3; font-weight: bold; } </style> <p>This content lives in the shadow root.</p> <slot></slot> </template> Light DOM content here (goes into the slot)</my-card>แท็ก <template> จะถูก browser ใช้งานไปและถูกแทนที่ด้วย shadow root ที่ถูกแนบ ไม่ต้องใช้ JavaScript เลย element จะมี shadow root ทันทีตอน parse
เรื่องราวของการ hydration
หัวข้อที่มีชื่อว่า “เรื่องราวของการ hydration”เมื่อ JavaScript โหลดขึ้นมา customElements.define('my-card', MyCard) จะอัปเกรด element หาก class เรียก attachShadow ใน constructor ของตัวเอง browser จะข้ามการสร้าง shadow root ตัวที่สอง (เพราะมีอยู่แล้ว) และนำตัวที่มีอยู่กลับมาใช้ใหม่ นั่นหมายความว่าเนื้อหาที่ถูก render จากเซิร์ฟเวอร์จะถูกรักษาไว้ — คุณสมบัตินี้สำคัญมากสำหรับ SSR hydration
class MyCard extends HTMLElement { connectedCallback() { // Shadow root already attached declaratively — shadowRoot is available const shadow = this.shadowRoot; // not null if (shadow) { // hydrate: add event listeners, update state, etc. } }}customElements.define('my-card', MyCard);หมายเหตุเรื่องการรองรับของ browser
หัวข้อที่มีชื่อว่า “หมายเหตุเรื่องการรองรับของ browser”Declarative Shadow DOM (attribute มาตรฐาน shadowrootmode) รองรับใน Chrome 111+, Edge 111+, Safari 16.4+ และ Firefox 123+ — ข้าม browser หลักทั้งหมดตั้งแต่ต้นปี 2024 เมื่อ Firefox เริ่มรองรับ (ก่อนหน้านั้นมี attribute shadowroot แบบไม่เป็นมาตรฐานใน Chrome 90–110 เท่านั้น ซึ่งถูกลบไปแล้วและแทนที่ด้วย shadowrootmode — อย่าสับสนกับ tutorial เก่า) ใน browser รุ่นเก่า attribute <template shadowrootmode> จะไม่เป็นที่รู้จัก และ template จะยังคงเฉื่อย (inert) อยู่ ให้ใส่ polyfill สำหรับ browser รุ่นเก่า:
// Polyfill for older browsersdocument.querySelectorAll('template[shadowrootmode]').forEach(function(t) { t.parentElement.attachShadow({ mode: t.getAttribute('shadowrootmode') }) .appendChild(t.content.cloneNode(true)); t.remove();});method คู่หนึ่งที่เกี่ยวข้องกันคือ shadowRoot.getHTML() และ shadowRoot.setHTMLUnsafe() ซึ่งรองรับข้าม browser หลัก (Baseline) ตั้งแต่เดือนกันยายน 2024 getHTML() แปลง shadow root กลับเป็น HTML string — เป็นด้าน “อ่าน” ที่คู่กับ declarative shadow DOM — ส่วน setHTMLUnsafe() ช่วยให้คุณ parse string ที่มี markup <template shadowrootmode> ให้กลายเป็น shadow tree จริงได้ ซึ่ง innerHTML เพียงอย่างเดียวทำไม่ได้
เดโมที่รันได้
หัวข้อที่มีชื่อว่า “เดโมที่รันได้”LivePreview ด้านล่างแสดง <dsd-card> ที่มี <template shadowrootmode="open"> อยู่ใน HTML แล้ว slot จะรับข้อความจาก light DOM ไม่ต้องใช้ JavaScript เพื่อให้ shadow แนบ
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
Declarative Shadow DOM (<template shadowrootmode>) | shadow root มีอยู่ตั้งแต่ parse HTML เลย ไม่ต้องรอ JavaScript โหลดเพื่อ render — สำคัญมากสำหรับ SSR และ first paint | server ต้อง duplicate markup ของ template ไว้ในทุก instance ของ component ทำให้ payload HTML ใหญ่ขึ้นตามจำนวน component บนหน้า |
| attachShadow แบบ JavaScript ปกติ | markup ฝั่ง server เล็กกว่า ไม่มี template ซ้ำ | ต้องรอ JavaScript โหลดและรันก่อน ระหว่างนั้นผู้ใช้อาจเห็นเนื้อหาไม่มีสไตล์ (FOUC) หรือไม่เห็นเนื้อหาเลย |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- สับสนว่าต้องเรียก
attachShadowซ้ำในconnectedCallback— ถ้า shadow root ถูกแนบแบบ declarative ไว้แล้ว browser จะข้ามให้เฉย ๆ ไม่ throw error แต่ถ้า logic ของคุณ assume ว่าต้องสร้างเองเสมอ อาจเขียน initialization ผิดที่และเผลอทับ shadow root ที่มีอยู่แล้ว - ไม่เช็คการรองรับของ browser ก่อน deploy — browser รุ่นเก่าที่ไม่รองรับ DSD จะ render
<template>เป็น element ธรรมดาที่มองเห็นได้ ทำให้ markup ภายในหลุดออกมาแสดงตรง ๆ กลางหน้า - ไม่ตรวจสอบ
this.shadowRootก่อน hydrate ฝั่ง client — ถ้า client-side code สร้าง shadow root ใหม่ทับของเดิมโดยไม่เช็คก่อน จะทำให้เนื้อหาที่ render จาก server หายไปและต้อง render ซ้ำโดยไม่จำเป็น เสียประโยชน์ของ DSD ไปเปล่า ๆ
💡 ตัวอย่างจากของจริง
Lit SSR (
@lit-labs/ssr) — render component เป็น Declarative Shadow DOM บนฝั่ง server แล้วส่ง HTML ที่มี shadow tree ครบไปให้ browser ทันที ลด time-to-first-paint และไม่ต้อง render ซ้ำตอน hydrate (lit.dev/docs/ssr)Adobe Spectrum Web Components — ออกแบบให้ compatible กับ SSR และ Declarative Shadow DOM เพื่อให้ design system ขนาดใหญ่ render ได้เร็วตั้งแต่ first paint โดยไม่ต้องรอ JavaScript hydrate (opensource.adobe.com)