Bildirim Temelli Gölge DOM

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

Bildirim Temelli Gölge DOM, şu anda standartlaştırma sürecinde olan bir web platformu özelliğidir. Chrome sürüm 111'de varsayılan olarak etkindir.

Gölge DOM, üç Web Bileşeni standardından biridir. HTML şablonları ve Özel Öğeler ile yuvarlanır. Gölge DOM, CSS stillerini belirli bir DOM alt ağacına kapsamak ve bu alt ağacı, dokümanın geri kalanından izole etmek için bir yol sunar. <slot> öğesi, Özel Öğenin alt öğelerinin kendi Gölge Ağacı içinde nereye ekleneceğini kontrol edebilmemizi sağlar. Bu özellikler bir araya geldiğinde, yerleşik bir HTML öğesi gibi mevcut uygulamalara sorunsuz şekilde entegre edilebilen bağımsız, yeniden kullanılabilir bileşenler oluşturmak için bir sistem sağlar.

Şimdiye 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 gayet iyi çalışır: Özel Öğelerimizi tanımlayan JavaScript modülleri, aynı zamanda kendi Gölge Köklerini oluşturur ve içeriklerini ayarlar. Ancak birçok web uygulamasının derleme sırasında içerik sunucusu tarafında veya statik HTML'ye oluşturması gerekir. Bu, JavaScript'i çalıştıramayan ziyaretçilere makul bir deneyim sunmanın önemli bir parçası olabilir.

Sunucu Tarafı Oluşturma (SSR) gerekçeleri projeden projeye değişir. Bazı web siteleri, erişilebilirlik yönergelerini karşılamak için sunucu tarafından oluşturulmuş tamamen işlevsel bir HTML sağlamalıdır. Diğerleri ise yavaş bağlantılar veya cihazlarda iyi bir performans sağlamak için temel olarak JavaScript içermeyen bir deneyim sunmayı tercih eder.

Geçmişte, Gölge DOM'u Sunucu Tarafı Oluşturma ile birlikte kullanmak zordu, çünkü sunucu tarafından oluşturulan HTML'de Gölge Kökleri ifade etmenin yerleşik bir yolu yoktur. Bunlar olmadan önceden oluşturulmuş DOM öğelerine Gölge Kökler eklemenin de performans üzerinde olumsuz etkileri olabilir. Bu durum, sayfa yüklendikten sonra düzen kaymasına neden olabilir veya Gölge Kök'ün stil sayfaları yüklenirken geçici olarak stilsiz içerik ("FOUC") gösterilebilir.

Bildirimsel Gölge DOM (DSD), bu sınırlamayı kaldırarak sunucuya Gölge DOM'u 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 alınan sadece HTML işaretlemesini yüklemek aşağıdaki DOM ağacında sonuç verir:

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

Bu kod örneği, Gölge DOM içeriğini görüntülemeyle ilgili Chrome DevTools Öğeleri panelinin kurallarına uygundur. Örneğin, Kural karakteri yuvalı Işık DOM içeriğini temsil eder.

Bu, Shadow DOM'un statik HTML'de kapsülleme ve alan projeksiyonu gibi avantajları sağlar. Gölge Kökü de dahil olmak üzere ağacın tamamını oluşturmak için JavaScript'e gerek yoktur.

Bileşen sıvısı

Bildirim Temelli Gölge DOM, stilleri kapsamak veya alt öğe yerleşimini özelleştirmek için tek başına kullanılabilir. Ancak Özel Öğelerle birlikte kullanıldığında çok daha etkilidir. Custom Elements kullanılarak oluşturulan bileşenler, otomatik olarak statik HTML'den yeni sürüme geçirilir. Bildirimli Gölge DOM'un kullanıma sunulmasıyla birlikte, artık Özel Öğeler yükseltilmeden önce bir gölge köküne sahip olabilir.

