एलान वाला शैडो डीओएम

डिक्लेरेटिव शैडो डीओएम, वेब प्लैटफ़ॉर्म की स्टैंडर्ड सुविधा है. यह Chrome के 90 वर्शन से काम करती है. ध्यान दें कि इस सुविधा की खास बातें 2023 में बदल गई हैं. इसमें shadowroot को shadowrootmode में बदलना भी शामिल है. साथ ही, इस सुविधा के सभी हिस्सों के सबसे अप-टू-डेट स्टैंडर्ड वर्शन, Chrome के 124 वर्शन में लॉन्च किए गए हैं.

ब्राउज़र के इस्तेमाल से जुड़ी सहायता

  • Chrome: 111.
  • Edge: 111.
  • Firefox: 123.
  • Safari: 16.4.

सोर्स

शैडो डीओएम, वेब कॉम्पोनेंट के तीन स्टैंडर्ड में से एक है. इसमें एचटीएमएल टेंप्लेट और कस्टम एलिमेंट भी शामिल हैं. शैडो डीओएम की मदद से, सीएसएस स्टाइल को किसी खास डीओएम सबट्री तक सीमित किया जा सकता है. साथ ही, उस सबट्री को दस्तावेज़ के बाकी हिस्से से अलग किया जा सकता है. <slot> एलिमेंट की मदद से, यह कंट्रोल किया जा सकता है कि कस्टम एलिमेंट के चाइल्ड एलिमेंट को उसके शैडो ट्री में कहां डाला जाए. इन सुविधाओं की मदद से, ऐसे सिस्टम बनाए जा सकते हैं जो अपने-आप काम करने वाले और फिर से इस्तेमाल किए जा सकने वाले कॉम्पोनेंट बनाते हैं. ये कॉम्पोनेंट, पहले से मौजूद ऐप्लिकेशन में आसानी से इंटिग्रेट हो जाते हैं, ठीक उसी तरह जैसे पहले से मौजूद एचटीएमएल एलिमेंट इंटिग्रेट होते हैं.

अब तक, शैडो DOM का इस्तेमाल करने का एक ही तरीका था. इसके लिए, JavaScript का इस्तेमाल करके शैडो रूट बनाया जाता था:

const host = document.getElementById('host');
const shadowRoot = host.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';

इस तरह का ज़रूरी एपीआई, क्लाइंट-साइड रेंडरिंग के लिए ठीक से काम करता है: हमारे कस्टम एलिमेंट तय करने वाले वही JavaScript मॉड्यूल, उनके शैडो रूट भी बनाते हैं और उनका कॉन्टेंट सेट करते हैं. हालांकि, कई वेब ऐप्लिकेशन को बिल्ड के समय कॉन्टेंट को सर्वर-साइड या स्टैटिक एचटीएमएल में रेंडर करना होगा. यह उन वेबसाइट पर आने वाले लोगों को बेहतर अनुभव देने का एक अहम हिस्सा हो सकता है जो JavaScript को चलाने में सक्षम नहीं हैं.

सर्वर-साइड रेंडरिंग (एसएसआर) की वजहें हर प्रोजेक्ट के हिसाब से अलग-अलग होती हैं. कुछ वेबसाइटों को सुलभता से जुड़े दिशा-निर्देशों का पालन करने के लिए, पूरी तरह से काम करने वाला सर्वर के ज़रिए रेंडर किया गया एचटीएमएल देना होगा. वहीं कुछ वेबसाइटों को यह पक्का करना होगा कि वे धीमे कनेक्शन या डिवाइसों पर अच्छा परफ़ॉर्म करें. इसके लिए, वे बिना JavaScript का बेसलाइन अनुभव देने का विकल्प चुनती हैं.

अब तक, सर्वर-साइड रेंडरिंग के साथ शैडो DOM का इस्तेमाल करना मुश्किल रहा है. ऐसा इसलिए, क्योंकि सर्वर से जनरेट किए गए एचटीएमएल में शैडो रूट को एक्सप्रेस करने का कोई तरीका पहले से मौजूद नहीं था. शैडो रूट को डीओएम एलिमेंट में अटैच करने पर, परफ़ॉर्मेंस पर भी असर पड़ता है. ये उन डीओएम एलिमेंट होते हैं जो इनके बिना पहले ही रेंडर किए जा चुके हैं. इसकी वजह से, पेज लोड होने के बाद लेआउट शिफ़्ट हो सकता है या Shadow Root की स्टाइलशीट लोड करते समय बिना स्टाइल वाली सामग्री ("FOUC") का फ़्लैश दिखा सकता है.

