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

सीधे एचटीएमएल में, Shadow DOM को लागू और इस्तेमाल करने का एक नया तरीका.

डिक्लेरेटिव शैडो डीओएम एक वेब प्लैटफ़ॉर्म की सुविधा है. फ़िलहाल, यह स्टैंडर्ड की प्रोसेस के दौरान है. यह Chrome के वर्शन 111 में डिफ़ॉल्ट रूप से चालू रहती है.

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

अब तक, JavaScript का इस्तेमाल करके शैडो रूट बनाना ही Shadow DOM का इस्तेमाल करता है:

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

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

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

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

घोषणात्मक शैडो DOM (डीएसडी) इस सीमा को हटा देता है, जिससे सर्वर पर शैडो DOM आ जाता है.

एक एलान वाला शैडो रूट बनाना

डिक्लेरेटिव शैडो रूट, 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 एलिमेंट पैनल के उन तरीकों का पालन करता है जो शैडो डीओएम कॉन्टेंट दिखाने के लिए किए गए हैं. उदाहरण के लिए, ↳ वर्ण स्लॉट किए गए Light DOM कॉन्टेंट को दिखाता है.

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

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

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

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

<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>

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

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

<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() जैसे फ़्रैगमेंट पार्स करने वाले एपीआई का इस्तेमाल करके भी डिक्लेरेटिव शैडो रूट नहीं बनाए जा सकते. डिक्लेरेटिव शैडो रूट के साथ एचटीएमएल को पार्स करने का सिर्फ़ एक ही तरीका है, एक नया includeShadowRoots विकल्प DOMParser को पास करना:

<script>
  const html = `
    <div>
      <template shadowrootmode="open"></template>
    </div>
  `;
  const div = document.createElement('div');
  div.innerHTML = html; // No shadow root here
  const fragment = new DOMParser().parseFromString(html, 'text/html', {
    includeShadowRoots: true
  }); // Shadow root 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 का इस्तेमाल करता है. इसे सभी शैडो रूट के साथ शेयर किया जाता है. इससे डुप्लीकेट मेमोरी खत्म हो जाती है.

कंस्ट्रक्शनेबल स्टाइलशीट, डिक्लेरेटिव शैडो डीओएम में काम नहीं करती. ऐसा इसलिए, क्योंकि फ़िलहाल एचटीएमएल में बनाने लायक स्टाइलशीट को क्रम से लगाने का कोई तरीका नहीं है और adoptedStyleSheets में जानकारी भरने के दौरान, उन्हें रेफ़र करने का कोई तरीका नहीं है.

बिना स्टाइल वाले कॉन्टेंट के फ़्लैश से बचना

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

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

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

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

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

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

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

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

सुविधा की पहचान और ब्राउज़र सहायता

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

नए वेब प्लैटफ़ॉर्म एपीआई के तौर पर, डिक्लेरेटिव शैडो DOM को फ़िलहाल सभी ब्राउज़र पर बड़े पैमाने पर काम नहीं करता. ब्राउज़र पर काम करने की सुविधा का पता लगाने के लिए, HTMLTemplateElement के प्रोटोटाइप पर shadowRootMode प्रॉपर्टी की जांच करें:

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

पॉलीफ़िल

डिक्लेरेटिव शैडो डीओएम के लिए, आसान पॉलीफ़िल बनाना आसान है. ऐसा इसलिए है, क्योंकि पॉलीफ़िल के लिए टाइमिंग सिमैंटिक या पार्सर वाली विशेषताओं को पूरी तरह से दोहराने की ज़रूरत नहीं होती, जो ब्राउज़र लागू करने से जुड़ी होती हैं. डिक्लेरेटिव शैडो डीओएम को पॉलीफ़िल करने के लिए, हम डीओएम को स्कैन करके सभी <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);

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