ข้ามไปยังเนื้อหา

Declarative Shadow DOM

Declarative Shadow DOM (DSD) ช่วยให้คุณแนบ shadow root เข้ากับ element โดยใช้แค่ HTML โดยไม่ต้องใช้ JavaScript เลย สิ่งนี้เหมาะอย่างยิ่งสำหรับ server-side rendering (SSR) ที่ shadow tree สามารถถูกรวมไว้ใน HTML response เริ่มต้น และ browser จะแนบ shadow root ให้ก่อนที่ JavaScript ใด ๆ จะทำงาน

การวาง <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

เมื่อ 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);

Declarative Shadow DOM รองรับใน Chrome 90+, Edge 90+, Safari 16.4+ และ Firefox 123+ ใน browser รุ่นเก่า attribute <template shadowrootmode> จะไม่เป็นที่รู้จัก และ template จะยังคงเฉื่อย (inert) อยู่ ให้ใส่ polyfill สำหรับ browser รุ่นเก่า:

// Polyfill for older browsers
document.querySelectorAll('template[shadowrootmode]').forEach(function(t) {
t.parentElement.attachShadow({ mode: t.getAttribute('shadowrootmode') })
.appendChild(t.content.cloneNode(true));
t.remove();
});

LivePreview ด้านล่างแสดง <dsd-card> ที่มี <template shadowrootmode="open"> อยู่ใน HTML แล้ว slot จะรับข้อความจาก light DOM ไม่ต้องใช้ JavaScript เพื่อให้ shadow แนบ

ตัวเลือกBenefitCost
Declarative Shadow DOM (<template shadowrootmode>)shadow root มีอยู่ตั้งแต่ parse HTML เลย ไม่ต้องรอ JavaScript โหลดเพื่อ render — สำคัญมากสำหรับ SSR และ first paintserver ต้อง 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

Adobe Spectrum Web Components — ออกแบบให้ compatible กับ SSR และ Declarative Shadow DOM เพื่อให้ design system ขนาดใหญ่ render ได้เร็วตั้งแต่ first paint โดยไม่ต้องรอ JavaScript hydrate

HTML attribute ใดบน element template ที่กระตุ้นให้เกิด Declarative Shadow DOM?
browser แนบ shadow root ใน Declarative Shadow DOM เมื่อใด?
เกิดอะไรขึ้นกับ this.shadowRoot ใน class ของ custom element เมื่อมี declarative shadow root อยู่แล้ว?