IlmHamroh
JavaScript Full-stack/13-qism. Brauzer API'lari, performans va xavfsizlik24/32-dars22 daqiqa
Mundarija (41)

Web Components'ga kirish: custom element va Declarative Shadow DOM

Qisqacha: Web Components — brauzerning o'zida bor, freymvorksiz o'z HTML tegingizni yaratish texnologiyasi. U uch qismdan iborat: custom element (nomida defis bor o'z teg, masalan <dish-card>), Shadow DOM (tegning tashqi sahifadan yopiq ichki dunyosi — stil va qidiruv u yerga kirmaydi) va template/slot. Declarative Shadow DOM shu yopiq dunyoni JavaScript'siz, HTML'ning o'zida yozadi: <template shadowrootmode="open">.

Bu darsda

  • Custom element nomi qachon to'g'ri, qachon xato ekanini bilasiz va :defined holatini tushuntira olasiz.
  • Shadow DOM nima uchun kerakligini, host, shadow root va light DOM nimaligini ko'rsata olasiz.
  • Declarative Shadow DOM'ni shadowrootmode, shadowrootdelegatesfocus, shadowrootclonable va shadowrootserializable atributlari bilan yoza olasiz.
  • Shadow DOM ichidan chiqqan hodisa tashqarida qanday ko'rinishini bilasiz.
  • Web Components qachon to'g'ri tanlov, qachon emasligini ayta olasiz.

Oldin bilishingiz kerak: template va slot, Kontent modeli, Holat pseudo-klasslari, Hodisa tarqalishi: capture va bubble.

1. Nega bu kerak?

O'tgan uch darsda brauzerning ilovaga oid imkoniyatlarini ko'rdik: Service Worker, kesh va PWA. Endi boshqa savol: sahifaning qismlarini qanday qilib bir marta yozib, ko'p joyda ishlatish mumkin?

Jasur aka «Bahor» oshxonasining menyu kartasini chiroyli qildirgan: rasm, nom, narx, «Savatga» tugmasi. Endi hamkor kafe ham shu kartani o'z saytiga qo'ymoqchi. Stajyor Sardor kartaning HTML va CSS kodini yuborib berdi.

Ertasi kuni hamkor qo'ng'iroq qildi: "Kartangiz buzilib chiqyapti". Sardor qarab ko'rdi. Hamkor saytida h3 { color: red; text-transform: uppercase; } qoidasi bor ekan. Taom nomi qizil va katta harflarga aylangan. Teskarisi ham bo'ldi: kartaning .price klassi hamkorning o'z .price klassini buzib yuborgan.

Muammo — CSS global. Kaskad darsida ko'rganimizdek, sahifadagi har bir qoida har bir elementga tegishi mumkin. CSS metodologiyalari va @scope bu muammoni kamaytiradi, lekin to'liq yopmaydi: hamkorning h3 qoidasi baribir kartaga yetib boradi.

Brauzerda buning uchun tayyor yechim bor. Kartani o'z tegiga aylantirish mumkin — <dish-card> ("taom kartasi"). Uning ichki tuzilmasi va stili alohida, yopiq "xonada" yashaydi. Tashqi h3 qoidasi u yerga kirmaydi, ichki .price esa tashqariga chiqmaydi. Bu texnologiyaning nomi — Web Components.

2. Web Components — uch texnologiya

2.1 Uch qism birga

Web Components (veb komponentlar) — bitta API emas, birgalikda ishlatiladigan uchta brauzer texnologiyasi:

Qism Nima qiladi Qayerda o'rganamiz
Custom element O'z tegingizni brauzerga tanitadi shu dars + Web Components
Shadow DOM Tegning ichini tashqi sahifadan yopadi shu dars + stillash
template va slot Qolip va to'ldiriladigan joylar template va slot

Bularning hech biri kutubxona emas. Hammasi brauzerda allaqachon bor, hech narsa o'rnatish shart emas. Yozgan tegingiz oddiy HTML sahifada ham, keyinroq o'rganadigan React loyihasida ham ishlaydi.

Bu dars — HTML tomoni: teg nomi, Shadow DOM tushunchasi va uni HTML'ning o'zida yozish. Tegga xatti-harakat beradigan JavaScript klassi — keyingi darsda.

2.2 Xonadon o'xshatishi

Ko'p qavatli uyni tasavvur qiling. Uyning umumiy yo'lagi bor — hamma ko'radi, hamma yuradi. Har bir xonadonning esa eshigi bor. Ichkarida egasi o'zi xohlagan rangga bo'yagan, o'z mebelini qo'ygan.

Yo'lakdagi farrosh yo'lakni oqqa bo'yasa, xonadonlar ichi oq bo'lib qolmaydi. Xonadon ichidagi qizil devor esa yo'lakka chiqmaydi. Lekin ba'zi narsalar eshikdan o'tadi: yo'lakdagi issiqlik (isitish) xonadonga ham kiradi.

Sahifa — yo'lak. Custom element — xonadon eshigi. Shadow DOM — xonadon ichi. "Issiqlik" esa CSS merosi: rang va shrift ichkariga meros bo'lib o'tadi. Buni pastda o'z ko'zingiz bilan ko'rasiz.

3. Custom element: o'z tegingiz

3.1 Nom qoidalari

Custom element (maxsus element) — siz o'ylab topgan va brauzerga ro'yxatdan o'tkazgan teg. Uning nomiga qat'iy qoidalar bor:

  • Nomda kamida bitta defis (-) bo'lishi shart: dish-card, bahor-cart, star-rating.
  • Nom kichik lotin harfi bilan boshlanadi. Katta harf ishlatilmaydi.
  • Ba'zi defisli nomlar band — ular eski SVG va MathML teglari: font-face, color-profile, annotation-xml va yana bir nechtasi.

Nomlarni inglizcha tanlaymiz — Toza kod: nomlash darsidan beri hamma nomlar shunday. dish-card — "taom kartasi", bahor-cart — "«Bahor» savati", star-rating — "yulduzli baho". Keyingi darsda har tegga JavaScript klassi yoziladi va uning nomi teg bilan bir xil bo'ladi: dish-card → DishCard.

Nega defis? HTML standarti kelajakda yangi teglar qo'shadi, lekin ularning nomida hech qachon defis bo'lmaydi. Siz defisli nom tanlasangiz, ertaga brauzer xuddi shu nomli standart teg chiqarib, saytingizni buzib qo'ymaydi.

Ro'yxatdan o'tkazish JavaScript'da bitta qator — customElements.define(nom, klass). Klassning o'zini keyingi darsda yozamiz. Hozir faqat nom qoidasini tekshirib ko'raylik. Chrome 154 da noto'g'ri nom shunday xato beradi:

text
Uncaught SyntaxError: Failed to execute 'define' on 'CustomElementRegistry': "dishcard" is not a valid custom element name

Tarjimasi: "CustomElementRegistry ning define metodini bajarib bo'lmadi: dishcard — yaroqli custom element nomi emas". CustomElementRegistry — brauzerdagi ro'yxat, unda qaysi nomga qaysi klass berilgani yoziladi. Dish-card (katta harf) va font-face (band nom) uchun ham aynan shu xato chiqadi.

Defis yo'q nom bilan sahifada teg yozsa bo'ladi, lekin brauzer uni noma'lum element deb oladi. Uni hech qachon "jonlantirib" bo'lmaydi.

Tekshirib ko'ring: Qaysi nomlar custom element uchun yaroqli? menu, menu-list, Menu-List, bahor-2026, font-face.

Javob

Yaroqli — menu-list va bahor-2026 (kichik harf bilan boshlanadi, defis bor; raqam boshqa joyda bo'lishi mumkin). menu — defis yo'q. Menu-List — katta harf bor. font-face — band nom (eski SVG tegi).

3.2 Ro'yxatdan o'tmagan teg ham ishlaydi

Qiziq fakt: <dish-card> ni HTML'ga hali hech qanday JavaScript'siz yozsangiz ham, brauzer xato bermaydi. U tegni oddiy inline element kabi ko'rsatadi — xuddi span kabi. Ichidagi matn ekranda chiqadi.

Brauzer ichida esa farq bor. Chrome'da tekshirdik:

  • document.createElement("dishcard") → HTMLUnknownElement — "noma'lum element", u abadiy shunday qoladi.
  • document.createElement("dish-card") → HTMLElement — "hali ro'yxatdan o'tmagan, lekin bo'lishi mumkin bo'lgan element".

Defisli teg kutib turadi. Skript keyinroq customElements.define("dish-card", ...) ni chaqirsa, sahifadagi hamma <dish-card> lar shu zahoti "jonlanadi". Bu jarayon upgrade (yangilash) deyiladi. Skript sahifa oxirida yoki defer bilan yuklansa ham, teglar o'z vaqtida yangilanadi.

3.3 :defined — ro'yxatdan o'tganmi?

Holat pseudo-klasslari darsida :defined ni tanigan edik. Endi uni amalda ko'ramiz. Ro'yxatdan o'tmagan custom element :defined emas, ro'yxatdan o'tgani — :defined. Oddiy HTML teglari (p, div) doim :defined.

Quyidagi oynada karta avval xira. Tugmani bosing:

html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  dish-card { display: block; padding: 0.75rem;
    border: 1px solid #c8d6c8; border-radius: 0.5rem; }
  dish-card:not(:defined) { opacity: 0.35; }
  button { font: inherit; padding: 0.4rem 0.8rem;
    margin-top: 0.5rem; }
</style>
<dish-card>Osh — 35 000 so'm</dish-card>
<button id="define" type="button">Ro'yxatdan o'tkazish</button>
<script>
  const card = document.querySelector("dish-card");
  console.log("defined:", card.matches(":defined"));

  document.getElementById("define").addEventListener("click", () => {
    if (customElements.get("dish-card")) return;
    customElements.define("dish-card", class extends HTMLElement {});
    console.log("defined:", card.matches(":defined"));
  });
</script>

Konsolda:

text
defined: false

Tugmani bosganingizda karta to'liq rangga kiradi va konsolga defined: true chiqadi. Biz buni bosib sinadik — aynan shunday.

Qatorma-qator:

  • dish-card:not(:defined) — "hali ro'yxatdan o'tmagan karta". Unga xiralik berdik.
  • class extends HTMLElement {} — bo'sh klass. U hech narsa qilmaydi, lekin nomni ro'yxatga yozish uchun yetarli. extends va HTMLElement haqida — keyingi darsda.
  • customElements.get("dish-card") — shu nom allaqachon ro'yxatda bo'lsa, klassni qaytaradi. Ikkinchi marta define qilish xato beradi, shuning uchun avval tekshirdik.

Real saytda bu holat bilan FOUC (flash of unstyled content — "bezaksiz kontent miltillashi") ning oldini olishadi. Skript yuklanguncha chala tegni yashirib turishadi: dish-card:not(:defined) { visibility: hidden; }.

Diqqat: visibility: hidden bilan ehtiyot bo'ling. Skript yuklanmasa (xato yoki sekin internet), kontent butunlay ko'rinmay qoladi. Shuning uchun quyidagi Declarative Shadow DOM ko'pincha yaxshiroq yo'l: u JavaScript'ni kutmaydi.

4. Shadow DOM: yopiq ichki dunyo

4.1 Atamalar

Shadow DOM (soya DOM) — elementga biriktirilgan, asosiy sahifadan ajratilgan alohida DOM daraxti. Uni xonadon o'xshatishi bilan nomlab olamiz. Asosiy so'z uchta:

  • Host (xo'jayin element) — shadow daraxt biriktirilgan element, ya'ni xonadonning eshigi. Bizda — <dish-card>.
  • Shadow root (soya ildizi) — xonadonning ichi boshlanadigan joy. DevTools'da u #shadow-root deb ko'rinadi. Uning ichidagi hamma elementlar birgalikda shadow tree (soya daraxti) deyiladi.
  • Light DOM (yorug' DOM) — host'ning oddiy farzandlari, ya'ni sahifa muallifi teg ichiga yozgan kontent. Ular yo'lakda turadi, lekin slotlar orqali ichkarida ko'rinadi.

Ikki dunyo orasidagi devor va eshik — shadow boundary (soya chegarasi). Keyingi bo'limlarda "chegara" deganimiz — shu.

flowchart TD
  D["document"] --> B["body"]
  B --> H["dish-card<br/>(host)"]
  H -. "shadow root" .-> S["#shadow-root"]
  S --> A["article"]
  A --> T["h3 → slot name"]
  A --> SL["slot"]
  H --> L1["span slot=name<br/>(light DOM)"]
  H --> L2["p<br/>(light DOM)"]

Nimaga qarang: host'dan ikki yo'l chiqyapti. Uzluksiz chiziqlar — oddiy farzandlar (light DOM), ular asosiy sahifaga tegishli. Uzuq chiziq — shadow root. Uning ichidagi article, h3 va slot sahifaning oddiy daraxtida yo'q.

4.2 Chegara nimani to'xtatadi

Shadow DOM uch narsani ajratadi. Buni Chrome'da birma-bir tekshirdik:

  1. Tashqi CSS ichkariga kirmaydi. Sahifadagi h3 { color: red } shadow ichidagi h3 ga tegmaydi.
  2. Ichki CSS tashqariga chiqmaydi. Shadow ichidagi h3 { color: green } sahifaning boshqa h3 lariga ta'sir qilmaydi.
  3. Qidiruv ichkariga kirmaydi. document.querySelector("h3") shadow ichidagi h3 ni topmaydi. Shadow ichidagi id lar ham sahifaning id lari bilan to'qnashmaydi.

Bu xususiyat inkapsulyatsiya (encapsulation) deyiladi — Private # maydonlar darsidagi g'oyaning HTML va CSS'dagi ko'rinishi: ichki tuzilma tashqaridan yopiq.

Lekin bitta narsa o'tadi — meros. color, font-family, line-height kabi meros bo'ladigan xususiyatlar host'dan ichkariga o'tadi. Xonadon o'xshatishidagi "issiqlik" — shu.

4.3 O'z ko'zingiz bilan: hamkor sahifasi

Quyidagi oynada hamkor kafening "buzuvchi" stili bor: hamma h3 qizil va katta harfli, .price esa ko'k. Pastda ikki karta: biri oddiy HTML, ikkinchisi Shadow DOM bilan:

html
<style>
  body { font: 1rem/1.5 Georgia, serif; color: #333; margin: 1rem; }
  /* hamkor saytining global stillari */
  h3 { color: #c0392b; text-transform: uppercase; }
  .price { color: #1f4e9c; font-size: 2rem; }
</style>
<p>Oddiy karta:</p>
<div class="card">
  <h3>Osh</h3>
  <p class="price">35 000 so'm</p>
</div>
<p>Shadow DOM bilan:</p>
<dish-card>
  <template shadowrootmode="open">
    <style>
      h3 { color: #2e7d32; margin: 0; }
      .price { font-weight: 700; margin: 0; }
    </style>
    <h3>Osh</h3>
    <p class="price">35 000 so'm</p>
  </template>
</dish-card>
<script>
  const pageCount = document.querySelectorAll("h3").length;
  console.log("Sahifadagi h3:", pageCount);
  const root = document.querySelector("dish-card").shadowRoot;
  const innerCount = root.querySelectorAll("h3").length;
  console.log("Shadow ichidagi h3:", innerCount);
</script>

Konsolda:

text
Sahifadagi h3: 1
Shadow ichidagi h3: 1

Nimaga qarang:

  • Oddiy kartada sarlavha qizil va katta harfli, narx ko'k va katta. Hamkor stili uni "egallab oldi".
  • Shadow kartada sarlavha yashil, oddiy harfli. Narx esa qalin, lekin ko'k ham, katta ham emas. Tashqi h3 va .price qoidalari ichkariga kirmadi.
  • Shrift esa ikkala kartada bir xil — Georgia. font-family body dan meros bo'lib, chegaradan o'tdi.
  • document.querySelectorAll("h3") faqat bitta h3 ni topdi — oddiy kartadagini. Shadow ichidagini topish uchun avval shadowRoot ga kirish kerak.

Tekshirib ko'ring: Hamkor sahifasiga dish-card { color: purple; } qoidasini qo'shsa, shadow kartadagi narx qanday rangda bo'ladi? Sarlavha-chi?

Javob

Narx — binafsha. .price ning ichki stilida color yo'q, u rangni meros oladi: host'ning rangi — binafsha, meros chegaradan o'tadi. Sarlavha esa yashil qoladi: ichki h3 { color: #2e7d32 } meros qiymatdan kuchli. Meros faqat element o'zi qiymat olmaganda ishlaydi (Meros darsidagi qoida).

5. Declarative Shadow DOM

5.1 Sintaksis

template va slot darsida bitta atributni qisqa ko'rgan edik. Endi to'liq:

html
<dish-card>
  <template shadowrootmode="open">
    <!-- shadow daraxt: stil va tuzilma -->
  </template>
  <!-- light DOM: sahifa muallifining kontenti -->
</dish-card>

Declarative Shadow DOM (deklarativ Shadow DOM, DSD) — Shadow DOM'ni JavaScript'siz, HTML'ning o'zida yozish usuli. "Deklarativ" — "nima kerakligini aytaman, qanday qilishni brauzer biladi" degani.

Brauzer sahifani o'qiyotganda (parse qilayotganda) shadowrootmode atributli template ni ko'radi. U qolipni saqlab qo'ymaydi, darhol ota elementga shadow root yasaydi va qolip ichini o'sha yerga ko'chiradi. template tegining o'zi DOM'da qolmaydi.

Qo'llanishi: Chrome 111 (2023), Safari 16.4 (2023), Firefox 123 (2024-yil fevral). 2024-yil fevralidan Baseline, 2026-yil avgustidan esa widely available (web-features 3.40).

5.2 Nega JavaScript'siz yozish kerak?

Shadow DOM 2016-yildan beri bor, lekin faqat JavaScript bilan yaratilardi. Bu ikki muammo tug'dirardi:

  1. Kutish. JavaScript yuklanib, bajarilguncha kartaning ichi yo'q. Sekin internetda foydalanuvchi bo'sh joy yoki "sakrayotgan" sahifa ko'radi.
  2. Server tayyorlay olmaydi. Ko'p saytlarda HTML'ni server tayyorlab yuboradi — bu SSR (server-side rendering, "serverda chizish"). Server oddiy HTML matn yuboradi, unda JavaScript ishlamaydi. DSD'gacha Shadow DOM'ni HTML matnda ifodalashning yo'li yo'q edi.

DSD ikkalasini hal qiladi. HTML kelishi bilan karta to'liq tayyor, stili bilan. JavaScript keyin yetib kelib, unga faqat xatti-harakat qo'shadi: tugma bosilishi, hisob. Bu — progressive enhancement g'oyasi: avval ishlaydigan asos, keyin qo'shimcha imkoniyat.

5.3 open va closed

shadowrootmode ning ikki qiymati bor:

  • open — ochiq. Tashqi JavaScript element.shadowRoot orqali ichkariga kira oladi. DevTools'da ham, testlarda ham qulay.
  • closed — yopiq. element.shadowRoot null qaytaradi. Tashqi skript ichkariga oddiy yo'l bilan kira olmaydi.

Quyidagi oynada ikki teg: <open-card> ("ochiq karta") va <closed-card> ("yopiq karta"):

html
<open-card>
  <template shadowrootmode="open"><p>Ochiq</p></template>
</open-card>
<closed-card>
  <template shadowrootmode="closed"><p>Yopiq</p></template>
</closed-card>
<script>
  const open = document.querySelector("open-card");
  const closed = document.querySelector("closed-card");
  console.log("ochiq:", open.shadowRoot.mode);
  console.log("yopiq:", closed.shadowRoot);
</script>

Konsolda:

text
ochiq: open
yopiq: null

Ikkala karta ham ekranda ko'rinadi — closed ko'rinishga ta'sir qilmaydi. U faqat JavaScript'dan kirishni yopadi.

Bo'sh joyni to'ldiring: shadowrootmode="closed" bilan yozilgan kartaning shadowRoot xususiyati qaytaradi.

Qaysi birini tanlash kerak? Deyarli har doim open. Sabab keyingi bo'limda.

5.4 closed — xavfsizlik emas

closed ni ko'pincha "himoya" deb o'ylashadi. Bu xato. Sahifadagi har qanday skript — sizniki ham, reklama skripti ham — bitta sahifada, bitta huquq bilan ishlaydi. Yopiq rejimni aylanib o'tishning yo'llari bor: masalan, skript attachShadow metodini komponent yuklanishidan oldin almashtirib qo'yishi mumkin. DevTools'da esa yopiq daraxt ham bemalol ko'rinadi.

Shuning uchun closed maxfiy ma'lumotni (token, parol) yashirish uchun yaramaydi. U faqat "ichkariga tegmang, bu ichki tuzilma" degan kelishuv. Lit kabi mashhur kutubxonalar open ni standart qilib olgan. Sahifa xavfsizligi — Brauzer xavfsizlik modeli darsida, shu qismning oxirida.

5.5 Qolgan uchta atribut

template ga yana uchta mantiqiy (qiymatsiz) atribut qo'shish mumkin:

Atribut Nima qiladi
shadowrootdelegatesfocus Host'ga fokus berilsa, ichidagi birinchi fokuslanadigan elementga o'tkazadi
shadowrootclonable cloneNode(true) shadow daraxtni ham nusxalaydi
shadowrootserializable getHTML() shadow daraxtni ham HTML matnga aylantira oladi

Fokusni uzatish. «Bahor» qidiruv maydoni o'z tegi bo'lsin: <search-field> ("qidiruv maydoni"), ichida yozuv va input. Bobur faqat klaviatura bilan ishlaydi. Unga sahifadagi "Qidiruvga o'tish" havolasi kerak — havola host'ga fokus beradi. Atributsiz host'ning o'zi fokus ololmaydi, hech narsa bo'lmaydi. shadowrootdelegatesfocus bilan fokus darhol ichidagi input ga o'tadi:

html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  search-field:focus-within { outline: 2px solid #2e7d32;
    outline-offset: 4px; display: inline-block; }
  button { font: inherit; padding: 0.4rem 0.8rem; margin-top: 1rem; }
</style>
<search-field id="search">
  <template shadowrootmode="open" shadowrootdelegatesfocus>
    <label>Taom qidirish: <input name="q"></label>
  </template>
</search-field>
<br>
<button id="go" type="button">Qidiruvga o'tish</button>
<script>
  const search = document.getElementById("search");
  document.getElementById("go").addEventListener("click", () => {
    search.focus();
    console.log("Fokusda:", document.activeElement.localName);
    const inner = search.shadowRoot.activeElement;
    console.log("Ichkarida:", inner.localName);
  });
</script>

Tugmani bosing: kursor maydonga tushadi va ramka paydo bo'ladi. Konsolga ikki qator chiqadi — sinovimizda aynan shunday chiqdi:

text
Fokusda: search-field
Ichkarida: input

E'tibor bering: document.activeElement — host, ichidagi input emas. Tashqi dunyo ichkariga qaramaydi, u faqat "fokus shu tegda" deb biladi. Haqiqiy fokuslangan element — shadowRoot.activeElement da. Shu sabab host'ga :focus va :focus-within ham ishlaydi.

Nusxa va matn. Chrome'da tekshirdik: atributsiz cloneNode(true) qilingan host'ning shadowRoot i null — shadow daraxt nusxalanmaydi. getHTML() esa faqat { serializableShadowRoots: true } opsiyasi bilan va faqat shadowrootserializable belgilangan daraxtlarni matnga aylantiradi. Bu ikki atribut sahifani nusxalaydigan yoki saqlaydigan vositalar uchun kerak — oddiy saytda kam ishlatiladi.

5.6 DSD faqat HTML tahlilida ishlaydi

Muhim tuzoq. DSD faqat brauzer HTML'ni o'qiyotganda ishlaydi. innerHTML orqali qo'shsangiz, template oddiy, uxlab yotgan qolip bo'lib qoladi. Chrome 154 konsolda shunday ogohlantiradi:

text
Found declarative shadowrootmode attribute on a template, but declarative Shadow DOM is not being parsed. Use setHTMLUnsafe() or parseHTMLUnsafe() instead.

Tarjimasi: "Qolipda deklarativ shadowrootmode atributi topildi, lekin deklarativ Shadow DOM tahlil qilinmayapti. O'rniga setHTMLUnsafe() yoki parseHTMLUnsafe() ni ishlating".

setHTMLUnsafe(matn) — innerHTML ning DSD'ni tushunadigan varianti. Nomidagi "Unsafe" ("xavfli") so'zi ogohlantirish: u ham innerHTML kabi matnni HTML qilib tahlil qiladi. Foydalanuvchi yozgan matnni unga berish — XSS eshigini ochish. Unga faqat o'zingiz yozgan, ishonchli HTML beriladi.

js
const box = document.querySelector("#menu");
const html = `<dish-card>
  <template shadowrootmode="open"><b>Osh</b></template>
</dish-card>`;

box.innerHTML = html;
console.log(box.firstElementChild.shadowRoot); // null

box.setHTMLUnsafe(html);
console.log(box.firstElementChild.shadowRoot.mode); // open

Tekshirib ko'ring: Server HTML'ni yuboradi, unda <template shadowrootmode="open"> bor. Sahifa JavaScript'i o'chirilgan brauzerda ochildi. Karta stili bilan ko'rinadimi?

Javob

Ha. DSD — HTML tahlilining bir qismi, uning uchun JavaScript kerak emas. Shadow root, uning stili va slotlar JavaScript'siz ishlaydi. Faqat xatti-harakat (tugma bosilganda nimadir qilish) bo'lmaydi — u keyingi darsda yoziladigan klassda.

6. Hodisalar chegaradan o'tganda

6.1 Retargeting

Hodisa tarqalishi darsida composedPath() ni ko'rib, "composed so'zi Shadow DOM bilan bog'liq" degan edik. Endi sababini ko'ramiz.

Shadow ichidagi tugma bosildi. Hodisa yuqoriga ko'tariladi (bubble), chegaradan o'tib, document ga yetadi. Lekin tashqaridagi tinglovchi event.target da ichki tugmani ko'rmaydi. U host'ni ko'radi. Brauzer hodisaning nishonini chegarada "almashtiradi". Bu retargeting (nishonni qayta belgilash) deyiladi.

html
<dish-card>
  <template shadowrootmode="open">
    <button type="button">Savatga</button>
  </template>
</dish-card>
<script>
  document.addEventListener("click", (event) => {
    const path = event.composedPath()
      .map((node) => node.localName || node.nodeName || "window");
    console.log("target:", event.target.localName);
    console.log("yo'l:", path.join(" > "));
  });
</script>

«Savatga» tugmasini bosing. Biz sinovda bosganimizda konsolga shu chiqdi:

text
target: dish-card
yo'l: button > #document-fragment > dish-card > body > html > #document > window
  • target — dish-card. Tashqi kod ichki tuzilmani bilmasligi kerak — inkapsulyatsiya hodisalarda ham ishlaydi.
  • composedPath() esa to'liq yo'lni beradi: button dan boshlab, shadow root (#document-fragment), host va oxirigacha. Eng oxirgisi window — u DOM tuguni emas, localName ham, nodeName ham yo'q. Shuning uchun uni || "window" bilan o'zimiz nomladik.

6.2 composed — chegaradan o'tadimi?

click, input, keydown kabi foydalanuvchi hodisalari chegaradan o'tadi — ular composed. O'zingiz yaratgan CustomEvent esa standart holatda o'tmaydi: u shadow root'da to'xtaydi. Chegaradan chiqarish uchun composed: true opsiyasi kerak. Buni keyingi darsda, <dish-card> «Savatga» bosilganda tashqariga xabar yuborganda yozamiz.

7. Foydalanish qulayligi

Shadow DOM bilan ishlaganda ikkita narsani yodda tuting. Malika ekran o'quvchi (NVDA) bilan ishlaydi.

Slotdagi kontent joyida o'qiladi. template va slot darsida aytgan edik: ekran o'quvchi light DOM kontentini slot turgan joyda o'qiydi. Malika ko'z bilan ko'radigan tartibni eshitadi. Bu yaxshi.

id havolalari chegaradan o'tmaydi. Sahifadagi <label for="name"> shadow ichidagi <input id="name"> ni topmaydi. Chrome'da tekshirdik: bunday label ning control xususiyati null. aria-labelledby va aria-describedby ham chegaradan o'tmaydi. Natijada Malika nomsiz maydonni eshitadi: "tahrirlash maydoni" — nima yozish kerakligi noma'lum.

Yechim — label va uning maydonini bir tomonda saqlash. Yuqoridagi qidiruv misolida label ham, input ham shadow ichida — shuning uchun ishlaydi.

8. Qachon ishlatiladi?

8.1 Yaxshi tanlov

  • Dizayn tizimlari. Katta kompaniyada o'nlab sayt bor, ba'zisi bir texnologiyada, ba'zisi boshqasida. Tugma, karta, dialog bir xil bo'lishi kerak. Web Components har qanday sahifada ishlaydi. Google'ning Material Web, Adobe'ning Spectrum Web Components, Web Awesome (avvalgi nomi Shoelace) — shunday kutubxonalar.
  • Boshqa saytga qo'yiladigan vidjetlar. Chat oynasi, to'lov tugmasi, xarita — hamkor saytining CSS'idan himoyalangan bo'lishi kerak. Bizning hamkor kafe muammosi — aynan shu.
  • Kichik, mustaqil qismlar. GitHub sahifalaridagi vaqt ko'rsatkichi <relative-time> ("3 soat oldin") — custom element.

Ko'p jamoalar Web Components'ni qo'lda emas, Lit kutubxonasi bilan yozadi. Lit — Google jamoasi yaratgan kichik kutubxona. U Tagged template literals darsida ko'rgan html va css teglari ustiga qurilgan. Hozir bilish shart emas: avval qo'lda yozishni o'rganamiz, shunda Lit ichida nima bo'layotganini tushunasiz.

8.2 Har doim ham emas

  • Butun ilova (ko'p sahifali, murakkab holatli) uchun ko'pincha React kabi freymvork qulayroq. React'ni 17-qismdan o'rganamiz.
  • Oddiy, bitta saytdagi qism uchun Shadow DOM ortiqcha bo'lishi mumkin. Global stil kerak bo'lsa (sayt bo'ylab bir xil shrift va ranglar), yopiq dunyo xalaqit beradi.
Vaziyat Tanlov
Ko'p saytda bir xil komponent Web Components
Begona saytga qo'yiladigan vidjet Web Components + Shadow DOM
Bitta katta ilova Freymvork (React — 17-qism)
Kichik sayt, bitta CSS fayl Oddiy HTML + CSS

9. Ko'p uchraydigan xatolar

9.1 Nomda defis yo'q

<dishcard> — ro'yxatdan o'tkazib bo'lmaydi, define is not a valid custom element name xatosini beradi. Tuzatish: dish-card. Loyiha prefiksi qulay: bahor-card, bahor-cart.

9.2 DSD'ni innerHTML bilan qo'shish

Shadow root yaratilmaydi, karta ichi bo'sh ko'rinadi, konsolda Found declarative shadowrootmode attribute on a template… ogohlantirishi chiqadi. Tuzatish: server yuborgan HTML'da yozing yoki ishonchli matn uchun setHTMLUnsafe().

9.3 Bitta host'ga ikki template

Element ichida ikkita shadowrootmode li template bo'lsa, faqat birinchisi shadow root bo'ladi. Ikkinchisi oddiy qolip bo'lib qoladi, Chrome konsolda ogohlantiradi: A second declarative shadow root cannot be created on a host. ("Host'da ikkinchi deklarativ shadow root yaratib bo'lmaydi"). Bir element — bitta shadow root.

9.4 Hamma elementga Shadow DOM berish mumkin deb o'ylash

Shadow root faqat custom element'larga va ba'zi oddiy teglarga biriktiriladi: div, span, p, article, section, header, footer, main, nav, aside, blockquote, body, h1–h6. input, a, img, button — yo'q. Chrome'da tekshirdik, input uchun: This element does not support attachShadow ("bu element attachShadow ni qo'llamaydi"). DSD'da esa sahifa to'xtamaydi: konsolga shu matn ogohlantirish bo'lib chiqadi, template esa oddiy qolip bo'lib qoladi.

9.5 closed ni himoya deb hisoblash

"Yopiq — demak, hech kim ko'rmaydi" — yo'q. «closed — xavfsizlik emas» bo'limiga qarang.

10. Mashqlar

1-mashq (oson): Nomlarni saralang

Har bir teg nomi uchun ayting: custom element bo'la oladimi? Bo'lmasa, nega?

bahor-menu, menu, Bahor-Menu, bahor-menu-2, color-profile, b-.

Yechim
  • bahor-menu — ha.
  • menu — yo'q, defis yo'q (HTMLUnknownElement bo'lib qoladi).
  • Bahor-Menu — yo'q, katta harf bor.
  • bahor-menu-2 — ha, defis ko'p bo'lishi mumkin, raqam ham.
  • color-profile — yo'q, band nom (eski SVG tegi).
  • b- — ha. G'alati ko'rinadi, lekin qoidaga mos: kichik harf bilan boshlanadi, defis bor. Real loyihada bunday nom tushunarsiz — tavsiya etilmaydi.

2-mashq (o'rta): Ish vaqti kartasi

«Bahor» ish vaqti uchun <bahor-hours> tegini DSD bilan yozing ("hours" — "ish soatlari"). Talablar:

  • Shadow ichida: h2 (nomli slot title, zaxirasi "Ish vaqti") va p (nomli slot time).
  • Ichki stil: h2 yashil (#2e7d32), p qalin.
  • Sahifada "buzuvchi" stil bo'lsin: h2 { color: red; }. Kartaning sarlavhasi baribir yashil qolsin.

Ishora: template va slot darsidagi nomli slotlar; stil template ichidagi <style> da.

Yechim
html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  h2 { color: red; }
</style>
<h2>Sahifa sarlavhasi</h2>
<bahor-hours>
  <template shadowrootmode="open">
    <style>
      h2 { color: #2e7d32; margin: 0 0 0.25rem; }
      p { font-weight: 700; margin: 0; }
    </style>
    <h2><slot name="title">Ish vaqti</slot></h2>
    <p><slot name="time"></slot></p>
  </template>
  <span slot="time">Har kuni 07:00 – 23:00</span>
</bahor-hours>

Sahifa sarlavhasi qizil, kartaniki yashil. Sarlavha slotiga hech narsa berilmadi — zaxira "Ish vaqti" ko'rinadi. Vaqt esa qalin: span light DOM'da, lekin u ichki p ichiga proyeksiya qilinadi va font-weight ni undan meros oladi.

3-mashq (qiyin): Nima nusxalanadi?

Ikki karta yarating: <a-card> (shadowrootmode="open" faqat) va <b-card> (shadowrootmode="open", shadowrootclonable, shadowrootserializable). Ichida — Osh matni. Har biri uchun konsolga chiqaring:

  1. cloneNode(true) nusxasining shadowRoot i bormi (true/false)?
  2. getHTML({ serializableShadowRoots: true }) natijasi.

Ishora: Boolean(qiymat) — null ni false ga aylantiradi.

Yechim
html
<a-card><template shadowrootmode="open">Osh</template></a-card>
<b-card><template shadowrootmode="open" shadowrootclonable
  shadowrootserializable>Osh</template></b-card>
<script>
  for (const name of ["a-card", "b-card"]) {
    const card = document.querySelector(name);
    const copy = card.cloneNode(true);
    console.log(name, "nusxada shadow:", Boolean(copy.shadowRoot));
    const html = card.getHTML({ serializableShadowRoots: true });
    console.log(name, "getHTML:", JSON.stringify(html));
  }
</script>

Konsolda:

text
a-card nusxada shadow: false
a-card getHTML: ""
b-card nusxada shadow: true
b-card getHTML: "<template shadowrootmode=\"open\" shadowrootserializable=\"\" shadowrootclonable=\"\">Osh</template>"

Birinchi karta uchun getHTML bo'sh satr qaytardi: light DOM'da hech narsa yo'q, shadow daraxt esa serializable emas. Ikkinchi karta esa o'zini qaytadan DSD ko'rinishida yozib berdi. Shu matnni serverga saqlab, keyin qayta ochsangiz, karta aynan tiklanadi. Biz JSON.stringify ni satr chegarasini aniq ko'rish uchun ishlatdik — u ichki qo'shtirnoqlarni \" qilib ko'rsatadi.

4-mashq: Amaliy tajriba — DevTools'da Shadow DOM

kurs/mashqlar/13/24-dsd/index.html faylini yarating. Unga 2-mashqdagi <bahor-hours> ni va «open va closed» bo'limidagi ikki kartani qo'ying. Live Server bilan oching, DevTools → Elements panelini oching.

  1. Kartani oching: #shadow-root (open) qatorini toping. Ichidagi h2 ni tanlang — Styles panelida sahifadagi h2 { color: red } qoidasi umuman yo'qligini ko'ring.
  2. closed-card ichida #shadow-root (closed) ko'rinadimi?
  3. Konsolda $0.shadowRoot ni yozing (avval Elements'da kartani tanlang).
Yechim
  1. Styles panelida faqat ichki qoida bor. Tashqi h2 qoidasi bu elementga tegishli emas — DevTools uni ko'rsatmaydi ham.
  2. Ha, DevTools yopiq daraxtni ham ko'rsatadi: #shadow-root (closed). Demak, closed foydalanuvchidan hech narsani yashirmaydi — faqat sahifa skriptlaridan.
  3. $0 — Elements'da tanlangan element. Ochiq karta uchun #shadow-root (open) obyekti chiqadi, yopiq karta uchun null.
bash
git add 13/24-dsd/index.html
git commit -m "13/24: Declarative Shadow DOM bilan ish vaqti kartasi"

11. Real ishda

  • Dizayn tizimlari. Material Web, Spectrum Web Components, Web Awesome — tayyor tugma, dialog, sana tanlagich teglari. Ular React, Vue yoki oddiy HTML loyihada bir xil ishlaydi.
  • Vidjetlar. To'lov, chat, sharh vidjetlari begona saytga Shadow DOM bilan qo'yiladi — sayt CSS'i ularni buzmasligi uchun.
  • SSR va DSD. Lit va boshqa vositalar serverda komponentni DSD ko'rinishida HTML'ga aylantiradi. Foydalanuvchi JavaScript yuklanmasidan oldin tayyor sahifani ko'radi.
  • Intervyu. "Shadow DOM nima va u nimani izolyatsiya qiladi?", "open va closed farqi?", "Custom element nomida nega defis bor?" — frontend intervyularida uchraydi.

Xulosa

  • Web Components — uch texnologiya: custom element, Shadow DOM, template/slot. Hammasi brauzerda bor, kutubxonasiz.
  • Custom element nomi — kichik harf bilan boshlanadi va defis bor. Ro'yxatdan o'tmagan defisli teg kutib turadi, define dan keyin "yangilanadi"; :defined buni ko'rsatadi.
  • Shadow DOM tashqi CSS'ni ichkariga kiritmaydi, ichkisini tashqariga chiqarmaydi, querySelector ichkariga qaramaydi. Meros o'tadi.
  • Declarative Shadow DOM — <template shadowrootmode="open">: shadow root JavaScript'siz, HTML tahlilida yaratiladi. innerHTML bilan ishlamaydi.
  • closed — himoya emas. shadowrootdelegatesfocus — fokusni ichkariga uzatadi.
  • Ichkaridan chiqqan hodisaning target i tashqarida — host (retargeting).

Keyingi dars: Web Components — <dish-card> ga JavaScript klassi bilan "jon" beramiz: hayot sikli callback'lari, atributlar, attachShadow, slotlar va tashqariga hodisa yuborish.

Manbalar

  • HTML Living Standard: "Custom elements", "The template element" (shadowrootmode) — html.spec.whatwg.org
  • DOM Standard: "Shadow tree", "Interface ShadowRoot" — dom.spec.whatwg.org
  • MDN: "Using shadow DOM", <template>: shadowrootmode — developer.mozilla.org
  • web.dev: "Declarative Shadow DOM" — web.dev
  • web-features 3.40: declarative-shadow-dom, shadow-dom, autonomous-custom-elements — github.com/web-platform-dx/web-features
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Web Components'ga kirish: custom element va Declarative Shadow DOM — IlmHamroh