İçeriğe geç
academia.sh

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.
  • @scope bir 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 ::part bilinç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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat