Bildirim Temelli Gölge DOM

Gölge DOM'yi doğrudan HTML'de uygulamanın ve kullanmanın yeni bir yolu.

Bildirimsel Gölge DOM, Chrome'un 90 sürümünden itibaren desteklenen standart bir web platformu özelliğidir. Bu özellikle ilgili spesifikasyonun 2023'te değiştiğini (shadowroot öğesinin shadowrootmode olarak yeniden adlandırılması dahil) ve özelliğin tüm bölümlerinin en güncel standartlaştırılmış sürümlerinin Chrome 124 sürümünde kullanıma sunulduğunu unutmayın.

Gölge DOM, HTML şablonları ve Özel Öğeler tarafından yuvarlanan üç Web Bileşeni standardından biridir. Gölge DOM, CSS stillerinin kapsamını belirli bir DOM alt ağacına uyarlamak ve bu alt ağacı belgenin geri kalanından ayırmak için bir yöntem sunar. <slot> öğesi, Özel Öğenin alt öğelerinin Gölge Ağacı içinde nereye eklenmesi gerektiğini kontrol etmemizi sağlar. Bu özelliklerin bir araya getirilmesiyle, tıpkı yerleşik bir HTML öğesi gibi mevcut uygulamalara sorunsuz bir şekilde entegre olan, bağımsız ve yeniden kullanılabilir bileşenler derlenebilir.

Şu ana kadar Gölge DOM'u kullanmanın tek yolu, JavaScript kullanarak bir gölge kökü oluşturmaktı:

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

Bunun gibi zorunlu bir API, istemci tarafı oluşturmada sorunsuz çalışır: Özel Öğelerimizi tanımlayan JavaScript modüllerinin Gölge Kökleri de kendi Gölge Kökleri oluşturulur ve içerikleri belirlenir. Ancak, birçok web uygulamasının derleme sırasında içeriği sunucu tarafında veya statik HTML'de oluşturması gerekir. Bu, JavaScript çalıştıramayan ziyaretçilere makul bir deneyim sunmanın önemli bir parçası olabilir.

Sunucu Tarafı Oluşturma (SSR) için gerekçeler projeden projeye değişir. Bazı web sitelerinin erişilebilirlik yönergelerine uymak için tümüyle işlevsel, sunucu tarafından oluşturulmuş HTML sağlaması gerekirken bazıları da yavaş bağlantılarda veya cihazlarda iyi bir performans elde etme yolu olarak JavaScript olmadan temel bir deneyim sunmayı tercih eder.

Geçmişte, Gölge DOM'yi Sunucu Tarafı Oluşturma ile birlikte kullanmak zordu. Çünkü, sunucu tarafından oluşturulan HTML'de Gölge Kökleri'ni ifade etmenin yerleşik bir yolu yoktu. Gölge Kökleri, onlar olmadan oluşturulmuş DOM öğelerine eklenirken de performans üzerinde bazı etkiler söz konusudur. Bu durum, sayfa yüklendikten sonra düzenin kaymasına neden olabilir veya Gölge Kökü'nün stil sayfaları yüklenirken geçici olarak biçimlendirilmemiş içeriğin ("FOUC") yanıp sönmesine neden olabilir.

Açıklayıcı Gölge DOM (DSD), bu sınırlamayı kaldırarak Gölge DOM'yi sunucuya getirir.

Bildirim Temelli Gölge Kökü Oluşturma

Bildirim Temelli Gölge Kökü, shadowrootmode özelliğine sahip bir <template> öğesidir:

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

shadowrootmode özelliğine sahip bir şablon öğesi, HTML ayrıştırıcı tarafından algılanır ve hemen üst öğesinin gölge kökü olarak uygulanır. Yukarıdaki örnekten tam HTML işaretlemesinin yüklenmesi aşağıdaki DOM ağacıyla sonuçlanır:

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

Bu kod örneği, Chrome Geliştirici Araçları Öğeleri panelinin Gölge DOM içeriğini görüntüleme kurallarına uymaktadır. Örneğin, 🎃 karakteri, slotlu Işık DOM içeriğini temsil eder.