डिक्लेरेटिव शैडो डीओएम (डीएसडी) इस पाबंदी को हटा देता है और शैडो डीओएम को सर्वर पर ला देता है.

डिक्लेरेटिव शैडो रूट बनाने का तरीका

डिक्लेरेटिव शैडो रूट, shadowrootmode एट्रिब्यूट वाला <template> एलिमेंट है:

<host-element>
  <template shadowrootmode="open">
    <slot></slot>
  </template>
  <h2>Light content</h2>
</host-element>

एचटीएमएल पार्सर, shadowrootmode एट्रिब्यूट वाले टेंप्लेट एलिमेंट का पता लगाता है और उसे अपने पैरंट एलिमेंट के शैडो रूट के तौर पर तुरंत लागू करता है. ऊपर दिए गए सैंपल नतीजों से प्योर एचटीएमएल मार्कअप को नीचे दिए गए डीओएम ट्री में लोड करने से लोड होता है:

<host-element>
  #shadow-root (open)
  <slot>
    ↳
    <h2>Light content</h2>
  </slot>
</host-element>

यह कोड सैंपल, Chrome DevTools के एलिमेंट पैनल के उन तरीकों का पालन करता है जो शैडो डीओएम कॉन्टेंट दिखाने के लिए इस्तेमाल किए गए हैं. उदाहरण के लिए, वर्ण, स्लॉट किए गए लाइट डीओएम कॉन्टेंट के बारे में बताता है.

इससे हमें स्टैटिक एचटीएमएल में, शैडो DOM के कंटेनमेंट और स्लॉट प्रोजेक्शन के फ़ायदे मिलते हैं. पूरे ट्री को जनरेट करने के लिए, शैडो रूट के साथ-साथ JavaScript की ज़रूरत नहीं होती.

कॉम्पोनेंट हाइड्रेशन

डिक्लेरेटिव शैडो DOM का इस्तेमाल स्टाइल को एनकैप्सुलेट करने या चाइल्ड प्लेसमेंट को पसंद के मुताबिक बनाने के लिए किया जा सकता है. हालांकि, कस्टम एलिमेंट के साथ इस्तेमाल करने पर यह सबसे ज़्यादा असरदार होता है. कस्टम एलिमेंट का इस्तेमाल करके बनाए गए कॉम्पोनेंट, स्टैटिक एचटीएमएल से अपने-आप अपग्रेड हो जाते हैं. डिक्लेरेटिव शैडो डीओएम की शुरुआत होने के बाद, अब किसी कस्टम एलिमेंट के अपग्रेड होने से पहले उसमें शैडो रूट बनाया जा सकता है.

एचटीएमएल से अपग्रेड किए जा रहे कस्टम एलिमेंट में शैडो रूट पहले से ही अटैच होगा. इसमें डिक्लेरेटिव शैडो रूट भी शामिल होगा. इसका मतलब है कि इंस्टैंशिएट किए जाने पर, एलिमेंट में shadowRoot प्रॉपर्टी पहले से ही उपलब्ध होगी. इसमें, आपका कोड साफ़ तौर पर नया कोड नहीं बनाएगा. अपने एलिमेंट के कन्स्ट्रक्टर में मौजूद किसी भी मौजूदा शैडो रूट के लिए, this.shadowRoot देखना सबसे अच्छा है. अगर पहले से कोई वैल्यू मौजूद है, तो इस कॉम्पोनेंट के एचटीएमएल में डिक्लेरेटिव शैडो रूट शामिल किया जाता है. अगर वैल्यू शून्य है, तो इसका मतलब है कि एचटीएमएल में कोई डिक्लेरेटिव शैडो रूट मौजूद नहीं है या ब्राउज़र, डिक्लेरेटिव शैडो DOM के साथ काम नहीं करता.

<menu-toggle>
  <template shadowrootmode="open">
    <button>
      <slot></slot>
    </button>
  </template>
  Open Menu
