İçeriğe geç
academia.sh

Ders 20 / 23

Ön ve Son İşlemciler

Derleme zamanında çalışan biçem araçları; iç içe yazımın düzleştirilmesi, gövde kopyalamanın bedeli, derleme zamanı değeriyle çalışma zamanı değerinin ayrımı ve son işlemcilerin dönüştürmeleri.

İçindekiler

Önceki dersteki bütün adlar tarayıcıda, sayfa çalışırken çözülüyordu. Bir başka araç sınıfı işlerin bir bölümünü daha erken yapar: dosya tarayıcıya ulaşmadan önce, derleme sırasında.

Bu ders o araçların ne yaptığını, hangi işin derleme zamanına hangisinin çalışma zamanına ait olduğunu ve derleme adımının bedelini ele alır. Belirli bir araç değil, iki araç sınıfı anlatılır.

İki Araç Sınıfı

Ön işlemci (preprocessor), CSS olmayan bir kaynak dilini girdi alır ve CSS üretir. Kaynak dil CSS’in üzerine yazım kolaylıkları ekler: iç içe kural yazımı, derleme zamanı değişkenleri, yeniden kullanılan bildirim gövdeleri, dosya bölme ve içe alma, döngü ve koşul.

Son işlemci (postprocessor), CSS alır ve CSS üretir. Yaptığı iş dönüştürmedir: yazımı başka bir yazıma çevirmek, gereksiz karakterleri atmak, kuralları birleştirmek, kullanılmayan kuralları çıkarmak.

Sınır kesin değildir; ikisi de derleme zamanında çalışan program dönüştürücüleridir. Ayrımı yapan, girdinin CSS olup olmadığıdır. Bir yapı zincirinde ikisi art arda kullanılabilir.

İç İçe Yazımın Düzleştirilmesi

En çok kullanılan kolaylık iç içe yazımdır. Kaynakta kurallar birbirinin içine yazılır, derleme sonunda düz kurallara açılır.

// onisleme.mjs — ic ice yazimin duzlestirilmesi ve derleme zamani yinelemesinin sayimi
// Ic ice bir kural agaci, derleme zamaninda duz kurallara acilir.
const KAYNAK = {
  secici: ".olcum-kart",
  bildirim: ["display: grid", "gap: var(--aralik-0)"],
  ic: [
    { secici: "&__baslik", bildirim: ["color: var(--metin-soluk)"], ic: [] },
    { secici: "&__deger", bildirim: ["font-size: 2rem"], ic: [
      { secici: "&--eksik", bildirim: ["color: var(--uyari)"], ic: [] },
    ] },
    { secici: "&:hover", bildirim: ["transform: translateY(-8px)"], ic: [] },
    { secici: ".olcum-kartlari &", bildirim: ["margin: 0"], ic: [] },
    { secici: "p", bildirim: ["margin-block: 0"], ic: [] },              // & yok: alt oge birlestiricisi
    { secici: "@media (min-width: 48rem)", bildirim: [], ic: [
      { secici: "&", bildirim: ["gap: var(--aralik-1)"], ic: [] },
    ] },
  ],
};

function duzlestir(dugum, ustSecici = null, kosul = null, cikti = []) {
  const kosullu = dugum.secici.startsWith("@");
  const yeniKosul = kosullu ? dugum.secici : kosul;
  let secici = ustSecici;
  if (!kosullu) {
    if (ustSecici === null) secici = dugum.secici;
    else if (dugum.secici.includes("&")) secici = dugum.secici.replaceAll("&", ustSecici);
    else secici = `${ustSecici} ${dugum.secici}`;
  }
  if (dugum.bildirim.length > 0) cikti.push({ kosul: yeniKosul, secici, bildirim: dugum.bildirim });
  for (const c of dugum.ic) duzlestir(c, secici, yeniKosul, cikti);
  return cikti;
}