Bildirimsel Gölge Kökü içeren ve HTML'den yükseltilen bir Özel Öğe, bu gölge köküne zaten eklenmiş olur. Bu, öğenin örneklendiğinde zaten kullanılabilir bir shadowRoot özelliğine sahip olacağı ve kodunuz açıkça böyle bir özellik oluşturmayacağı anlamına gelir. Öğe oluşturucunuzda mevcut herhangi bir gölge kökü olup olmadığını kontrol etmek için this.shadowRoot öğesini kontrol etmeniz önerilir. Zaten bir değer varsa bu bileşenin HTML'si Bildirimsel Gölge Kökü içerir. Değer null ise HTML'de Bildirimsel Gölge Kökü yoktur veya tarayıcı Bildirimsel 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ımdaydı ve şimdiye kadar attachShadow() kullanarak bir gölge kökü oluşturmadan önce mevcut bir gölge kökü olup olmadığını kontrol etmek için bir neden yoktu. Bildirimsel Gölge DOM, mevcut bileşenlerin buna rağmen çalışmasına olanak tanıyan küçük bir değişiklik içerir: Bildirimsel Gölge Kökü'ne sahip bir öğede attachShadow() yönteminin çağrılması hata vermez. Bunun yerine, Bildirimsel Gölge Kökü boşaltılır ve döndürülür. Bildirim temelli kökler zorunlu bir değiştirme işlemi oluşturulana kadar korunur. Bu nedenle, Bildirimsel Gölge DOM için geliştirilmemiş eski bileşenlerin çalışmaya devam etmesine izin verilir.

Yeni oluşturulan Özel Öğeler için yeni ElementInternals.shadowRoot özelliği, bir öğenin açık ve kapalı olan mevcut Bildirimli Gölge Kökü'ne referans almak için açık bir yol sağlar. Bu, Bildirimli Gölge Kök'ü kontrol etmek ve kullanmak için kullanılabilir ancak böyle bir kök sağlanmadığı durumlarda attachShadow() değerine geri döner.

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 ü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 dokümanının geri kalanı gibi akışla yürütülebilmesini sağlar. Bir öğeye gölge kökü eklemek, mevcut gölge köklerinin kaydının tutulmasını gerektirmediğinden yazma ve oluşturma için de uygundur.

Gölge köklerini üst öğeleriyle ilişkilendirmenin karşılığında, birden fazla öğenin aynı Bildirimli Gölge Kökü <template>'nden başlatılması mümkün değildir. Ancak, Bildirimli Gölge DOM'un kullanıldığı çoğu durumda, her bir gölge kökünün içeriği nadiren aynı olduğundan bu yöntem önemli değildir. Sunucu tarafından oluşturulan HTML genellikle tekrarlanan öğe yapıları içerir, ancak içerikleri genellikle farklıdır (metindeki veya özelliklerdeki küçük farklılıklar gibi). Serileştirilmiş Bildirimli Gölge Kökü'nün içerikleri tamamen statik olduğundan, birden fazla öğenin tek bir Bildirimli Gölge Kökü'nden yükseltilmesi yalnızca öğelerin aynı olması halinde çalışır. Son olarak, tekrarlanan benzer gölge köklerinin ağ aktarım boyutu üzerindeki etkisi, sıkıştırmanın etkileri nedeniyle nispeten azdır.

Gelecekte, paylaşılan gölge köklerini yeniden ziyaret etmek mümkün olabilir. DOM, yerleşik şablon oluşturma desteği verirse belirli bir öğe için gölge kökü oluşturmak amacıyla örnek oluşturulan şablonlar olarak kullanılabilir. Mevcut Bildirimsel Gölge DOM tasarımı, gölge kök ilişkilendirmesini tek bir öğeyle sınırlandırarak bu olanağın gelecekte mümkün olmasına olanak tanır.

Canlı yayın havalı bir şey