</menu-toggle>
<script>
  class MenuToggle extends HTMLElement {
    constructor() {
      super();

      // Detect whether we have SSR content already:
      if (this.shadowRoot) {
        // A Declarative Shadow Root exists!
        // wire up event listeners, references, etc.:
        const button = this.shadowRoot.firstElementChild;
        button.addEventListener('click', toggle);
      } else {
        // A Declarative Shadow Root doesn't exist.
        // Create a new shadow root and populate it:
        const shadow = this.attachShadow({mode: 'open'});
        shadow.innerHTML = `<button><slot></slot></button>`;
        shadow.firstChild.addEventListener('click', toggle);
      }
    }
  }

  customElements.define('menu-toggle', MenuToggle);
</script>

कस्टम एलिमेंट काफ़ी समय से इस्तेमाल किए जा रहे हैं और अब तक attachShadow() का इस्तेमाल करके शैडो रूट बनाने से पहले, मौजूदा शैडो रूट की जांच करने की कोई ज़रूरत नहीं थी. डिक्लेरेटिव शैडो डीओएम में एक छोटा सा बदलाव होता है, जो इसके बावजूद मौजूदा कॉम्पोनेंट को काम करने देता है: मौजूदा घोषणा वाले शैडो रूट वाले एलिमेंट पर attachShadow() तरीके को कॉल करने से गड़बड़ी नहीं होगी. इसके बजाय, डिक्लेरेटिव शैडो रूट को खाली कर दिया जाता है और उसे वापस लाया जाता है. इससे डिक्लेरेटिव शैडो डीओएम के लिए बनाए गए पुराने कॉम्पोनेंट को काम करने की अनुमति नहीं मिलती, क्योंकि डिक्लेरेटिव रूट तब तक सुरक्षित रहते हैं, जब तक ज़रूरी बदलाव नहीं किया जाता.

नए कस्टम एलिमेंट के लिए, नई ElementInternals.shadowRoot प्रॉपर्टी की मदद से, एलिमेंट के मौजूदा डिक्लेरेटिव शैडो रूट का रेफ़रंस साफ़ तौर पर दिया जा सकता है. इसमें, ओपन और क्लोज़्ड, दोनों तरह के कस्टम एलिमेंट शामिल हैं. इसका इस्तेमाल किसी भी डिक्लेरेटिव शैडो रूट की जांच करने और उसे इस्तेमाल करने के लिए किया जा सकता है. साथ ही, अगर आपने कोई शैडो रूट नहीं दिया है, तो इस तरीके से attachShadow() पर वापस आया जा सकता है.

class MenuToggle extends HTMLElement {
  constructor() {
    super();

    const internals = this.attachInternals();

    // check for a Declarative Shadow Root:
    let shadow = internals.shadowRoot;

    if (!shadow) {
      // there wasn't one. create a new Shadow Root:
      shadow = this.attachShadow({
        mode: 'open'
      });
      shadow.innerHTML = `<button><slot></slot></button>`;
    }

    // in either case, wire up our event listener:
    shadow.firstChild.addEventListener('click', toggle);
  }
}

customElements.define('menu-toggle', MenuToggle);

हर रूट के लिए एक शैडो

एलान वाला शैडो रूट सिर्फ़ अपने पैरंट एलिमेंट से जुड़ा होता है. इसका मतलब है कि शैडो रूट हमेशा अपने एलिमेंट के साथ मौजूद होते हैं. डिज़ाइन से जुड़े इस फ़ैसले से यह पक्का हो जाता है कि शैडो रूट को एचटीएमएल दस्तावेज़ के बाकी हिस्से की तरह स्ट्रीम किया जा सकता है. यह संलेखन और जनरेट करने के लिए भी सुविधाजनक है, क्योंकि किसी तत्व में शैडो रूट जोड़ने के लिए मौजूदा शैडो रूट की रजिस्ट्री को बनाए रखने की ज़रूरत नहीं होती.