Bu, bize Gölge DOM'nin statik HTML'de kapsülleme ve alan projeksiyonu avantajlarını sunar. Gölge Kökü de dahil olmak üzere ağacın tamamını üretmek için JavaScript gerekmez.

Bileşen sıvısı

Bildirim temelli Gölge DOM, stilleri kapsüllemek veya alt yerleşimi özelleştirmek için kendi başına kullanılabilir ancak Özel Öğelerle kullanıldığında daha etkilidir. Özel Öğeler kullanılarak oluşturulan bileşenler, statik HTML'den otomatik olarak yeni sürüme geçirilir. Bildirim Temelli Gölge DOM'nin kullanıma sunulmasıyla artık Özel Öğe, yeni sürüme geçirilmeden önce gölge köküne sahip olabilir.

Bildirim Temelli Gölge Kökü içeren HTML'den yükseltilmekte olan bir Özel Öğeye, söz konusu gölge kökü zaten eklenmiş olacaktır. Bu, örneklendirildiğinde öğenin halihazırda bir shadowRoot özelliğine sahip olacağı ve kodunuz açıkça oluşturmadığı anlamına gelir. Öğenizin oluşturucusunda mevcut gölge kökü olup olmadığını kontrol etmek için this.shadowRoot yönergesini kontrol etmeniz önerilir. Zaten bir değer varsa bu bileşenin HTML'si bir Bildirim Temelli Gölge Kökü içerir. Değer null ise HTML'de Bildirim Temelli Gölge Kökü yoktur veya tarayıcı Bildirim Temelli Gölge DOM'u desteklemiyordur.

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

Özel Öğeler bir süredir kullanılıyor ve şu ana kadar attachShadow() kullanarak bir gölge kökü oluşturmadan önce mevcut bir gölge kökü olup olmadığını kontrol etmek gerekmiyor. Bildirim Temelli Gölge DOM, mevcut bileşenlerin buna rağmen çalışmasına olanak tanıyan küçük bir değişiklik içerir: Mevcut bir Declarative Gölgesi Kökü'ne sahip bir öğede attachShadow() yönteminin çağrılması hata vermez. Bunun yerine Bildirim Temelli Gölge Kökü boşaltılır ve geri döndürülür. Bu, bildirim temelli kökler, zorunlu bir değiştirme oluşturulana kadar korunduğu için Declarative Shadow DOM'ye yönelik olarak oluşturulmamış eski bileşenlerin çalışmaya devam etmesini sağlar.

Yeni oluşturulan Özel Öğeler için yeni bir ElementInternals.shadowRoot özelliği, bir öğenin mevcut Bildirim Temelli Gölge Kökü'ne (hem açık hem de kapalı) referans almanın açık bir yolunu sağlar. Bu, herhangi bir Bildirim Temelli Gölge Kökü olup olmadığını kontrol etmek ve kullanmak için kullanılabilir. Aynı zamanda, böyle bir kök sağlanmadığı durumlarda da attachShadow() kullanılır.

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

Kök başına bir gölge

Bildirim Temelli Gölge Kökü, yalnızca kendi üst öğesiyle ilişkilendirilir. Bu, gölge köklerinin her zaman ilişkili öğeleriyle aynı yerde bulunduğu anlamına gelir. Bu tasarım kararı, gölge köklerinin HTML belgesinin geri kalanı gibi akışa uygun olmasını sağlar. Bir öğeye gölge kökü eklemek, mevcut gölge köklerinin kaydını tutmayı gerektirmediğinden yazma ve oluşturma açısından da kullanışlıdır.