Bildirimsel Gölge Kökleri'nin doğrudan üst öğesiyle ilişkilendirilmesi, yükseltme ve bu öğeye ekleme sürecini basitleştirir. Bildirimsel Gölge Kökler, HTML ayrıştırması sırasında tespit edilir ve açılış <template> etiketleriyle 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ış gerçekleştirilebilir": Alındığı gibi 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. Yani Bildirimsel Gölge Kökü, yalnızca HTML ayrıştırması sırasında bulunan shadowrootmode özelliğine sahip <template> etiketleri için ayrıştırılır ve eklenir. Başka bir deyişle, Bildirimli Gölge Kökler 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

Bildirimli Gölge Kökler, güvenlikle ilgili bazı önemli noktalardan kaçınmak için innerHTML veya insertAdjacentHTML() gibi parça ayrıştırma API'leri kullanılarak da oluşturulamaz. Bildirimsel Gölge Kökler uygulanmış halde HTML'yi ayrıştırmanın tek yolu, DOMParser öğesine yeni bir includeShadowRoots seçeneği iletmektir:

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

Stille sunucu oluşturma

Satır içi ve harici stil sayfaları, standart <style> ve <link> etiketleri kullanılarak Bildirimli 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: Aynı stil sayfası birden fazla Bildirimsel 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ökleri tarafından paylaşılan tek bir yedekleme CSSStyleSheet kullanarak yinelenen bellek ek yükünü ortadan kaldırır.

Oluşturulabilir Stil Sayfaları, Bildirimli Gölge DOM'da desteklenmez. Bunun nedeni, şu anda HTML'de yapılandırılabilir stil sayfalarını serileştirmenin ve adoptedStyleSheets doldurulurken bunlara başvuruda bulunmanın bir yolu olmamasıdır.

Stilsiz içeriklerin yanıp sönmesinden kaçınma

Bildirimli Gölge DOM'u henüz desteklemeyen tarayıcılardaki olası bir sorun, henüz yeni sürüme geçirilmemiş Custom Elements için ham içeriklerin gösterildiği "biçimlendirilmemiş içeriğin yeniden tetiklenmesi"nden (FOUC) kaçınmaktır. Bildirimsel Gölge DOM'dan önce, FOUC'den kaçınmak için yaygın olarak kullanılan tekniklerden biri, gölge kökü eklenip doldurulmadığı için 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>

Bildirimli Gölge DOM'un kullanıma sunulmasıyla birlikte, Ö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ünün içeriğinin gösterilmesini engeller. Ancak bu kuralın kaldırılması, Bildirimli Gölge DOM'un polyfill'i yüklenip gölge kök şablonunu gerçek bir gölge köküne dönüştürene kadar Bildirimli Gölge DOM desteği olmayan tarayıcıların yanlış veya stilsiz içerik göstermesine neden olur.

Neyse ki bu sorun, FOUC stil kuralı değiştirilerek CSS'de çözülebilir. Bildirimsel Gölge DOM'u 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'yu ö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 eşleşmez. HTML ayrıştırması sırasında <template shadowrootmode> alt öğesi kaldırıldığından kural, Bildirimli Gölge DOM'u destekleyen tarayıcılarda yok sayılır.

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

Bildirim Temelli 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 özelliği kullanmaktadır. Daha 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 geniş kapsamlı destek sunmamaktadır. Tarayıcı desteği, HTMLTemplateElement prototipinde shadowRootMode özelliğinin olup olmadığı kontrol edilerek algılanabilir:

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

Polyester Lifi

Bildirimli Gölge DOM için basitleştirilmiş bir çoklu dolgu oluşturmak nispeten basittir. Çünkü bir çoklu dolgunun, tarayıcı uygulamasının kendisiyle ilgili olduğu zamanlama semantiğini veya yalnızca ayrıştırıcıya özgü özellikleri mükemmel bir şekilde kopyalamasına gerek yoktur. Bildirimli Gölge DOM'u çoklu doldurma amacıyla tüm <template shadowrootmode> öğelerini bulmak için DOM'u tarayıp bunları üst öğelerinde ekli Gölge Kökleri'ne dönüştürebiliriz. Bu işlem, belge hazır olduğunda yapılabilir veya Custom Element 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