शैडो रूट को उनके पैरंट एलिमेंट से जोड़ने का नुकसान यह है कि एक ही डिक्लेरेटिव शैडो रूट <template> से कई एलिमेंट को शुरू नहीं किया जा सकता. हालांकि, ज़्यादातर मामलों में ऐसा होने की संभावना नहीं होती है, जहां डिक्लेरेटिव शैडो डीओएम का इस्तेमाल किया जाता है. ऐसा इसलिए, क्योंकि हर शैडो रूट का कॉन्टेंट शायद ही एक जैसा हो. सर्वर से रेंडर किए गए एचटीएमएल में अक्सर एलिमेंट के दोहराए गए स्ट्रक्चर होते हैं, लेकिन उनका कॉन्टेंट आम तौर पर अलग होता है. उदाहरण के लिए, टेक्स्ट या एट्रिब्यूट में थोड़े-बहुत अंतर. सीरियल के डिक्लेरेटिव शैडो रूट का कॉन्टेंट पूरी तरह से स्टैटिक होता है. इसलिए, एक डिक्लेरेटिव शैडो रूट से कई एलिमेंट को अपग्रेड करने की सुविधा सिर्फ़ तब काम करेगी, जब एलिमेंट एक जैसे होंगे. आखिर में, कंप्रेशन के असर की वजह से, नेटवर्क ट्रांसफ़र साइज़ पर बार-बार एक जैसे शैडो रूट का असर कम पड़ता है.

आने वाले समय में, ऐसा हो सकता है कि शैडो रूट का फिर से इस्तेमाल किया जा सके. अगर DOM में टेंप्लेट बनाने की सुविधा काम करती है, तो एलिमेंट के लिए शैडो रूट बनाने के लिए, एलिमेंट के एलान वाले शैडो रूट को टेंप्लेट के तौर पर इस्तेमाल किया जा सकता है. डिक्लेरेटिव शैडो डीओएम डिज़ाइन की मदद से, आने वाले समय में शैडो रूट असोसिएशन को किसी एक एलिमेंट तक सीमित करके, इस संभावना को बनाए रखा जा सकता है.

स्ट्रीमिंग अच्छी है

डिक्लेरेटिव शैडो रूट को सीधे उनके पैरंट एलिमेंट से जोड़ने पर, उन्हें अपग्रेड करने और उस एलिमेंट से अटैच करने की प्रोसेस आसान हो जाती है. डिक्लेरेटिव शैडो रूट का पता एचटीएमएल पार्स करने के दौरान लगाया जाता है. साथ ही, उनका ओपनिंग <template> टैग मिलते ही, ये रूट अटैच हो जाते हैं. <template> में पार्स किया गया एचटीएमएल, सीधे शैडो रूट में पार्स किया जाता है, ताकि उसे "स्ट्रीम" किया जा सके: इसे मिलने के साथ ही रेंडर किया जाता है.

<div id="el">
  <script>
    el.shadowRoot; // null
  </script>

  <template shadowrootmode="open">
    <!-- shadow realm -->
  </template>

  <script>
    el.shadowRoot; // ShadowRoot
  </script>
</div>

सिर्फ़ पार्सर

डिक्लेरेटिव शैडो DOM, एचटीएमएल पार्सर की एक सुविधा है. इसका मतलब है कि एलान वाला शैडो रूट, सिर्फ़ उन <template> टैग के लिए पार्स और अटैच किया जाएगा जिनमें shadowrootmode एट्रिब्यूट मौजूद है और जो एचटीएमएल पार्स करने के दौरान मौजूद हैं. दूसरे शब्दों में, शुरुआती एचटीएमएल पार्स करने के दौरान डिक्लेरेटिव शैडो रूट बनाए जा सकते हैं:

<some-element>
  <template shadowrootmode="open">
    shadow root content for some-element
  </template>
</some-element>

<template> एलिमेंट का shadowrootmode एट्रिब्यूट सेट करने से कुछ नहीं होता और टेंप्लेट एक सामान्य टेंप्लेट एलिमेंट बना रहता है:

const div = document.createElement('div');
const template = document.createElement('template');
template.setAttribute('shadowrootmode', 'open'); // this does nothing
div.appendChild(template);
div.shadowRoot; // null

सुरक्षा से जुड़ी कुछ अहम बातों से बचने के लिए, innerHTML या insertAdjacentHTML() जैसे फ़्रैगमेंट पार्स करने वाले एपीआई का इस्तेमाल करके, एलान वाले शैडो रूट भी नहीं बनाए जा सकते. डिक्लेरेटिव शैडो रूट की मदद से एचटीएमएल को पार्स करने के लिए, setHTMLUnsafe() या parseHTMLUnsafe() का इस्तेमाल करें:

