Ders 05 / 22
Kod Bölme ve Tembel Yükleme
Paketin rota sınırlarından bölünmesi; bağımlılık kapanışı, kullanım imzasına göre parça oluşturma, ilk yükleme maliyetinin ölçülmesi, gezinme başına ek indirme, önceden getirme ve bölmenin getirdiği bekleme ile hata durumları.
İçindekiler
Yönlendirme artık hangi ekranın kime açık olduğunu biliyor. Bilmediği şey, o ekranın kodunun ne zaman indirileceği. Kuzey Yamaç Ölçüm İstasyonu uygulamasının bütün ekranları tek bir pakette birlikte iniyor: yalnız oturum açmak için gelen bir kullanıcı, ölçüm grafiğinin çizim kütüphanesini ve istasyon haritasını da indiriyor.
Rota tanımı bu bölünmeyi yapmak için gereken bilgiyi zaten taşıyor. Hangi bileşenin hangi yolda kullanıldığı ağacın içinde yazılı; kullanıcının hangi yolda olduğu da çalışma zamanında bilinen tek şey. Kod bölme, bu iki bilgiyi birleştirip paketi rota sınırlarından ayırma işidir.
Bağımlılık Kapanışı
Bir rotanın gerçekte neye ihtiyaç duyduğu, doğrudan kullandığı modüllerle sınırlı değildir. Modüller birbirine bağlıdır: ölçüm grafiği çizim modülünü, o da tarih biçimlendirmeyi kullanır. Rotanın maliyeti, bağımlılık çizgesinde ondan erişilebilen tüm modüllerin toplamıdır. Bu kümeye bağımlılık kapanışı denir ve Veri Yapıları kursundaki çizge dolaşımıyla hesaplanır.
Kapanışlar çıkarıldıktan sonra ikinci soru gelir: bir modül birden çok rotada kullanılıyorsa hangi parçaya girecek? Her rotanın parçasına kopyalanırsa aynı kod birden çok kez iner. Tek bir ortak parçaya konursa, o parçayı istemeyen rotalar da onu indirmek zorunda kalır.
Çözüm modülleri kullanım imzasına göre gruplamaktır: bir modülün imzası, onu isteyen rotaların kümesidir. Aynı imzaya sahip modüller birlikte iner, çünkü hep birlikte gerekirler.
// kod-bolme.mjs — rota bagimlilik kapanisi, kullanim imzasina gore parcalama ve maliyet const MODUL = { // boyut: aktarilan kB, bagimlilik: dogrudan cagrilanlar "cekirdek": { boyut: 38, bagimlilik: ["tasarim-sistemi"] }, "tasarim-sistemi": { boyut: 24, bagimlilik: [] }, "tablo": { boyut: 18, bagimlilik: ["tasarim-sistemi"] }, "grafik": { boyut: 96, bagimlilik: ["tasarim-sistemi", "tarih-bicimlendirme"] }, "harita": { boyut: 120, bagimlilik: ["tasarim-sistemi"] }, "form-dogrulama": { boyut: 15, bagimlilik: ["tasarim-sistemi"] }, "tarih-bicimlendirme": { boyut: 12, bagimlilik: [] }, "disa-aktarma": { boyut: 30, bagimlilik: [] }, }; // Her rota: kendi duzen zinciri boyunca gereken modullerin listesi. const ROTA = { "/oturum": ["form-dogrulama"], "/istasyon": ["tablo", "harita"], "/istasyon/:kimlik": ["tablo", "tarih-bicimlendirme"], "/istasyon/:kimlik/olcum": ["grafik", "tablo", "disa-aktarma"], "/istasyon/:kimlik/ayar": ["form-dogrulama"], }; const GIRIS = ["cekirdek"]; // her ziyarette inen giris noktasi function kapanis(kokler) { // bagimlilik cizgesinde erisilen tum modulller const gorulen = new Set(), yigit = [...kokler]; while (yigit.length) { const m = yigit.pop(); if (gorulen.has(m)) continue; gorulen.add(m); yigit.push(...MODUL[m].bagimlilik); } return gorulen; } const toplam = (kume) => [...kume].reduce((t, m) => t + MODUL[m].boyut, 0); const girisKapanis = kapanis(GIRIS); const rotaKapanis = Object.fromEntries( Object.entries(ROTA).map(([yol, k]) => [yol, kapanis(k)])); // Kullanim imzasi: bir modulu isteyen rotalarin kumesi. Ayni imza = ayni parca. const imza = new Map(); for (const [modul] of Object.entries(MODUL)) { if (girisKapanis.has(modul)) continue; // giris parcasinda zaten var const isteyen = Object.keys(ROTA).filter((y) => rotaKapanis[y].has(modul)); if (isteyen.length === 0) continue; const anahtar = isteyen.join("+"); if (!imza.has(anahtar)) imza.set(anahtar, []); imza.get(anahtar).push(modul); } console.log("-- parcalar --"); console.log(`giris${" ".repeat(3)}${String(toplam(girisKapanis)).padStart(4)} kB ${[...girisKapanis].sort().join(", ")}`); const parcaAdi = new Map(); let no = 0; for (const [anahtar, moduller] of [...imza].sort((a, b) => b[0].split("+").length - a[0].split("+").length)) { const ad = `parca-${++no}`; parcaAdi.set(anahtar, ad); console.log(`${ad} ${String(moduller.reduce((t, m) => t + MODUL[m].boyut, 0)).padStart(4)} kB ${moduller.sort().join(", ")}`); console.log(`${" ".repeat(12)}isteyen: ${anahtar.split("+").join(" ")}`); } const tekPaket = toplam(new Set([...girisKapanis, ...Object.values(rotaKapanis).flatMap((s) => [...s])])); const bolunmus = (yol) => toplam(new Set([...girisKapanis, ...rotaKapanis[yol]])); console.log("-- ilk yukleme maliyeti --"); console.log("rota".padEnd(26), "tek paket", "bolunmus", "kazanc"); for (const yol of Object.keys(ROTA)) { const b = bolunmus(yol); console.log(yol.padEnd(26), String(tekPaket).padStart(9), String(b).padStart(9), `${String(Math.round((1 - b / tekPaket) * 100)).padStart(5)}%`); } console.log("-- gezinmede ek indirme --"); for (const [onceki, sonraki] of [ ["/istasyon", "/istasyon/:kimlik"], ["/istasyon/:kimlik", "/istasyon/:kimlik/olcum"], ["/istasyon/:kimlik/olcum", "/istasyon/:kimlik/ayar"], ["/oturum", "/istasyon"], ]) { const inen = new Set([...girisKapanis, ...rotaKapanis[onceki]]); const eksik = [...rotaKapanis[sonraki]].filter((m) => !inen.has(m)); console.log(`${onceki} -> ${sonraki}`.padEnd(52), String(eksik.reduce((t, m) => t + MODUL[m].boyut, 0)).padStart(4), "kB ", eksik.sort().join(", ") || "-"); }
-- parcalar --
giris 62 kB cekirdek, tasarim-sistemi
parca-1 18 kB tablo
isteyen: /istasyon /istasyon/:kimlik /istasyon/:kimlik/olcum
parca-2 15 kB form-dogrulama
isteyen: /oturum /istasyon/:kimlik/ayar
parca-3 12 kB tarih-bicimlendirme
isteyen: /istasyon/:kimlik /istasyon/:kimlik/olcum
parca-4 126 kB disa-aktarma, grafik
isteyen: /istasyon/:kimlik/olcum
parca-5 120 kB harita
isteyen: /istasyon
-- ilk yukleme maliyeti --
rota tek paket bolunmus kazanc
/oturum 353 77 78%
/istasyon 353 200 43%
/istasyon/:kimlik 353 92 74%
/istasyon/:kimlik/olcum 353 218 38%
/istasyon/:kimlik/ayar 353 77 78%
-- gezinmede ek indirme --
/istasyon -> /istasyon/:kimlik 12 kB tarih-bicimlendirme
/istasyon/:kimlik -> /istasyon/:kimlik/olcum 126 kB disa-aktarma, grafik
/istasyon/:kimlik/olcum -> /istasyon/:kimlik/ayar 15 kB form-dogrulama
/oturum -> /istasyon 138 kB harita, tablo
Parça listesi imza gruplamasının sonucudur. Tasarım sistemi her rotada kullanıldığı için giriş parçasına düşmüştür. Tablo modülü üç rotanın ortağıdır ve tek bir parçada durur; üç rotaya kopyalanmadığı gibi, onu istemeyen oturum ekranına da yüklenmez. Dışa aktarma ile grafik modülleri aynı parçada birleşmiştir, çünkü ikisini de yalnızca ölçüm geçmişi ister — ayrı parçalara konmaları fazladan bir istek doğururdu, hiçbir rotaya kazanç getirmezdi.
Ölçülen Kazanç
İkinci tablo bölmenin neden yapıldığını sayıyla söyler. Oturum ekranıyla gelen bir kullanıcı 353 kB yerine 77 kB indirir. Kazanç rotaya göre değişir: haritayı ya da grafiği isteyen rotalarda daha küçüktür, çünkü asıl ağırlık oradadır.
Buradan iki karar çıkar. Ağır modülü bölmek, hafif modülü bölmekten çok daha fazla kazanç sağlar; bölme çabası boyuta bakılarak yöneltilir. Ve kazanç, girişin büyüklüğüyle sınırlıdır: her ziyarette inen 62 kB hiçbir bölmeyle azalmaz. Giriş parçasına gereksiz modül koymak, bölmeyle kazanılanı geri verir.
Bölmenin bir maliyeti de vardır ve tabloda görünmez: her parça ayrı bir istektir. Modüller, Araçlar ve Ekosistem kursundaki paketleme dersinde tanımlanan bu maliyet, çok küçük parçalarda indirilen bayttan büyük olabilir. Ölçüt şudur: bir parça, kaçınılan indirme kadar kazanç sağlamıyorsa ayrılmaz.
Gezinme Başına Maliyet
Üçüncü tablo bölmenin ilk yüklemeden sonraki etkisini gösterir. İstasyon ayrıntısından ölçüm geçmişine geçiş 126 kB’lik yeni bir indirme gerektirir. Kullanıcı bağlantıya bastığı anda bu indirme başlar; bitene kadar ekranda içerik yoktur.
Tek paketle bu bekleme yoktu — kod zaten inmişti. Bölme, ilk yükleme süresini gezinme süresine dağıtır. Bu takas her zaman kârlı değildir: kullanıcıların büyük çoğunluğunun girdiği bir ekranın kodunu geciktirmek, ilk yüklemeden kazanılanı gezinmede fazlasıyla geri verir.
Takası ortadan kaldıran düzenek önceden getirmedir. Bir parça, gerekmeden önce, boşta kalan zamanda indirilir. Yönlendirmenin bunun için iki iyi işareti vardır: ekranda görünür hâle gelen bağlantılar ve kullanıcının üzerine geldiği bağlantı. Tarayıcı ve Web Platformu kursunda tanıtılan kesişim gözlemcisi birincisini, işaretçi olayları ikincisini verir.
Önceden getirme, gerçek isteğin önüne geçmemelidir. Düşük öncelikli yapılır ve boşta çalışma penceresinde tetiklenir; ölçülü ağ bağlantılarında ise hiç yapılmaz. Aksi hâlde kullanıcının o an ihtiyaç duyduğu veriyle yarışır.
Bekleme ve Hata
Bölünmüş bir parçanın yüklenmesi eşzamansız bir işlemdir; dolayısıyla iki yeni durum doğar ve ikisi de arayüzde karşılanmalıdır.
Bekleme durumu, parça inerken ne görüneceğini belirler. Boş bir ekran bırakmak, kullanıcının gezinmenin çalışmadığını düşünmesine yol açar. Doğru davranış, düzen zincirinin korunan bölümünü ekranda tutup yalnız değişen yuvaya bir gösterge koymaktır: kabuk yerinde kaldığı için ekran sarsılmaz. Çok kısa beklemelerde göstergenin görünüp hemen kaybolması da rahatsız edicidir; gösterge küçük bir gecikmeden sonra çıkarılır.
Hata durumu daha kolay unutulur. Ağ kesilmiş olabilir; parça sunucudan silinmiş olabilir. İkinci durum sanıldığından sıradandır: kullanıcı sayfayı açıkken yeni bir sürüm yayımlanırsa, eski sayfanın istediği parça adları artık bulunmaz. Bu, Eşzamansız JavaScript ve Çalışma Zamanı kursundaki Hata ve Yeniden Deneme Kalıpları dersinin doğrudan uygulamasıdır: yükleme başarısız olduğunda bir kez yeniden denenir, yine başarısız olursa kullanıcıya sayfayı yenilemesini öneren bir hata görünümü gösterilir.
Üçüncü bir tuzak koruyucularla kesişir. Yetkisiz bir kullanıcının hiç göremeyeceği bir ekranın parçası indirilmemelidir; koruyucu kararı, parça isteğinden önce verilir. Aksi hâlde bölme, hem gereksiz bayt indirir hem de yetkisiz kullanıcıya o ekranın var olduğunu bildirir.
Özet
- Bir rotanın maliyeti, bağımlılık çizgesinde ondan erişilen tüm modüllerin kapanışıdır.
- Modüller kullanım imzasına — kendilerini isteyen rotalar kümesine — göre gruplanır; aynı imzalı modüller aynı parçaya girer, böylece ne kopyalanır ne de gereksiz indirilir.
- Bölmenin kazancı girişin büyüklüğüyle sınırlıdır ve ağır modüllerde yoğunlaşır; her parça bir istek maliyeti getirdiği için kazanç sağlamayan bölme yapılmaz.
- Bölme, ilk yükleme süresini gezinme süresine dağıtır; boşta zamanda ve düşük öncelikle yapılan önceden getirme bu takası hafifletir.
- Parça yüklemesi eşzamansızdır: bekleme durumunda korunan kabuk ekranda tutulur, hata durumunda yeniden deneme ve açık bir hata görünümü gerekir.
- Koruyucu kararı parça isteğinden önce verilir; görülemeyecek bir ekranın kodu indirilmez.
Sonraki Adım
Yönlendirme tamamlandı: adres hangi ekranın görüneceğini, kime açık olduğunu ve hangi kodun ineceğini belirliyor. Adresin taşıdığı bir durum türü de tanımlandı — süzgeç, sıralama, sayfa. Uygulamanın geri kalan durumu ise hâlâ dağınık: oturum bilgisi koruyucularda, istasyon listesi bir yerde, açık paneller bileşenlerin içinde, sunucudan gelen ölçümler bir başka yerde. Bunların hepsine “durum” demek, aralarındaki en önemli farkı gizler: bazıları uygulamanın kendi ürettiği, bazıları uzaktaki bir kaynağın kopyası. Sonraki konu durumu türlerine ayırmakla başlar ve her türün nereye konacağını belirleyen ölçütleri kurar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.