Bildirim Temelli Gölge DOM

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 ürününü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 hatırlatmak isteriz.

Tarayıcı Desteği

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

Kaynak

Gölge DOM, HTML şablonları ve Özel Öğeler ile yuvarlanan üç Web Bileşeni 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ı 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 performans elde etmenin bir yolu olarak temel, JavaScript olmadan bir deneyim sunmayı seçer.

Geçmişte, Gölge DOM'yi sunucu tarafından oluşturulan HTML'de ifade etmenin yerleşik bir yolu olmadığından Sunucu Tarafı Oluşturma ile birlikte kullanmak zordu. 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 en güçlüdür. Ö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ı this.shadowRoot 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 öğ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. Bildirim temelli kökler, zorunlu bir değişim oluşturulana kadar korunduğundan, Bildirim Temelli Gölge DOM için 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. Ayrıca, 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 için de 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, her bir gölge kökünün içeriği nadiren aynı olduğundan, Bildirim Temelli Gölge DOM'un kullanıldığı çoğu durumda bu sorun 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 varyasyonlar). 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 işlemi yalnızca öğeler aynı olduğunda çalışır. Son olarak, tekrar eden 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 Bildirim Temelli Gölge Kökleri, belirli bir öğenin gölge kökünü oluşturmak üzere ö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ına olanak tanıyor.

Akış yapmak havalı bir şeydir

Bildirim Temelli Gölge Kökleri doğrudan üst öğeleriyle ilişkilendirmek, bunları yükseltme ve söz konusu öğ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> 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ış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ırma sırasında mevcut olan 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, 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ı, 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 doldururken bunlara başvurmanın mümkün olmamasıdır.

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

Henüz Declarative Shadow DOM'yi desteklemeyen tarayıcılardaki olası sorunlardan biri, "stilsiz içerik flash'ı" kullanmaktan kaçınmaktır. (FOUC) içerir. Burada, henüz yükseltilmemiş özel öğeler için ham içerikler gösterilir. Bildirim Temelli Gölge DOM'den önce, FOUC'tan 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>

Bildirim temelli 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, Bildirimsel Gölge DOM polyfill'i yüklenip gölge kök şablonunu 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 ve 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>

Henüz tanımlanmamış Özel Öğeyi gizlemek yerine, revize edilmiş "FOUC" kuralı, bir <template shadowrootmode> öğesini takip ettiklerinde 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

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 özellik kullanmaktadır. 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 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

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