<script>
  const html = `
    <div>
      <template shadowrootmode="open"></template>
    </div>
  `;
  const div = document.createElement('div');
  div.innerHTML = html; // No shadow root here
  div.setHTMLUnsafe(html); // Shadow roots included
  const newDocument = Document.parseHTMLUnsafe(html); // Also here
</script>

स्टाइल के साथ सर्वर-रेंडरिंग

इनलाइन और बाहरी स्टाइलशीट, स्टैंडर्ड <style> और <link> टैग का इस्तेमाल करके डिक्लेरेटिव शैडो रूट में पूरी तरह से काम करती हैं:

<nineties-button>
  <template shadowrootmode="open">
    <style>
      button {
        color: seagreen;
      }
    </style>
    <link rel="stylesheet" href="/comicsans.css" />
    <button>
      <slot></slot>
    </button>
  </template>
  I'm Blue
</nineties-button>

इस तरह से तय की गई स्टाइल भी काफ़ी ऑप्टिमाइज़ की जाती हैं: अगर एक ही स्टाइल शीट, कई डिक्लेरेटिव शैडो रूट में मौजूद है, तो उसे सिर्फ़ एक बार लोड और पार्स किया जाता है. ब्राउज़र, एक बैकिंग CSSStyleSheet का इस्तेमाल करता है, जिसे सभी शैडो रूट शेयर करते हैं. इससे डुप्लीकेट मेमोरी ओवरहेड की समस्या खत्म हो जाती है.

बनाई जा सकने वाली स्टाइलशीट, डिक्लेरेटिव शैडो DOM में काम नहीं करती हैं. इसकी वजह यह है कि फ़िलहाल, एचटीएमएल में कंस्ट्रक्शन वाली स्टाइलशीट को क्रम में लगाने का कोई तरीका नहीं है. साथ ही, adoptedStyleSheets की जानकारी अपने-आप भरने के दौरान, इन स्टाइलशीट को रेफ़र करने का कोई तरीका भी नहीं है.

बिना स्टाइल वाले कॉन्टेंट को फ़्लैश होने से रोकने का तरीका

जिन ब्राउज़र में अब तक डिक्लेरेटिव शैडो डीओएम काम नहीं करता उनमें "बिना स्टाइल वाले कॉन्टेंट का फ़्लैश" (एफ़ओयूसी) से बचने की एक समस्या है. इसमें, उन कस्टम एलिमेंट के लिए रॉ कॉन्टेंट दिखाया जाता है जिन्हें अब तक अपग्रेड नहीं किया गया है. डिक्लेरेटिव शैडो DOM से पहले, एफ़ओयूसी से बचने का एक आम तरीका यह था कि उन कस्टम एलिमेंट पर display:none स्टाइल नियम लागू किया जाना जो अब तक लोड नहीं हुए हैं. इसकी वजह यह है कि इन एलिमेंट में शैडो रूट अटैच और पॉप्युलेट नहीं किया गया है. इस तरह, कॉन्टेंट तब तक नहीं दिखता, जब तक कि वह "तैयार न हो":

<style>
  x-foo:not(:defined) > * {
    display: none;
  }
</style>

डिक्लेरेटिव शैडो डीओएम की मदद से, कस्टम एलिमेंट को एचटीएमएल में इस तरह रेंडर या लिखा जा सकता है कि उनका शैडो कॉन्टेंट अपनी जगह पर मौजूद हो और क्लाइंट-साइड कॉम्पोनेंट के लागू होने से पहले तैयार हो:

<x-foo>
  <template shadowrootmode="open">
    <style>h2 { color: blue; }</style>
    <h2>shadow content</h2>
  </template>
</x-foo>

इस मामले में, display:none "FOUC" नियम, डिक्लेरेटिव शैडो रूट के कॉन्टेंट को दिखने से रोक देगा. हालांकि, इस नियम को हटाने से, डिक्लेरेटिव शैडो डीओएम सपोर्ट वाले ब्राउज़र में गलत या बिना स्टाइल वाला कॉन्टेंट तब तक दिखेगा, जब तक कि डिक्लेरेटिव शैडो डीओएम पॉलीफ़िल लोड नहीं हो जाता और शैडो रूट टेंप्लेट असली शैडो रूट में नहीं बदल देता.

