İçeriğe geç
academia.sh

Ders 13 / 19

Geliştirme Sunucusu

Derlemenin ikinci kipi; modüllerin istendikçe dönüştürülüp sunulması, doğrulayıcıyla yeniden kullanım, sıcak modül değişiminin geçersiz kılma yayılımı olarak modellenmesi ve kabul sınırının doğruluk koşulları.

İçindekiler

Önceki beş ders tek bir kipi kurdu: yayın için derleme. Bu kipte çizge tümüyle taranır, parçalar birleştirilir, ölü kod atılır, varlıklar işlenir ve adlar içeriğe bağlanır. Adımların hepsinin karşılığı vardır — ama karşılığı yalnız yayında vardır.

Kuzey Yamaç Ölçüm İstasyonu arayüzünde bir biçimlendirme satırını değiştiren biri için bu adımların hiçbiri işe yaramaz. Çizgenin tümünü yeniden taramak, karmaları yeniden hesaplamak ve sayfayı baştan yüklemek iki şey kaybettirir: saniyeler ve ekrandaki durum. Panelin açık olması, süzgecin ayarlanmış olması, kaydırma konumu — tam yükleme hepsini siler.

Bu ders derlemenin ikinci kipini kurar. Sorusu şudur: bir dosya değiştiğinde en az ne yapılabilir?

Kipler Arasındaki Fark

Geliştirme kipi, yayın kipinin adımlarının çoğunu düşürür.

Paketleme düşer. Tarayıcı, standart modülleri kendisi yükleyebilir; çizgeyi dolaşma işi tarayıcının modül yükleyicisine bırakılır. Sunucunun görevi tek bir modülü istendiğinde vermeye iner.

Ağaç sarsma, küçültme ve içerik karması düşer. Üçü de çıktı boyutunu küçültür; yerel bağlantıda boyut belirleyici değildir ve üçü de tüm çizgeyi görmeyi gerektirir.

Kalması gerekenler ise üçtür: belirteç çözümü — tarayıcı çıplak belirteçleri çözemez —, hedefin anlamadığı sözdizimin çevrilmesi ve kaynak haritası üretimi. Modüller, Araçlar ve Ekosistem kursundaki kaynak haritaları dersi bu üçüncüsünün neden vazgeçilmez olduğunu göstermişti: dönüştürülmüş kodda hata ayıklanamaz.

Bu düşürme, maliyeti çizge boyutundan bağımsız kılar. Yayın kipinde bir değişiklik tüm çizgeyi ilgilendirir; geliştirme kipinde yalnız değişen dosyayı.

İstendikçe Sunma

Aşağıdaki sunucu tek bir dönüşüm yapar ve tek bir dosya sunar.

Bu dersin sunucusu, önceki derslerde kurulan kaynak/ ağacını sunar; o derslerin kurulum blokları bu dizinde önce çalıştırılmalıdır.

// gelistirme-sunucusu.mjs — modulleri istendikce, donusturerek ve dogrulayiciyla sunar.
import { createServer } from "node:http";
import { createHash } from "node:crypto";
import { readFileSync, existsSync } from "node:fs";

// Tek donusum: goreli belirtecler sunucudaki mutlak yollara cevrilir.
// Paketleme yok; her modul kendi dosyasi olarak kalir.
function donustur(yol, kaynak) {
  return "// [gelistirme] " + yol + "\n" + kaynak
    .replace(/from\s+"\.\/([^"]+)"/g, 'from "/kaynak/$1"')
    .replace(/^import\s+"\.\/([^"]+)"/gm, 'import "/kaynak/$1"')
    .replace(/import\(\s*"\.\/([^"]+)"\s*\)/g, 'import("/kaynak/$1")');
}

