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.

Browser Support

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

Source

Gölge DOM, HTML şablonları ve Özel Öğeler ile birlikte üç Web Bileşenleri standardından biridir. Gölge DOM, CSS stillerini belirli bir DOM alt ağacı için kapsama alma ve bu alt ağacı belgenin geri kalanından izole etme yöntemi sağlar. <slot> öğesi, bir özel öğenin alt öğelerinin Gölge Ağacı'na nereye ekleneceğini kontrol etmemizi sağlar. Bu özelliklerin bir arada kullanılması, yerleşik bir HTML öğesi gibi mevcut uygulamalara sorunsuz bir şekilde entegre olan, kendi kendine yeten, yeniden kullanılabilir bileşenler oluşturmayı sağlar.

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

Bu gibi zorunlu API'ler istemci tarafı oluşturma için iyi çalışır: Özel öğelerimizi tanımlayan JavaScript modülleri, gölge köklerini 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 olarak 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 verilen 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, daha önce bunlar olmadan oluşturulmuş DOM öğelerine eklendiğinde de performansla ilgili sonuçlar ortaya çıkar. Bu durum, sayfa yüklendikten sonra düzenin kaymasına neden olabilir veya gölge kökün stil sayfaları yüklenirken geçici olarak stil uygulanmamış içeriğin yanıp sönmesine ("FOUC") yol açabilir.

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ısı tarafından algılanır ve hemen üst öğesinin gölge kökü olarak uygulanır. Yukarıdaki örnekteki saf HTML işaretlemesini yüklediğinizde aşağıdaki DOM ağacı oluşur:

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

Bu kod örneği, Chrome DevTools Öğeler panelinin Gölge DOM içeriğini görüntülemeyle ilgili kurallarına uyar. Ö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.

Özel Öğeler ve mevcut Gölge Kökleri'ni algılama

Beyanî Gölge DOM, stilleri kapsayacak veya alt öğe yerleşimini özelleştirecek bir yöntem olarak tek başına kullanılabilir ancak en güçlü performansı Özel Öğeler ile birlikte kullanıldığında gösterir. Özel Öğeler kullanılarak oluşturulan bileşenler, statik HTML'den otomatik olarak yükseltilir. Beyan Edilmiş Gölge DOM'un kullanıma sunulmasıyla birlikte, özel öğelerin yükseltilmeden önce gölge kökü olması artık mümkün.

Ö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öntemi çağrıldığında hata atılmaz. 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 ElementInternals.shadowRoot mülkü, bir öğenin hem açık hem de kapalı olan mevcut açıklayıcı gölge köküne 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.

Browser Support

  • Chrome: 77.
  • Edge: 79.
  • Firefox: 93.
  • Safari: 16.4.

Source

Bileşenlerin nemlendirilmesi

HTML'den yükseltilen ve açıklayıcı gölge kökü içeren bir özel öğeye bu gölge kökü zaten eklenmiştir. Bu, öğenin ElementInternals örneği oluşturulduğunda, kodunuzun açıkça oluşturması gerekmeden önceden mevcut bir shadowRoot özelliğine sahip olacağı anlamına gelir. Öğenizin kurucusundaki mevcut gölge kökü için ElementInternals.shadowRoot değerini kontrol etmeniz önerilir. Zaten bir değer varsa bu bileşenin HTML'sinde bir Declarative Shadow Root 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();

      const supportsDeclarative = HTMLElement.prototype.hasOwnProperty("attachInternals");
      const internals = supportsDeclarative ? this.attachInternals() : undefined;

      const toggle = () => {
        console.log("menu toggled!");
      };

      // 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.firstElementChild.addEventListener("click", toggle);
    }
  }

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

Kök başına bir gölge

Açıklayıcı gölge kökü yalnızca ü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öklerin bir HTML dokümanının geri kalanı gibi aktarılabilir olmasını sağlar. Bir öğeye gölge kök eklemek, mevcut gölge köklerin bir kayıt defterini tutmayı gerektirmediğinden, içerik oluşturma 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 içerikleri genellikle farklıdır (ör. metin veya özelliklerde küçük farklılıklar). Serileştirilmiş bir beyansal gölge kökünün içeriği tamamen statik olduğundan, tek bir beyansal gölge kökünden birden fazla öğeyi yükseltmek yalnızca öğeler aynıysa işe yarar. 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 şablonlama desteği kazanırsa beyansal gölge kökleri, belirli bir öğenin gölge kökünü oluşturmak için örneklendirilen şablonlar olarak değerlendirilebilir. Mevcut Declarative Shadow 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.

Canlı yayın yapmak çok havalı

Açıklayıcı Gölge Kökleri'ni 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> etiketiyle 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ı

Bildirimsel gölge DOM, HTML ayrıştırıcısı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>

Bir <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 sahibi sunucu tarafı oluşturma

Satır içi ve harici stil sayfaları, standart <style> ve <link> etiketleri kullanılarak beyansal gölge kökleri içinde tamamen 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 son derece optimize edilmiştir: Aynı stil sayfası birden fazla açıklayıcı gölge kökünde mevcutsa 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. Bu sayede, 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 oluşturulabilir stil sayfalarını serileştirmenin ve adoptedStyleSheets öğesini doldururken bunlara referans vermenin 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 HTML'de oluşturulabilir veya oluşturulabilir. Böylece, gölge içerikleri istemci tarafı bileşen uygulaması yüklenmeden önce hazır olur:

<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, FOUC'yi önlemek için kullanabileceğimiz <template> öğesini korur:

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

Düzeltilen "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 shadowRootMode mülkünün varlığı kontrol edilerek algılanabilir:

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

Çoklu dolgu

Bir polyfill'in, tarayıcı uygulamasının ilgilendiği zamanlama anlamlarını veya yalnızca ayrıştırıcı özelliklerini mükemmel şekilde kopyalamasına gerek olmadığından, beyan biçimindeki gölge DOM için basitleştirilmiş bir polyfill oluşturmak nispeten kolaydır. Beyan Edilmiş Gölge DOM'u çoklu dolgulamak için tüm <template shadowrootmode> öğelerini bulmak üzere DOM'u tarayabilir ve ardından bunları üst öğelerinde ekli Gölge Köklerine dönüştürebiliriz. Bu işlem, doküman hazır olduğunda yapılabilir veya Özel Öğe yaşam döngüsü gibi daha spesifik etkinlikler tarafından tetiklenebilir.

(function attachShadowRoots(root) {
  if (supportsDeclarativeShadowDOM()) {
    // Declarative Shadow DOM is supported, no need to polyfill.
    return;
  }
  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