Gölge köklerini üst öğeleriyle ilişkilendirmenin olumsuz etkisi, birden fazla öğenin aynı Bildirim Temelli Gölge Kökü <template>'nden başlatılmasının mümkün olmamasıdır. Ancak, Bildirim Temelli Gölge DOM'un kullanıldığı çoğu durumda, her gölge kökünün içeriği nadiren aynı olduğundan bu durum pek olası değildir. Sunucu tarafından oluşturulan HTML genellikle tekrarlanan öğe yapıları içerse de içerikleri genellikle farklılık gösterir (örneğin, metin veya özelliklerdeki küçük değişiklikler). Serileştirilmiş bir Bildirimsel Gölge Kökü'nün içeriği tamamen statik olduğundan, tek bir Bildirim Temelli Gölge Kökü'nden birden fazla öğe yükseltme, yalnızca öğeler aynı olduğunda çalışır. Son olarak, tekrarlanan benzer gölge köklerinin ağ aktarım boyutu üzerindeki etkisi, sıkıştırmadan dolayı nispeten küçüktür.

Gelecekte, paylaşılan gölge köklerine yeniden bakmak mümkün olabilir. DOM, yerleşik şablon oluşturma desteği kazanırsa Bildirimsel Gölge Kökleri, belirli bir öğenin gölge kökünü oluşturmak amacıyla örneklenen şablonlar olarak değerlendirilebilir. Mevcut Bildirim Temelli Gölge DOM tasarımı, gölge kökü ilişkilendirmesini tek bir öğeyle sınırlayarak bu olasılığın gelecekte var olmasını sağlar.

Akış yapmak havalı bir şeydir

Bildirim Temelli Gölge Kökleri doğrudan üst öğeleriyle ilişkilendirmek, bunları yükseltme ve bu öğeye ekleme işlemini basitleştirir. Bildirim Temelli Gölge Kökleri, HTML ayrıştırması sırasında algılanır ve açılış <template> etiketiyle karşılaşıldığında hemen eklenir. <template> içindeki ayrıştırılan HTML, doğrudan gölge köküne ayrıştırılır. Böylece "akışa alınabilir": Alındığı anda oluşturulabilir.

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

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

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

Yalnızca ayrıştırıcı

Bildirim Temelli Gölge DOM, HTML ayrıştırıcının bir özelliğidir. Bu, Bildirim Temelli Gölge Kökünün yalnızca HTML ayrıştırması sırasında bulunan shadowrootmode özelliğine sahip <template> etiketleri için ayrıştırılacağı ve ekleneceği anlamına gelir. Başka bir deyişle, Bildirim Temelli Gölge Kökleri ilk HTML ayrıştırması sırasında oluşturulabilir:

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

Bir <template> öğesinin shadowrootmode özelliğinin ayarlanması hiçbir şey yapmaz ve şablon normal bir şablon öğesi olarak kalır:

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

Güvenlikle ilgili bazı önemli hususlardan kaçınmak için Bildirim Temelli Gölge Kökleri, innerHTML veya insertAdjacentHTML() gibi parça ayrıştırma API'leri kullanılarak da oluşturulamaz. Bildirim Temelli Gölge Kökleri uygulanmış HTML'yi ayrıştırmanın tek yolu setHTMLUnsafe() veya parseHTMLUnsafe() kullanmaktır:

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

Stil ile sunucu oluşturma

Satır içi ve harici stil sayfaları, standart <style> ve <link> etiketleri kullanılarak Bildirim Temelli Gölge Kökleri içinde tam olarak desteklenir:

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

Bu şekilde belirtilen stiller de yüksek düzeyde optimize edilir: Birden fazla Bildirim Temelli Gölge Kökünde aynı stil sayfası varsa sadece bir kez yüklenir ve ayrıştırılır. Tarayıcı, tüm gölge kökleri tarafından paylaşılan tek bir yedekleme CSSStyleSheet kullanarak yinelenen bellek ek yükünü ortadan kaldırır.

Oluşturulabilir Stil Sayfaları, Bildirim Temelli Gölge DOM'de desteklenmez. Bunun nedeni, şu anda HTML'de yapılandırılabilir stil sayfalarını seri hale getirmenin ve adoptedStyleSheets öğesini doldururken bunlara başvurmanın bir yolunun olmamasıdır.

Stilsiz içeriklerin yanıp sönmesini önleme

