Ders 21 / 23
Kapsamlı Biçem Yaklaşımları
Adların kapsamını gerçekten sınırlayan yaklaşımlar; üretilmiş yerel adlar, bildirim karmasına göre sınıf paylaşımı, belge kapsamı bildirimi, gölge ağaç kapsüllemesi ve yaklaşımların verdiği güvencelerin karşılaştırılması.
İçindekiler
Önceki dersin sonunda bir sorun açıkta kaldı. Adlandırma yöntemi çakışmayı önler ama bunu yalnız sözleşmeyle yapar: iki geliştirici aynı bloğa aynı adı verirse hiçbir araç uyarmaz, kurallar sessizce birbirine karışır.
Bu ders adın kapsamını sözleşmeden çıkarıp mekanizmaya bağlayan yaklaşımları ele alır. Önce “kapsam” sözcüğünün neyi güvenceye aldığını ayırmak gerekir.
Kapsam Neyi Güvenceye Alır
Biçem sızıntısı tek bir olay değildir; üç ayrı olaydır.
Ad çakışması. İki ayrı bileşen aynı sınıf adını kullanır; birinin kuralları diğerinin ögelerine de uyar.
Dışarıdan sızma. Bileşenin dışında yazılmış genel bir kural — p, a, * + * gibi —
bileşenin içindeki ögelere uyar.
Dışarı sızma. Bileşenin içinde yazılmış bir kural, bileşenin dışındaki ögelere uyar. Alt öge birleştiricisiyle yazılmış her kuralın taşıdığı risktir.
Bir yaklaşımın “kapsamlı” sayılması, üçünü birden çözdüğü anlamına gelmez. Aşağıdaki program önce iki üretim yöntemini hesaplıyor, sonra beş yaklaşımın hangi yönü engellediğini karşılaştırıyor.
// kapsam.mjs — yerel kapsamli ad uretimi ve bildirim karmasina gore sinif paylasimi import { createHash } from "node:crypto"; const karma = (metin, uzunluk = 5) => createHash("sha256").update(metin).digest("hex").slice(0, uzunluk); // --- 1. yerel ad + dosya yolu -> benzersiz uretilmis ad --- const uret = (yol, yerelAd) => `${yerelAd}_${karma(`${yol}#${yerelAd}`)}`; const GIRDILER = [ ["bilesenler/olcum-kart.css", "kart"], ["bilesenler/olcum-kart.css", "baslik"], ["bilesenler/yan-bilgi.css", "kart"], // ayni yerel ad, baska dosya ["bilesenler/yan-bilgi.css", "baslik"], ["bilesenler/olcum-kart.css", "kart"], // ayni girdi: ayni ad uretilmeli ]; console.log("--- yerel adlarin uretilmis adlara eslenmesi ---"); const uretilen = new Map(); for (const [yol, ad] of GIRDILER) { const u = uret(yol, ad); console.log(` ${yol.padEnd(30)} .${ad.padEnd(10)} -> .${u}`); uretilen.set(`${yol}#${ad}`, u); } const cakisma = new Set([...uretilen.values()]).size !== uretilen.size; console.log(` ayri girdi sayisi: ${uretilen.size}, uretilen ad sayisi: ${new Set([...uretilen.values()]).size}, cakisma: ${cakisma}`); // --- 2. bildirim kumesinin karmasina gore sinif paylasimi --- const duzenle = (bildirimler) => bildirimler.map((b) => b.replace(/\s+/g, " ").trim()).sort().join(";"); const sinif = (bildirimler) => "b" + karma(duzenle(bildirimler), 7); const BILESENLER = { "olcum-kart": ["padding: 1.5rem", "border: 1px solid #ccd", "border-radius: 4px"], "yan-bilgi-kutu": ["border-radius: 4px", "padding: 1.5rem", "border: 1px solid #ccd"], // ayni kume "kunye": ["padding: 1.5rem", "border: 1px solid #ccd"], "konum-panosu": ["padding: 1.5rem", "border: 1px solid #ccd", "border-radius: 4px"], }; console.log("\n--- bildirim kumesi karmasina gore uretilen sinif ---"); const uretilenKural = new Map(); for (const [ad, bildirimler] of Object.entries(BILESENLER)) { const s = sinif(bildirimler); uretilenKural.set(s, duzenle(bildirimler)); console.log(` ${ad.padEnd(18)} -> .${s}`); } console.log(` bilesen sayisi: ${Object.keys(BILESENLER).length}, uretilen kural sayisi: ${uretilenKural.size}`); for (const [s, govde] of uretilenKural) console.log(` .${s} { ${govde.replaceAll(";", "; ")} }`); // --- 3. kapsamlama yaklasimlarinin verdigi guvenceler --- console.log("\n--- hangi yaklasim neyi guvenceye alir ---"); const YAKLASIMLAR = [ { ad: "yalniz adlandirma sozlesmesi", adCakismasi: false, disaridanSizma: false, disariSizma: false }, { ad: "uretilmis yerel ad", adCakismasi: true, disaridanSizma: false, disariSizma: true }, { ad: "calisma zamani uretimi", adCakismasi: true, disaridanSizma: false, disariSizma: true }, { ad: "belge kapsami bildirimi", adCakismasi: false, disaridanSizma: false, disariSizma: true }, { ad: "golge agac kapsullemesi", adCakismasi: true, disaridanSizma: true, disariSizma: true }, ]; console.log("yaklasim".padEnd(30) + "ad cakismasi".padStart(14) + "disaridan sizma".padStart(18) + "disari sizma".padStart(15)); for (const y of YAKLASIMLAR) { const i = (v) => (v ? "engellenir" : "engellenmez"); console.log(y.ad.padEnd(30) + i(y.adCakismasi).padStart(14) + i(y.disaridanSizma).padStart(18) + i(y.disariSizma).padStart(15)); }
--- yerel adlarin uretilmis adlara eslenmesi ---
bilesenler/olcum-kart.css .kart -> .kart_39622
bilesenler/olcum-kart.css .baslik -> .baslik_fc1e4
bilesenler/yan-bilgi.css .kart -> .kart_28bdd
bilesenler/yan-bilgi.css .baslik -> .baslik_4d665
bilesenler/olcum-kart.css .kart -> .kart_39622
ayri girdi sayisi: 4, uretilen ad sayisi: 4, cakisma: false
--- bildirim kumesi karmasina gore uretilen sinif ---
olcum-kart -> .b3fd5e52
yan-bilgi-kutu -> .b3fd5e52
kunye -> .bc3527a6
konum-panosu -> .b3fd5e52
bilesen sayisi: 4, uretilen kural sayisi: 2
.b3fd5e52 { border-radius: 4px; border: 1px solid #ccd; padding: 1.5rem }
.bc3527a6 { border: 1px solid #ccd; padding: 1.5rem }
--- hangi yaklasim neyi guvenceye alir ---
yaklasim ad cakismasi disaridan sizma disari sizma
yalniz adlandirma sozlesmesi engellenmez engellenmez engellenmez
uretilmis yerel ad engellenir engellenmez engellenir
calisma zamani uretimi engellenir engellenmez engellenir
belge kapsami bildirimi engellenmez engellenmez engellenir
golge agac kapsullemesi engellenir engellenir engellenir
Üretilmiş Yerel Adlar
Birinci yaklaşımda kaynak dosyalarda kısa, okunur adlar yazılır: .kart, .baslik. Bir
derleme adımı bu adları dosya yoluyla birlikte karmalayarak benzersiz adlara çevirir ve
belgeyi üreten şablon da aynı eşlemeyi kullanır.
Çıktının ilk bloğu iki özelliği gösteriyor. İki ayrı dosyadaki .kart adı farklı adlara
çevriliyor — çakışma yapısal olarak olanaksız. Aynı girdi ise her zaman aynı adı üretiyor;
üretim belirlenimcidir, iki ayrı derlemede aynı çıktı verir.
Üretilen adın başına yerel adın konması bilinçlidir: geliştirici araçlarında .kart_39622
görüldüğünde kaynağın hangi ad olduğu okunur. Yalnız karmadan oluşan adlar bu okunurluğu
kaybeder.
Yaklaşımın sınırı, adın çalışma zamanında birleştirilememesidir. Ad bir dizgi birleştirmesiyle üretiliyorsa derleyici o adı göremez ve eşleyemez; adlar kaynakta tam metin olarak geçmelidir.
Bildirim Karmasına Göre Paylaşım
İkinci yaklaşımda sınıf adı, bildirimlerin kendisinden türetilir. Bildirim kümesi sıralanarak tek bir metne indirgenir ve o metnin karması sınıf adı olur.
Çıktının ikinci bloğu dört bileşen için iki kural üretiyor. Üç bileşenin bildirimleri aynı — yazım sırası ve fazladan boşluk farklı olsa da düzenlenmiş metin aynı — ve üçü de aynı sınıfı paylaşıyor. Bildirim kümesi farklı olan tek bileşen kendi sınıfını alıyor.
Sonuç, üretilen dosyanın bileşen sayısıyla değil ayrı bildirim kümesi sayısıyla büyümesidir. Aynı gövdeyi kopyalayan önceki dersin yazımıyla karşılaştırıldığında yineleme kendiliğinden ortadan kalkar.
Bedeli üç başlıkta toplanır. Üretim çalışma zamanında yapılıyorsa kuralların belgeye eklenmesi ilk çizimden sonraya düşebilir. Kuralların basamaklamadaki sırası üretim sırasına bağlıdır ve kaynağı okuyarak öngörülemez. Karma adlar okunur değildir; hata ayıklama aracı kuralı gösterir ama hangi bileşenden geldiğini göstermez.
Belge Kapsamı Bildirimi
CSS’in kendi aracı @scope kuralıdır. Bir kapsam kökü ve isteğe bağlı bir kapsam
sınırı alır; içindeki kurallar yalnız kökün alt ağacında, sınırın dışında kalan ögelere
uyar.
@scope (.olcum-kart) to (.olcum-kart__govde) { p { margin-block: 0; } a { color: var(--marka-koyu); } }
Buradaki p kuralı yalnız ölçüm kartının içinde ve kart gövdesine girmeden önce geçerlidir.
Aynı kural kapsamsız yazılsaydı sayfadaki bütün paragraflara uyardı.
@scope bir yenilik daha getirir: aynı özelliğe yazan iki kapsamlı kural arasında, kapsam
kökü ögeye daha yakın olan kazanır. Bu ölçüt özgüllükten önce değerlendirilir. İç içe
geçmiş iki tema kapsamında içteki temanın kazanması bundandır.
Tablonun gösterdiği sınır önemlidir: @scope dışarı sızmayı engeller, ad çakışmasını
engellemez. İki bileşen aynı sınıf adını kullanıyorsa ve ikisi de kapsamsız kural yazıyorsa
sorun sürer.
Gölge Ağaç Kapsüllemesi
Üç yönü birden engelleyen tek yaklaşım gölge ağaçtır. Web Components ile kurulan bir gölge kökün içindeki biçem dosyası yalnız o ağaca uygulanır; dışarıdaki seçiciler içeriye, içerideki seçiciler dışarıya uymaz.
Kapsüllemenin bilinçli üç deliği vardır. Kalıtılan özellikler sınırı geçer: yazı tipi ve
renk dışarıdan içeriye akar. Özel özellikler de kalıtılır, dolayısıyla bileşenin biçem
arayüzü aynı mekanizmayla dışarıdan doldurulur. ::part sözde elemanı ise bileşenin
dışarıya açtığı parçaları hedeflenebilir kılar.
Bu delikler kusur değil tasarımdır: tam yalıtım, bileşeni temaya kapatırdı.
Seçim Ölçütü
Yaklaşımlar birbirini dışlamaz ve seçim üç soruya bağlıdır. Bileşenler ayrı takımlarca mı yazılıyor — ad çakışması gerçek bir risk mi? Sayfaya dışarıdan biçem geliyor mu — dışarıdan sızma korunması gerekiyor mu? Bir derleme adımı zaten var mı?
Küçük bir sayfada adlandırma sözleşmesi ile @scope birlikte yeterlidir; ikisi de derleme
adımı gerektirmez. Bileşenlerin bağımsız dağıtıldığı bir yerde üretilmiş adlar ya da gölge
ağaç gerekir.
İstasyon Sayfasında
/* bilesenler/olcum-kart.css — 4. adım: kapsamlı yazım */ @scope (.olcum-kart) { :scope { display: grid; gap: var(--aralik-0); background-color: var(--kart-zemin, var(--zemin)); } .olcum-kart__deger { font-size: 2rem; font-variant-numeric: tabular-nums; } p { margin-block: 0; } dt { color: var(--metin-soluk); } }
:scope sözde sınıfı kapsam kökünün kendisini hedefler. Tür seçicileri artık güvenle
yazılabiliyor: p ve dt kuralları yalnız kartın içinde geçerli, sayfanın geri kalanına
sızmıyor.
Sınıf adları yine de yöntemli yazıldı. Kapsam dışarı sızmayı kesiyor, ama sayfada başka bir
.olcum-kart__deger tanımlanırsa iki kural yine yarışır.
Özet
- Biçem sızıntısı üç ayrı olaydır: ad çakışması, dışarıdan sızma ve dışarı sızma; bir yaklaşım bunların hepsini birden engellemeyebilir.
- Üretilmiş yerel adlar, yerel adı dosya yoluyla karmalayarak çakışmayı yapısal olarak önler ve belirlenimcidir; adın çalışma zamanında birleştirilmemesi koşuluyla çalışır.
- Bildirim karmasına göre üretilen sınıflar aynı bildirim kümesini paylaştırır; üretilen dosya bileşen sayısıyla değil ayrı bildirim kümesi sayısıyla büyür.
@scopebir kök ve sınır alır, dışarı sızmayı keser ve yakınlık ölçütünü özgüllükten önce uygular; ad çakışmasını engellemez.- Gölge ağaç üç yönü de kapatır; kalıtılan özellikler, özel özellikler ve
::partbilinçli olarak açık bırakılmış geçitlerdir.
Sonraki Adım
Buraya kadarki yaklaşımların hepsi aynı varsayımı paylaşıyor: biçem bileşen başına yazılır ve her bileşenin kendi kuralları vardır. Bunun tersini savunan bir yaklaşım daha vardır — bileşene özel kural hiç yazmamak, belgeyi önceden tanımlı ve tek bildirimlik sınıflardan kurmak. Sonraki ders bu yaklaşımı ve dayandığı kısıt düşüncesini ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.