अच्छी बात यह है कि इसे एफ़ओयूसी शैली के नियम में बदलाव करके सीएसएस में हल किया जा सकता है. जिन ब्राउज़र में डिक्लेरेटिव शैडो डीओएम की सुविधा काम करती है उनमें <template shadowrootmode> एलिमेंट को तुरंत शैडो रूट में बदल दिया जाता है. इससे, डीओएम ट्री में कोई <template> एलिमेंट नहीं बचता. डिक्लेरेटिव शैडो डीओएम के साथ काम न करने वाले ब्राउज़र, <template> एलिमेंट को सुरक्षित रखते हैं. इसका इस्तेमाल, एफ़ओयूसी को रोकने के लिए किया जा सकता है:

<style>
  x-foo:not(:defined) > template[shadowrootmode] ~ *  {
    display: none;
  }
</style>

अब तक तय नहीं किए गए कस्टम एलिमेंट को छिपाने के बजाय, बदले गए "FOUC" नियम के तहत, <template shadowrootmode> एलिमेंट को फ़ॉलो करने पर इसके बच्चे छिप जाते हैं. कस्टम एलिमेंट तय करने के बाद, नियम मैच नहीं होता. इस नियम को उन ब्राउज़र में अनदेखा कर दिया जाता है जिनमें डिक्लेरेटिव शैडो डीओएम काम करता है, क्योंकि एचटीएमएल पार्स करने के दौरान <template shadowrootmode> चाइल्ड को हटा दिया जाता है.

सुविधा की पहचान और ब्राउज़र के साथ काम करने की सुविधा

डिक्लेरेटिव शैडो DOM, Chrome 90 और Edge 91 से उपलब्ध है. हालांकि, इसमें स्टैंडर्ड shadowrootmode एट्रिब्यूट के बजाय, shadowroot नाम के पुराने नॉन-स्टैंडर्ड एट्रिब्यूट का इस्तेमाल किया गया है. नया shadowrootmode एट्रिब्यूट और स्ट्रीमिंग का तरीका, Chrome 111 और Edge 111 में उपलब्ध है.

नए वेब प्लैटफ़ॉर्म एपीआई के तौर पर, Deflarative Shadow DOM को सभी ब्राउज़र पर बड़े पैमाने पर इस्तेमाल नहीं किया जा सकता. ब्राउज़र के साथ काम करने का पता लगाने के लिए, HTMLTemplateElement के प्रोटोटाइप में shadowRootMode प्रॉपर्टी की मौजूदगी का पता लगाएं:

function supportsDeclarativeShadowDOM() {
  return HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode');
}

Polyfill

डिक्लेरेटिव शैडो डीओएम के लिए आसान पॉलीफ़िल बनाना काफ़ी आसान है. ऐसा इसलिए है, क्योंकि पॉलीफ़िल को टाइमिंग सेमेटिक्स या सिर्फ़ पार्स करने वाले टूल की उन विशेषताओं को पूरी तरह से दोहराने की ज़रूरत नहीं होती जिनसे ब्राउज़र को काम करने में मदद मिलती है. पॉलीफ़िल डिक्लेरेटिव शैडो DOM को स्कैन करके, सभी <template shadowrootmode> एलिमेंट ढूंढे जा सकते हैं. इसके बाद, उन्हें पैरंट एलिमेंट पर अटैच किए गए शैडो रूट में बदला जा सकता है. दस्तावेज़ तैयार होने पर या कस्टम एलिमेंट की लाइफ़साइकल जैसे कुछ खास इवेंट से ट्रिगर होने पर, यह प्रोसेस पूरी की जा सकती है.

(function attachShadowRoots(root) {
  root.querySelectorAll("template[shadowrootmode]").forEach(template => {
    const mode = template.getAttribute("shadowrootmode");
    const shadowRoot = template.parentNode.attachShadow({ mode });

    shadowRoot.appendChild(template.content);
    template.remove();
    attachShadowRoots(shadowRoot);
  });
})(document);

इसके बारे में और पढ़ें