Bildirimsel Gölge DOM'yi henüz desteklemeyen tarayıcılardaki olası sorunlardan biri, henüz yeni sürüme geçirilmemiş Özel Öğeler için ham içeriğin gösterildiği "stilsiz içerik flash'ı" (FOUC) kullanmaktan kaçınmaktır. Bildirim Temelli Gölge DOM'den önce, FOUC'dan kaçınmak için yaygın olarak kullanılan bir teknik, gölge kökleri eklenmediğinden ve doldurulmadığından henüz yüklenmemiş Özel Öğelere display:none stil kuralı uygulamaktı. Bu şekilde, içerik "hazır" olana kadar görüntülenmez:

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

Bildirimsel Gölge DOM'nin kullanıma sunulmasıyla Özel Öğeler, istemci tarafı bileşen uygulaması yüklenmeden önce gölge içeriği yerinde ve hazır olacak şekilde HTML'de oluşturulabilir veya yazılabilir:

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

Bu durumda display:none "FOUC" kuralı, bildirim temelli gölge kökü içeriğinin gösterilmesini engeller. Ancak bu kuralın kaldırılması, Bildirim Temelli Gölge DOM desteği olmayan tarayıcıların, Bildirim Temelli Gölge DOM polyfill yüklenene ve gölge kökü şablonu gerçek bir gölge köküne dönüştürene kadar yanlış veya biçimlendirilmemiş içerik göstermesine neden olur.

Neyse ki, bu sorun CSS'de FOUC stil kuralı değiştirilerek çözülebilir. Bildirim Temelli Gölge DOM'yi destekleyen tarayıcılarda <template shadowrootmode> öğesi, hemen bir gölge köküne dönüştürülür. Böylece DOM ağacında <template> öğesi kalmaz. Bildirimsel Gölge DOM'yi desteklemeyen tarayıcılar, FOUC'u önlemek için kullanabileceğimiz <template> öğesini korur:

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

Düzeltilmiş "FOUC" kuralı, henüz tanımlanmamış Özel Öğeyi gizlemek yerine, <template shadowrootmode> öğesini takip eden alt öğelerini gizler. Özel öğe tanımlandıktan sonra kural artık eşleşmez. <template shadowrootmode> alt öğesi HTML ayrıştırması sırasında kaldırıldığından, kural, Bildirimsel Gölge DOM'yi destekleyen tarayıcılarda yoksayılır.

Özellik algılama ve tarayıcı desteği

Bildirimsel Gölge DOM, Chrome 90 ve Edge 91'den beri kullanılmaktadır, ancak standartlaştırılmış shadowrootmode özelliği yerine shadowroot adlı, standart olmayan eski bir özellik kullanmaktadır. Yeni shadowrootmode özelliği ve akış davranışı Chrome 111 ve Edge 111'de mevcuttur.

Yeni bir web platformu API'si olan Declarative Shadow DOM, henüz tüm tarayıcılarda yaygın bir şekilde desteklenmemektedir. Tarayıcı desteği, HTMLTemplateElement prototipinde bir shadowRootMode özelliğinin olup olmadığı kontrol edilerek algılanabilir:

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

Çoklu Dolgu

Bildirim temelli Gölge DOM için basitleştirilmiş bir polyfill oluşturmak, nispeten kolaydır. Çünkü bir çoklu dolgunun, tarayıcı uygulamasının kendisiyle alakalı zamanlama semantiğini veya yalnızca ayrıştırıcı özelliklerini mükemmel bir şekilde kopyalaması gerekmez. Bildirimsel Gölge DOM'ye çoklu dolgu yapmak için DOM'yi tarayarak tüm <template shadowrootmode> öğelerini bulabilir ve ardından bunları üst öğesine ekli Gölge Kökleri'ne dönüştürebiliriz. Bu işlem, belge hazır olduğunda yapılabilir veya Özel Öğe yaşam döngüleri gibi daha spesifik etkinlikler tarafından tetiklenebilir.

(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);

Daha fazla bilgi