// ozgulluk uclusu: a=kimlik, b=sinif/oznitelik/sozde sinif, c=tur
const ozgulluk = (s) => [
  (s.match(/#[\w-]+/g) || []).length,
  (s.match(/\.[\w-]+/g) || []).length + (s.match(/\[[^\]]+\]/g) || []).length + (s.match(/:(?!:)[\w-]+/g) || []).length,
  (s.replace(/:(?!:)[\w-]+/g, " ").match(/(^|[\s>+~])([a-zA-Z][\w-]*)/g) || []).length,
];

console.log("--- ic ice kaynagin duzlestirilmesi ---");
for (const k of duzlestir(KAYNAK)) {
  const [a, b, c] = ozgulluk(k.secici);
  const on = k.kosul ? `${k.kosul} { ` : "";
  const arka = k.kosul ? " }" : "";
  console.log(`  ${(on + k.secici + " { " + k.bildirim.join("; ") + " }" + arka).padEnd(78)} (${a}, ${b}, ${c})`);
}

// --- derleme zamani yinelemesi: her cagrida govde kopyalanir ---
console.log("\n--- ayni bildirim kumesini paylasmanin iki yolu ---");
const GOVDE = ["border: 1px solid var(--cizgi)", "border-radius: 4px", "padding: var(--aralik-1)"];
const govdeBayt = GOVDE.join("; ").length + 2;
const BILESENLER = [".olcum-kart", ".yan-bilgi__kutu", ".kunye", ".konum-bolumu__pano", ".olcum-suzgeci"];

let kopyali = 0;
for (const b of BILESENLER) kopyali += b.length + 3 + govdeBayt;
const ortakSecici = BILESENLER.join(", ");
const ortak = ortakSecici.length + 3 + govdeBayt;

console.log(`  govde: ${GOVDE.length} bildirim, ${govdeBayt} bayt`);
console.log(`  ${BILESENLER.length} bilesene govdeyi kopyalayan yazim : ${kopyali} bayt`);
console.log(`  ortak secici listesiyle tek kural          : ${ortak} bayt`);
console.log(`  fark: ${kopyali - ortak} bayt (%${(((kopyali - ortak) / kopyali) * 100).toFixed(1)} daha az)`);

// --- derleme zamani degeri ile calisma zamani degerinin ayrimi ---
console.log("\n--- ayni ad, iki cozum ani ---");
const DERLEME = { "$aralik": "1.5rem" };                    // derleme zamaninda yerine konur
const uretilen = "padding: $aralik;".replace(/\$[\w-]+/g, (m) => DERLEME[m]);
console.log("  derleme zamani degiskeni -> uretilen CSS:", uretilen);
console.log("  calisma zamani ozelligi  -> uretilen CSS: padding: var(--aralik-1);");
console.log("  ilk yazimda uretilen dosyada 'aralik' adi yoktur; deger sabittir.");
--- ic ice kaynagin duzlestirilmesi ---
  .olcum-kart { display: grid; gap: var(--aralik-0) }                            (0, 1, 0)
  .olcum-kart__baslik { color: var(--metin-soluk) }                              (0, 1, 0)
  .olcum-kart__deger { font-size: 2rem }                                         (0, 1, 0)
  .olcum-kart__deger--eksik { color: var(--uyari) }                              (0, 1, 0)
  .olcum-kart:hover { transform: translateY(-8px) }                              (0, 2, 0)
  .olcum-kartlari .olcum-kart { margin: 0 }                                      (0, 2, 0)
  .olcum-kart p { margin-block: 0 }                                              (0, 1, 1)
  @media (min-width: 48rem) { .olcum-kart { gap: var(--aralik-1) } }             (0, 1, 0)

--- ayni bildirim kumesini paylasmanin iki yolu ---
  govde: 3 bildirim, 78 bayt
  5 bilesene govdeyi kopyalayan yazim : 471 bayt
  ortak secici listesiyle tek kural          : 155 bayt
  fark: 316 bayt (%67.1 daha az)

--- ayni ad, iki cozum ani ---
  derleme zamani degiskeni -> uretilen CSS: padding: 1.5rem;
  calisma zamani ozelligi  -> uretilen CSS: padding: var(--aralik-1);
  ilk yazimda uretilen dosyada 'aralik' adi yoktur; deger sabittir.

Düzleştirme tek bir kurala dayanır: iç kuralda & varsa üst seçicinin yerine konur, yoksa üst seçiciyle arasına alt öge birleştiricisi girer.

Bu ayrım çıktının yedinci satırında görünüyor. p seçicisi & taşımadığı için .olcum-kart p oluyor; üçlüsü de (0,1,1)(0, 1, 1)’e çıkıyor. Dördüncü satırda ise &__deger yazımı seçiciyi birleştirerek tek sınıfa çeviriyor ve üçlü (0,1,0)(0, 1, 0) kalıyor. Önceki dersin adlandırma yöntemi iç içe yazımla ancak bu ikinci biçimde birlikte çalışır.

Beşinci satır & işaretinin sonda da yazılabileceğini gösteriyor: .olcum-kartlari & yazımı bileşeni bir bağlamın içine yerleştirir ve üçlüyü büyütür.

Bir uyarı gerekir: iç içe yazım CSS’in kendisinde de tanımlıdır. Bu durumda düzleştirmeyi derleyici değil tarayıcı yapar ve kural aynı kalır. Yani iç içe yazım tek başına bir derleme adımını gerektirmez.

Yinelemenin Bedeli

Ön işlemcilerin ikinci yaygın kolaylığı, bir bildirim gövdesini adlandırıp birden çok yerde çağırmaktır. Çağrı derleme zamanında kopyalanır; üretilen dosyada aynı bildirimler her seçicinin altında ayrı ayrı yer alır.

Çıktının ikinci bloğu beş bileşen için bunu sayıyor: kopyalayan yazım 471 bayt, ortak seçici listesiyle yazılan tek kural 155 bayt. Fark, gövdenin kaç kez çağrıldığıyla doğru orantılı büyür.

Bu, gövde paylaşımının yasak olduğu anlamına gelmez; kararın ölçütünü verir. Gövde birkaç bildirimden uzunsa ve çok sayıda yerde çağrılıyorsa ortak bir seçici listesi ya da ortak bir sınıf daha az bayt üretir. Kısa ve az sayıda çağrılan gövdelerde kopyalama okunurluğu artırır.

Derleme Zamanı Değeri ile Çalışma Zamanı Değeri

Üçüncü blok iki değişken türünü aynı sonuç üzerinden karşılaştırıyor. Derleme zamanı değişkeni üretilen dosyada iz bırakmaz: padding: 1.5rem yazılıdır, aralik adı yoktur. Çalışma zamanı özelliği ise dosyada durur ve tarayıcıda çözülür.

Ayrım, hangisinin seçileceğini belirler:

  • Değer bağlama göre değişecekse — ortam sorgusuna, kullanıcı tercihine, bir öznitelik değerine, betiğe göre — çalışma zamanı özelliği gerekir. Derleme zamanı değeri bunların hiçbirini göremez.
  • Değer yalnız kaynağı düzenlemek içinse — bir dosya yolu, bir kesme noktası sayısı, bir döngü sınırı — derleme zamanı değişkeni yeterlidir ve üretilen dosyayı küçültür.

Karışık kullanım da olağandır: kaynak ölçek derleme zamanında hesaplanır, hesaplanan değerler çalışma zamanı özelliklerine yazılır.

Son İşlemcilerin İşleri

Son işlemcilerin dönüştürmeleri dört öbekte toplanabilir.

Yazım genişletme. Bir özelliğin henüz her yerde çözülmeyen yazımı, eşdeğer eski yazımla birlikte üretilir. Kaynak tek yazımla kalır, üretilen dosya iki yazımı da taşır.

Küçültme. Boşluk, yorum ve gereksiz noktalı virgüller atılır; renkler ve sayılar en kısa eşdeğer yazımlarına çevrilir. Anlam değişmez, bayt sayısı düşer.

Birleştirme. Aynı koşulu taşıyan ortam sorguları tek bloğa toplanır; aynı bildirim kümesine sahip seçiciler tek kuralda birleştirilir.

Kullanılmayan kural ayıklama. Belgede eşleşmeyen seçiciler çıkarılır. Bu dönüştürme risklidir: sınıf adı çalışma zamanında üretiliyorsa — bir öznitelik değerinden birleştirilerek ya da betikle eklenerek — ayıklayıcı o adı göremez ve gerekli kuralı siler. Bu yüzden adların kaynakta tam metin olarak geçmesi bir yazım kuralına dönüşür.

Derleme Adımının Bedeli

Derleme adımı üç maliyet getirir. Birincisi kurulumdur: kaynak dosyaların doğrudan çalışmaması, her değişikliğin bir yapı komutundan geçmesi.

İkincisi hata ayıklama uzaklığıdır. Tarayıcı üretilen dosyayı gösterir, yazdığınız dosyayı değil. Kaynak eşlemesi (source map) bu uzaklığı kapatır: üretilen dosyadaki her konumun kaynaktaki karşılığını tutan bir yan dosya üretilir ve geliştirici araçları kuralı kaynak satırıyla gösterir.

Üçüncüsü bağımlılıktır. Kaynak dil CSS değilse, dosyalar o dilin araç zinciri olmadan okunamaz. Bu bedel, kaynak dilin sağladığı kolaylıklar platformun kendisine geçtikçe azalır.

İstasyon Sayfasının Kaynağı

/* bilesenler/olcum-kart.css — 3. adım: iç içe yazımla aynı bileşen */
.olcum-kart {
  display: grid;
  gap: var(--aralik-0);

  &__baslik { color: var(--metin-soluk); }
  &__deger  { font-size: 2rem; font-variant-numeric: tabular-nums; }
  &__deger--eksik { color: var(--uyari); }

  &:hover,
  &:focus-within { transform: translateY(var(--kart-vurgu-yolu, -8px)); }

  @media (prefers-reduced-motion: reduce) {
    &:hover,
    &:focus-within { transform: none; }
  }
}

Bileşenin bütün kuralları tek bir blokta toplandı ve hepsi & ile birleşen tek sınıf seçicileri üretiyor. Ortam sorgusu da bileşenin içinde duruyor; hareket azaltma kuralı artık ayrı bir dosyada değil, ait olduğu bileşenin yanında.

Bu yazımın sınırı, iç içe girmenin derinliğidir. İki düzeyden fazla iç içe yazım, üretilen seçicilerin okunmasını zorlaştırır ve & taşımayan bir iç kural üçlüyü sessizce büyütür.

Özet

  • Ön işlemci CSS olmayan bir kaynak dilinden CSS üretir; son işlemci CSS alıp CSS üretir. İkisi de derleme zamanında çalışır ve sınırları kesin değildir.
  • İç içe yazım tek kuralla düzleşir: & varsa üst seçicinin yerine konur, yoksa alt öge birleştiricisi girer ve özgüllük üçlüsü büyür.
  • Adlandırılmış bir bildirim gövdesinin her çağrısı üretilen dosyaya kopyalanır; çok sayıda çağrıda ortak bir seçici listesi daha az bayt üretir.
  • Derleme zamanı değeri üretilen dosyada iz bırakmaz ve bağlamı göremez; bağlama göre değişmesi gereken her değer çalışma zamanı özelliği olmalıdır.
  • Kullanılmayan kural ayıklama, sınıf adları kaynakta tam metin olarak geçmiyorsa gerekli kuralları silebilir.

Sonraki Adım

Derleme adımı bir sorunu çözmedi: adlar hâlâ küresel. İki ayrı bileşen dosyasında aynı sınıf adı yazılırsa kurallar birbirine karışır ve önceki dersteki adlandırma yöntemi bunu yalnız sözleşmeyle önler — kimse denetlemezse çakışma olur. Sonraki ders adların kapsamını gerçekten sınırlayan yaklaşımları 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