Bildirim Temelli Gölge DOM

Bildirimsel Gölge DOM, Chrome'un 90 sürümünden itibaren desteklenen bir standart web platformu özelliğidir. Bu özelliğin spesifikasyonunun 2023'te değiştiğini (shadowroot'ün 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.

Tarayıcı Desteği

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

Kaynak

Gölge DOM, HTML şablonları ve Özel Öğeler ile birlikte üç Web Bileşenleri standardından biridir. Gölge DOM, CSS stillerinin kapsamını belirli bir DOM alt ağacına uygulamak ve bu alt ağacı dokümanın geri kalanından izole etmek için bir yol sunar. <slot> öğesi, Özel Öğe'nin alt öğelerinin Gölge Ağacı'na nereye eklenmesi gerektiğini kontrol etmemizi sağlar. Bu özelliklerin birlikte kullanılması, yerleşik bir HTML öğesi gibi mevcut uygulamalara sorunsuz bir şekilde entegre edilebilen bağımsız, yeniden kullanılabilir bileşenler derlemesine olanak tanıyan bir sistem sağlar.

Ş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ülleri aynı zamanda Gölge Köklerini de oluşturur ve içeriklerini ayarlar. 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ında Oluşturma (SSR) için gerekçeler projeden projeye değişir. Bazı web siteleri, erişilebilirlik yönergelerini karşılamak için sunucu tarafından oluşturulan, tam işlevli HTML sağlamalıdır. Diğerleri ise yavaş bağlantılarda veya cihazlarda iyi performans sağlamak için JavaScript içermeyen temel bir deneyim sunmayı tercih eder.

Geçmişte, sunucu tarafından oluşturulan HTML'de Gölge Kökleri'ni ifade etmenin yerleşik bir yolu olmadığından Gölge DOM'u sunucu tarafı oluşturma ile birlikte kullanmak zordu. Gölge Kökleri, onlar olmadan oluşturulmuş DOM öğelerine eklenirken de performans üzerinde bazı etkileri vardır. 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 yanıp sönmesi ("FOUC") gösterilebilir.

Bildirimsel Gölge DOM (DSD), bu sınırlamayı kaldırarak Gölge DOM'u sunucuya getirir.

Açıklayıcı gölge kökü oluşturma

Açıklayıcı 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, ışık DOM'u içeriğini temsil eder.

Bu sayede, statik HTML'de Gölge DOM'un kapsayıcı özelliğinden ve slot projeksiyonundan yararlanabiliriz. Gölge kökü dahil olmak üzere ağacın tamamının oluşturulması için JavaScript gerekmez.

Bileşen su içmesi

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 en güçlüdür. Özel Öğeler kullanılarak oluşturulan bileşenler, statik HTML'den otomatik olarak yükseltilir. Açıklayıcı Gölge DOM'un kullanıma sunulmasıyla birlikte, özel öğelerin yükseltilmeden önce gölge kökü olması artık mümkün.

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ı this.shadowRoot kontrol etmeniz önerilir. Zaten bir değer varsa bu bileşenin HTML'sinde açıklayıcı gölge kökü bulunur. Değer null ise HTML'de beyansal gölge kökü bulunmaz veya tarayıcı beyansal gölge DOM'u desteklemez.

<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 şimdiye kadar attachShadow() kullanarak yeni bir gölge kök oluşturmadan önce mevcut bir gölge kök olup olmadığını kontrol etmeye gerek yoktu. Açıklayıcı Gölge DOM, mevcut bileşenlerin buna rağmen çalışmasını sağlayan küçük bir değişiklik içerir: Mevcut bir açıklayıcı gölge kökü olan bir öğede attachShadow() yönteminin çağrılması hata oluşturmaz. Bunun yerine, beyan dilinde gölge kökü boşaltılır ve döndürülür. Zorunlu bir değişim oluşturulana kadar beyan kökleri korunduğundan, beyan DOM'u için oluşturulmayan eski bileşenlerin çalışmaya devam etmesine olanak tanır.

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 Açıklayıcı Gölge Kökü olup olmadığını kontrol etmek ve kullanmak için kullanılabilir. Bu durumda, sağlanmayan durumlarda attachShadow() değerine geri dönülebilir.

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öklerin her zaman ilişkili öğeleriyle birlikte yerleştirildiği 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 için de kullanışlıdır.

Gölge kökleri üst öğeleriyle ilişkilendirmenin dezavantajı, aynı beyansal gölge kökünden <template> birden fazla öğenin başlatılamamasıdır. Ancak her gölge kökünün içeriği nadiren aynı olduğundan, beyansal gölge DOM'un kullanıldığı çoğu durumda bu durumun önemli olması olası değildir. Sunucu tarafından oluşturulan HTML genellikle yinelenen öğe yapıları içerir ancak bu yapıların içeriği genellikle farklıdır (ör. metin veya özelliklerde küçük farklılıklar). 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, sıkıştırmanın etkileri nedeniyle, benzer gölge köklerin tekrarlanmasının ağ aktarımı boyutu üzerindeki etkisi nispeten küçüktür.

Gelecekte, paylaşılan gölge kökleri tekrar ziyaret edilebilir. DOM, yerleşik şablon oluşturma desteği kazanırsa Bildirim Temelli Gölge Kökleri, belirli bir öğenin gölge kökünü oluşturmak üzere örneklenen şablonlar olarak değerlendirilebilir. Mevcut beyansal gölge DOM tasarımı, gölge kök ilişkilendirmesini tek bir öğeyle sınırlayarak bu olasılığın gelecekte var olmasına olanak tanır.

Akış yapmak havalı bir şeydir

Açıklayıcı gölge köklerini doğrudan üst öğeleriyle ilişkilendirmek, bunları yükseltme ve ilgili öğeye ekleme sürecini basitleştirir. Açıklayıcı Gölge Kökleri, HTML ayrıştırma sırasında algılanır ve açılış <template> etiketleriyle karşılaşıldığında hemen eklenir. <template> içindeki ayrıştırılmış HTML doğrudan gölge köke ayrıştırılır. Bu sayede "akış şeklinde" oynatılabilir: Alındığı şekilde oluşturulur.

<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, açıklayıcı gölge kökün yalnızca HTML ayrıştırma sırasında mevcut olan bir shadowrootmode özelliğine sahip <template> etiketleri için ayrıştırılacağı ve ekleneceği anlamına gelir. Diğer bir deyişle, beyansal gölge kökler ilk HTML ayrıştırma işlemi sırasında oluşturulabilir:

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

<template> öğesinin shadowrootmode özelliğini ayarlamak 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

Bazı önemli güvenlik hususlarını önlemek için innerHTML veya insertAdjacentHTML() gibi parça ayrıştırma API'leri kullanılarak da beyan biçiminde gölge kökler oluşturulamaz. Açıklayıcı 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 oranda optimize edilir: Birden fazla Bildirim Temelli Gölge Kökünde aynı stil sayfası varsa, yalnızca bir kez yüklenir ve ayrıştırılır. Tarayıcı, tüm gölge kökler tarafından paylaşılan tek bir destek CSSStyleSheet kullanır ve böylece yinelenen bellek yükü ortadan kaldırılır.

Oluşturulabilir stil sayfaları, beyansal gölge DOM'da desteklenmez. Bunun nedeni, şu anda HTML'de yapılandırılabilir stil sayfalarını seri hale getirmenin ve adoptedStyleSheets doldururken bunlara başvurmanın mümkün olmamasıdır.

Stili olmayan içeriğin yanıp sönmesini önleme

Henüz beyansal gölge DOM'u desteklemeyen tarayıcılarda karşılaşılabilecek bir sorun, henüz yükseltilmemiş özel öğeler için ham içeriklerin gösterildiği "stil uygulanmamış içeriklerin yanıp sönmesi" (FOUC) sorununu önlemektir. Bildirimsel gölge DOM'den önce, FOUC'den kaçınmak için yaygın olarak kullanılan bir teknik, henüz yüklenmemiş özel öğelere display:none stil kuralı uygulamaktı. Bu öğelerin gölge kökü eklenip doldurulmadığı için bu teknik kullanılıyordu. Bu sayede, içerik "hazır" olana kadar gösterilmez:

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

Açıklayıcı Gölge DOM'un kullanıma sunulmasıyla birlikte Özel Öğeler, gölge içeriklerinin istemci tarafı bileşen uygulaması yüklenmeden önce hazır ve yerinde olacak şekilde HTML'de oluşturulabilir veya oluşturulabilir:

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

Bu durumda, display:none "FOUC" kuralı, açıklayıcı gölge kökün içeriğinin gösterilmesini engeller. Ancak bu kuralın kaldırılması, Bildirimsel Gölge DOM desteği olmayan tarayıcıların, Bildirimsel Gölge DOM polyfill yüklenip gölge kök şablonunu gerçek bir gölge köke dönüştürene kadar yanlış veya stilsiz içerik göstermesine neden olur.

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

<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ırma sırasında kaldırıldığı için kural, beyansal gölge DOM'u 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ılabiliyordu ancak standart shadowrootmode özelliği yerine shadowroot adlı eski bir standart olmayan özellik kullanılıyordu. Yeni shadowrootmode özelliği ve akış davranışı Chrome 111 ve Edge 111'de kullanılabilir.

Yeni bir web platformu API'si olan Declarative Shadow DOM henüz tüm tarayıcılarda yaygın olarak 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

Bildirimsel Gölge DOM için basitleştirilmiş bir polyfill oluşturmak, nispeten kolaydır. Çünkü bir çoklu dolgunun, tarayıcı uygulamasının ilgilendiği 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 olaylar 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