createServer((istek, yanit) => {
  yanit.sendDate = false;                        // ciktiyi belirlenimci kilmak icin
  const yol = istek.url.split("?")[0];
  const dosya = yol.replace(/^\//, "");
  if (!yol.startsWith("/kaynak/") || !existsSync(dosya)) {
    yanit.writeHead(404).end();
    return;
  }
  const govde = donustur(yol, readFileSync(dosya, "utf8"));
  const etiket = '"' + createHash("sha256").update(govde).digest("hex").slice(0, 12) + '"';
  if (istek.headers["if-none-match"] === etiket) {
    yanit.writeHead(304, { ETag: etiket }).end();
    return;
  }
  yanit.writeHead(200, {
    "Content-Type": "text/javascript; charset=utf-8",
    "Content-Length": Buffer.byteLength(govde),
    "Cache-Control": "no-cache",
    ETag: etiket,
  }).end(govde);
}).listen(8172, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8172"));

Çizgeyi dolaşan taraf tarayıcıdır. Aşağıdaki istemci, tarayıcının modül yükleme döngüsünün küçük bir karşılığıdır: bir modülü alır, içindeki statik belirteçleri okur, onları ister. Dinamik içe aktarımı izlemez — çünkü tarayıcı da izlemez, çağrı yürütülene kadar bekler.

// istemci.mjs — tarayicinin modul yukleme dongusunun kucuk bir karsiligi.
// Yalniz statik belirtecler izlenir; import() cagrisi yurutulene kadar istenmez.
const KOK = "http://127.0.0.1:8172";
const STATIK = [/from\s*"([^"]+)"/g, /^import\s*"([^"]+)"/gm];

async function yukle(kokler) {
  const alinan = new Map(), kuyruk = [...kokler];
  while (kuyruk.length > 0) {
    const yol = kuyruk.shift();
    if (alinan.has(yol)) continue;
    const govde = await (await fetch(KOK + yol)).text();
    alinan.set(yol, govde.length);
    for (const kalip of STATIK)
      for (const [, b] of govde.matchAll(kalip)) if (b.startsWith("/kaynak/")) kuyruk.push(b);
  }
  return alinan;
}

const kisa = (k) => [...k].map(([y]) => y.replace("/kaynak/", "")).sort().join(", ");
const ilk = await yukle(["/kaynak/giris-tarayici.js"]);
console.log("ilk yuklemede alinan : " + ilk.size + " modul  " + kisa(ilk));
const sonra = await yukle([...ilk.keys(), "/kaynak/panel.js"]);
const ek = new Map([...sonra].filter(([y]) => !ilk.has(y)));
console.log("panel acilinca ek    : " + ek.size + " modul  " + kisa(ek));
console.log("uyari.js henuz istenmedi: " + !sonra.has("/kaynak/uyari.js"));

Uzun ömürlü sunucu kendi betiğiyle başlatılır. Bağlantı noktası olarak seçilen 8172 keyfîdir ve boş olmalıdır.

#!/usr/bin/env bash
# Sunucuyu baslatir, tek modulu ister, dogrulayiciyi sinar, modul dongusunu kosturur.
cp kaynak/sablon.js sablon.ilk
node gelistirme-sunucusu.mjs > /dev/null &
sunucu=$!
sleep 1

echo "--- tek modulun sunulan hali ---"
curl -sS http://127.0.0.1:8172/kaynak/sablon.js

ETIKET=$(curl -sS -o /dev/null -D - http://127.0.0.1:8172/kaynak/sablon.js \
  | awk 'tolower($1) == "etag:" { print $2 }' | tr -d '\r')
echo "--- dogrulayici $ETIKET ---"
curl -sS -o /dev/null -w "degismeden ikinci istek : %{http_code}\n" \
  -H "If-None-Match: $ETIKET" http://127.0.0.1:8172/kaynak/sablon.js
printf '\nexport const dipnot = () => "olcumler UTC";\n' >> kaynak/sablon.js
curl -sS -o /dev/null -w "dosya degistikten sonra : %{http_code}\n" \
  -H "If-None-Match: $ETIKET" http://127.0.0.1:8172/kaynak/sablon.js

echo "--- istendikce yukleme ---"
node istemci.mjs

kill "$sunucu"
mv sablon.ilk kaynak/sablon.js
--- tek modulun sunulan hali ---
// [gelistirme] /kaynak/sablon.js
import { olcumSatiri } from "/kaynak/bicim.js";

export const liste = (olcumler) =>
  "<ul>" + olcumler.map((o) => "<li>" + olcumSatiri(o) + "</li>").join("") + "</ul>";
export const baslik = (istasyon) => "<h1>" + istasyon + "</h1>";

--- dogrulayici "4e20f52c3876" ---
degismeden ikinci istek : 304
dosya degistikten sonra : 200
--- istendikce yukleme ---
ilk yuklemede alinan : 9 modul  arac.js, bicim.js, birim.js, giris-tarayici.js, kayit.js, olcek-kaydi.js, sablon.js, tahmin.js, tarih.js
panel acilinca ek    : 3 modul  disa-aktarma.js, grafik.js, panel.js
uyari.js henuz istenmedi: true

Doğrulayıcı değeri sunulan modülün baytlarından üretilir: aynı bayt dizisi her zaman aynı değeri, tek karakterlik bir değişiklik başka bir değeri verir. Buradaki değer bu dizinin o andaki içeriğine bağlıdır — önceki derslerin kurulum blokları aynı dosyayı yeniden yazdığı için, hangi blokların çalıştırıldığına göre farklı bir doğrulayıcı görülebilir. Anlamlı olan değerin kendisi değil, değişmeyen modülde aynı kalmasıdır.

Çıktının son üç satırı geliştirme kipinin kazancını gösterir. İlk yüklemede on üç modülün dokuzu istenir; panel açılınca üçü daha gelir; eşik uyarısı modülü hiç istenmez, çünkü onu isteyen dinamik çağrı henüz yürümemiştir. Hiçbir aşamada tüm çizge taranmaz.

Doğrulayıcı satırları ikinci kazancı verir. İçeriği değişmemiş bir modül, ikinci istekte gövdesiz yanıtla geçiştirilir; sayfa yenilendiğinde yalnız değişen dosyalar yeniden iner.

Geçersiz Kılma Yayılımı

Sayfayı yenilemeden güncellemek bundan fazlasını gerektirir. Bir modülün yeni sürümü alındığında, onu içe aktarmış modüllerin elindeki değerler eskidir: içe aktarılan bağlar yeniden okunmalı, o modüller de yeniden değerlendirilmelidir. Değişim, çizgede kenar yönünün tersine — istenenden isteyene doğru — yayılır.

Yayılımın sonsuza kadar sürmemesi için bir durak gerekir. Kabul sınırı (accept boundary), kendi güncellemesini karşılayabildiğini bildiren modüldür: yeni sürümüyle değiştirilir, isteyenlerine haber verilmez. Sınır bulunamaz ve yayılım giriş noktasına ulaşırsa yapılacak tek şey tam yüklemedir.

// sicak.mjs — modul cizgesinde gecersiz kilma yayilimi ve kabul siniri.
import { readFileSync } from "node:fs";
import { dirname, join, relative } from "node:path";

const GIRIS = "kaynak/giris-tarayici.js";
const BAG = [/from\s+"(\.[^"]+)"/g, /^import\s+"(\.[^"]+)"/gm, /import\(\s*"(\.[^"]+)"\s*\)/g];

// Kabul siniri: guncellemesini kendisi karsilayan, importerlarina yaymayan modul.
const KABUL = new Set(["kaynak/sablon.js", "kaynak/panel.js", "kaynak/grafik.js"]);
// Modul duzeyinde durum tutan moduller: yeniden degerlendirilirse durum sifirlanir.
const DURUM = new Set(["kaynak/kayit.js"]);

const cizge = new Map(), kuyruk = [GIRIS];
while (kuyruk.length > 0) {
  const yol = kuyruk.shift();
  if (cizge.has(yol)) continue;
  const kaynak = readFileSync(yol, "utf8");
  const komsu = BAG.flatMap((k) => [...kaynak.matchAll(k)]
    .map(([, b]) => relative(".", join(dirname(yol), b))));
  cizge.set(yol, komsu);
  kuyruk.push(...komsu);
}

const isteyen = new Map([...cizge.keys()].map((y) => [y, []]));
for (const [yol, komsu] of cizge) for (const k of komsu) isteyen.get(k).push(yol);

function yayil(degisen) {
  const gecersiz = [], gorulen = new Set(), kuyruk = [degisen];
  let tamYukleme = false;
  while (kuyruk.length > 0) {
    const yol = kuyruk.shift();
    if (gorulen.has(yol)) continue;
    gorulen.add(yol);
    gecersiz.push(yol);
    if (KABUL.has(yol)) continue;                 // sinir bulundu, yukari yayilmaz
    if (yol === GIRIS) { tamYukleme = true; continue; }
    kuyruk.push(...isteyen.get(yol));
  }
  return { gecersiz, tamYukleme, durum: gecersiz.filter((y) => DURUM.has(y)) };
}

const ad = (y) => y.replace("kaynak/", "");
console.log("cizge: " + cizge.size + " modul, kabul siniri: " + [...KABUL].map(ad).join(", "));
console.log("degisen".padEnd(18) + "sonuc".padEnd(14) + "yeniden degerlendirilen kume");
for (const degisen of ["kaynak/grafik.js", "kaynak/panel.js", "kaynak/bicim.js",
                       "kaynak/birim.js", "kaynak/kayit.js", "kaynak/giris-tarayici.js"]) {
  const r = yayil(degisen);
  console.log(ad(degisen).padEnd(18) +
    (r.tamYukleme ? "tam yukleme" : "sicak degisim").padEnd(14) +
    r.gecersiz.length + "/" + cizge.size + "  " + r.gecersiz.map(ad).join(", ") +
    (r.durum.length ? "   [durum sifirlanir: " + r.durum.map(ad).join(", ") + "]" : ""));
}
$ node sicak.mjs
cizge: 13 modul, kabul siniri: sablon.js, panel.js, grafik.js
degisen           sonuc         yeniden degerlendirilen kume
grafik.js         sicak degisim 1/13  grafik.js
panel.js          sicak degisim 1/13  panel.js
bicim.js          sicak degisim 4/13  bicim.js, sablon.js, disa-aktarma.js, panel.js
birim.js          sicak degisim 10/13  birim.js, grafik.js, uyari.js, arac.js, tahmin.js, olcek-kaydi.js, panel.js, bicim.js, sablon.js, disa-aktarma.js
kayit.js          sicak degisim 7/13  kayit.js, arac.js, olcek-kaydi.js, bicim.js, sablon.js, disa-aktarma.js, panel.js   [durum sifirlanir: kayit.js]
giris-tarayici.js tam yukleme   1/13  giris-tarayici.js

Tablo üç şey söyler.

Sınırın altındaki değişiklik ucuzdur. Grafik ve panel modülleri kendi güncellemelerini kabul ettiği için yalnız kendileri yeniden değerlendirilir; on üç modülün on ikisi dokunulmadan kalır.

Yaprak modüldeki değişiklik pahalıdır. Birim modülü çizgede aşağıdadır ve onu doğrudan ya da dolaylı olarak isteyen dokuz modül vardır; hepsi yeniden değerlendirilir. Ortak yardımcı modüllere yapılan küçük düzenlemeler bu yüzden yavaş görünür.

Kabul sınırının yeri kararı belirler. Sınırlar görünüm sınırlarına konur, çünkü bir görünüm kendi çıktısını yeniden üretebilir. Yardımcı modüller sınır olamaz: değerlerini isteyenlerin elindeki bağları güncelleyemezler.

Sıcak Değişimin Doğruluk Koşulu

Sıcak değişim bir iyileştirme değil, bir risktir; doğru uygulanmadığında geliştiricinin gördüğü durumla soğuk yüklemede oluşacak durum ayrışır.

Birinci tehlike durum sıfırlanmasıdır. Modül düzeyinde durum tutan bir modül yeniden değerlendirildiğinde o durum kaybolur. Tablodaki son sütun bunu işaretliyor: kayıt modülü güncellendiğinde biçimlendirici kaydı boşalır ve onu dolduran yan etkili modül de yeniden çalışmazsa arayüz eksik davranır. Ağaç sarsma dersindeki sessiz davranış kaybının aynısı, bu kez geliştirme sırasında ortaya çıkar.

İkinci tehlike eski kapanışlardır. Bir modülün eski sürümünde kaydedilmiş bir olay dinleyicisi ya da zamanlayıcı, modül değiştirildikten sonra da çalışmaya devam eder. Kabul sınırı, yeni sürüme geçmeden önce eskisinin bıraktıklarını temizlemekle yükümlüdür.

Üçüncü tehlike birikmiş sapmadır. Onlarca sıcak değişimden sonraki durum, aynı kaynağın soğuk yüklemesiyle oluşacak durumdan farklı olabilir. Kural, şüphe duyulan her noktada sayfayı elle yenilemek ve doğrulamayı soğuk yükleme üzerinde yapmaktır.

Bu üç tehlike, sıcak değişimin uygulanamadığı durumlarda tam yüklemeye düşmenin neden bir kusur değil bir güvence olduğunu açıklar.

Geliştirme ile Yayın Arasındaki Uçurum

Geliştirme kipinin düşürdüğü her adım, iki kip arasında bir davranış farkı olasılığı yaratır. Ağaç sarsma kapalıysa, elenmemesi gereken bir modülün elendiği yayında ortaya çıkar. Modüller ayrı dosyalar hâlinde sunuluyorsa, paketlemenin ürettiği değerlendirme sırası farkı yayında ortaya çıkar. Küçültme kapalıysa, ada dayalı bir davranış yayında kırılır.

Bu farkların hepsinin ortak bir sonucu vardır: geliştirme kipinde çalışması, yayın kipinde çalıştığının kanıtı değildir. Bu yüzden yayın çıktısını yerelde çalıştırıp denemek geliştirme akışının bir parçasıdır ve dağıtım konusundaki önizleme dağıtımları dersi bu denemeyi her değişikliğe yayacaktır.

Özet

  • Geliştirme kipi paketleme, ağaç sarsma, küçültme ve içerik karmasını düşürür; belirteç çözümü, sözdizim çevirisi ve kaynak haritası kalır.
  • Çizgeyi tarayıcının modül yükleyicisi dolaşır; sunucu tek bir modülü dönüştürüp verir, maliyet çizge boyutundan bağımsızlaşır.
  • Örnekte ilk yükleme on üç modülün dokuzunu ister, panel üç modül daha ekler, dinamik sınırın ardındaki modül hiç istenmez; doğrulayıcı değişmeyen modülleri gövdesiz yanıtla geçiştirir.
  • Sıcak değişim, geçersiz kılmanın isteyenlere doğru yayılmasıdır; kabul sınırı yayılımı durdurur, sınır bulunamazsa tam yükleme yapılır.
  • Sınırın altındaki değişiklik bir modülü, ortak bir yardımcı modüldeki değişiklik on modülü yeniden değerlendirtir; sınırlar görünüm sınırlarına konur.
  • Sıcak değişim durum sıfırlanması, eski kapanışlar ve birikmiş sapma riskleri taşır; şüphe hâlinde soğuk yükleme doğrulamanın ölçüsüdür.

Sonraki Adım

Geliştirme ve yayın kipleri yalnız hızda değil, çalıştıkları ortamda da ayrılır. Yerelde istasyon verisi bir deneme ucundan, yayında gerçek ölçüm hattından gelir; günlük ayrıntısı yerelde açık, yayında kapalıdır; kimlik doğrulama uçları farklı adreslerdedir. Bu farkların kaynağa gömülmesi kabul edilemez, çünkü aynı kaynaktan iki ayrı çıktı üretilir. Farkı taşıyan şey derleme zamanı değişkenleridir. Sonraki ders bu değişkenlerin çıktıya nasıl girdiğini gösterir ve tek kritik kuralı kurar: istemciye giden bir değişken herkese açıktı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