Ders 19 / 19
Web Dışı Hedefler
Aynı derleme çıktısının masaüstü kabuğu, yerel gövde içine gömülü web görünümü ve uygulama mağazası paketiyle dağıtılması; kırılan kök varsayımının denetlenmesi, köprü yüzeyinin daraltılması ve kabuk ile içerik sürümlerinin ayrışması.
İçindekiler
Buraya kadarki her dağıtım biçimi aynı varsayımı paylaşıyordu: çıktı bir adrese konur, kullanıcı o adresi bir tarayıcıyla açar ve yeni sürüm bir sonraki istekte geçerli olur.
Bu varsayımın geçerli olmadığı hedefler vardır. Aynı derleme çıktısı bir masaüstü kabuğunun içine, bir yerel gövdenin içindeki web görünümüne ya da bir uygulama mağazasından dağıtılan pakete konabilir. Bu ders, hedef değiştiğinde hangi varsayımların kırıldığını ve her kırığın nasıl kapatıldığını ele alır.
Değişen Dört Varsayım
Yükleme kaynağı ve kök. Belge bir HTTP kökünden değil dosya sisteminden yükleniyorsa
/ ile başlayan hiçbir bağ uygulamanın dizinine çözülmez. Kök-göreli her bağ kırılır.
Köken. Yerel bir dosyadan yüklenen belgenin kökeni ya boştur ya benzersizdir. Aynı köken politikası, çerezler, depolama anahtarları ve Service Worker kaydı köken üzerinden tanımlı olduğu için, web’de çalışan bir depolama şeması kabuk içinde başka bir anahtar uzayına düşer ya da hiç kullanılamaz.
Güncelleme kanalı. Web’de yeni sürüm bir sonraki istekte geçerlidir; bir önceki dersin işaretçi değişimi bir milisaniyenin altındaydı. Kabuk tarafında güncelleme ya bir indirme ya bir inceleme sürecidir ve geri alma milisaniyelerle değil günlerle anılır.
Ağ varsayımı. Kabuk çevrimdışı açılabilir. Tarayıcı ve Web Platformu kursundaki çevrimdışı stratejiler burada bir seçenek değil, varsayılan davranıştır.
Bir de sessiz bir ödünleşim değişikliği vardır. Web’de kod bölmenin kazancı ilk boyamayı hızlandırmasıydı; paketlenmiş bir hedefte bütün parçalar zaten yerel diskte durur. Kod bölmenin oradaki karşılığı güncelleme boyutudur: değişmeyen parçalar yeniden indirilmez.
Mutlak Yol Varsayımı
Birinci kırık mekanik olarak saptanabilir ve büyük ölçüde mekanik olarak kapatılabilir.
// yol-denetimi.mjs — cikti agacindaki mutlak yol varsayimlarini bulur ve goreliye cevirir. import { posix } from "node:path"; // Cikti agaci: yol -> govde. Butun baglar kok-goreli yazilmis durumda. const CIKTI = { "belge.html": '<link rel="stylesheet" href="/varlik/istasyon.css">\n' + '<script type="module" src="/varlik/giris.js"></script>\n' + '<img src="/varlik/logo.svg" alt="Kuzey Yamac">\n', "arsiv/2026-03.html": '<link rel="stylesheet" href="/varlik/istasyon.css">\n' + '<script type="module" src="/varlik/giris.js"></script>\n', "varlik/istasyon.css": '.harita { background-image: url("/varlik/harita.svg"); }\n' + '@font-face { font-family: "Olcum"; src: url("/varlik/olcum.woff2") format("woff2"); }\n', "varlik/giris.js": 'import { ciz } from "/varlik/panel.js";\n' + 'export const arka = (ad) => fetch("/veri/" + ad + ".json");\n', }; const KALIP = [ [/((?:href|src)=")(\/[^"]+)(")/g, "isaretleme bagi"], [/(url\(")(\/[^"]+)("\))/g, "bicem url()"], [/(from ")(\/[^"]+)(")/g, "modul belirteci"], ]; // Calisma zamaninda birlestirilen yol: mekanik olarak duzeltilemez. const BIRLESIK = /"(\/[^"]*)"\s*\+/g; function goreli(kaynakYolu, mutlak, modulMu) { const g = posix.relative(posix.dirname(kaynakYolu), mutlak.slice(1)); return modulMu && !g.startsWith(".") ? "./" + g : g; } const bulgu = []; const yeniCikti = {}; for (const [yol, govde] of Object.entries(CIKTI)) { let sonuc = govde; for (const [kalip, tur] of KALIP) { sonuc = sonuc.replace(kalip, (t, on, mutlak, arka) => { const g = goreli(yol, mutlak, tur === "modul belirteci"); bulgu.push([yol, tur, mutlak, g]); return on + g + arka; }); } for (const [, mutlak] of govde.matchAll(BIRLESIK)) bulgu.push([yol, "birlesik yol", mutlak, "ELLE"]); yeniCikti[yol] = sonuc; } console.log("dosya".padEnd(20) + "tur".padEnd(18) + "mutlak".padEnd(24) + "goreli"); for (const [yol, tur, mutlak, g] of bulgu) console.log(yol.padEnd(20) + tur.padEnd(18) + mutlak.padEnd(24) + g); const kalan = Object.entries(yeniCikti) .flatMap(([yol, g]) => [...g.matchAll(/"(\/[^"]+)"/g)].map(([, m]) => yol + " -> " + m)); console.log("\ncevrimden sonra kalan mutlak yol: " + (kalan.join(", ") || "yok")); console.log("\n-- cevrilmis iki dosya --"); for (const yol of ["arsiv/2026-03.html", "varlik/giris.js"]) process.stdout.write(yol + ":\n" + yeniCikti[yol]);
$ node yol-denetimi.mjs
dosya tur mutlak goreli
belge.html isaretleme bagi /varlik/istasyon.css varlik/istasyon.css
belge.html isaretleme bagi /varlik/giris.js varlik/giris.js
belge.html isaretleme bagi /varlik/logo.svg varlik/logo.svg
arsiv/2026-03.html isaretleme bagi /varlik/istasyon.css ../varlik/istasyon.css
arsiv/2026-03.html isaretleme bagi /varlik/giris.js ../varlik/giris.js
varlik/istasyon.css bicem url() /varlik/harita.svg harita.svg
varlik/istasyon.css bicem url() /varlik/olcum.woff2 olcum.woff2
varlik/giris.js modul belirteci /varlik/panel.js ./panel.js
varlik/giris.js birlesik yol /veri/ ELLE
cevrimden sonra kalan mutlak yol: varlik/giris.js -> /veri/
-- cevrilmis iki dosya --
arsiv/2026-03.html:
<link rel="stylesheet" href="../varlik/istasyon.css">
<script type="module" src="../varlik/giris.js"></script>
varlik/giris.js:
import { ciz } from "./panel.js";
export const arka = (ad) => fetch("/veri/" + ad + ".json");
Çıktının söylediği üç şey var.
Göreli yol dosyanın kendi konumuna bağlıdır. Aynı biçem dosyası kökteki belgeden
varlik/istasyon.css, alt dizindeki belgeden ../varlik/istasyon.css olur. Çevrim, dosya
başına yapılmak zorundadır.
Modül belirteci ayrı bir kural ister. panel.js biçiminde bir belirteç çıplak
belirteçtir ve modül yükleyicisi onu bir dosya olarak çözmez; ./ öneki zorunludur.
Çalışma zamanında birleştirilen yol mekanik olarak düzeltilemez. Son satırda kalan
/veri/ öneki, adres bir değişkenle birleştirildiği için hiçbir dizgi çevrimiyle
yakalanamaz. Bu tür yolların tek çözümü, tabanın tek bir yerde hesaplanmasıdır: bir modül
kendi konumunu import.meta.url üzerinden çözer ve bütün çalışma zamanı adresleri bu tabana
göre kurulur. Denetimin işi, bu kuralın ihlallerini derlemede bulmaktır.
Köprü Yüzeyi
İkinci kırık bir yol sorunu değil, bir yetki sorunudur. Kabuğun web görünümüne açtığı işlev
kümesi — köprü (bridge) — uygulamanın yerel yetki yüzeyidir. Kural tek cümledir: köprüde
açılan bir yetenek, açtığı yetkinin tamamını açar. dosyaOku(yol), “bir dosya okuma”
yeteneği değil, “her dosyayı okuma” yeteneğidir.
// kopru.mjs — kabuk ile web gorunumu arasindaki koprunun yuzey denetimi. // Kullanim: node kopru.mjs genis | node kopru.mjs dar const AGIRLIK = { yok: 0, "kullanici secimi": 1, "uygulama dizini": 2, sinirsiz: 100 }; const YUZEY = { genis: { uzakIcerik: true, // gorunume ucuncu taraf icerigi gomuluyor kokenDenetimi: false, // kopru cagrisinin hangi kokenden geldigi sinanmiyor yetenek: [ { ad: "dosyaOku(yol)", kapsam: "sinirsiz", dogrulama: "yok" }, { ad: "kabukCalistir(komut)", kapsam: "sinirsiz", dogrulama: "yok" }, { ad: "agIstegi(adres)", kapsam: "sinirsiz", dogrulama: "yok" }, { ad: "bildirimGoster(metin)", kapsam: "yok", dogrulama: "tur" }, ], }, dar: { uzakIcerik: false, kokenDenetimi: true, yetenek: [ { ad: "olcumDosyasiSec()", kapsam: "kullanici secimi", dogrulama: "tur" }, { ad: "arsiveYaz(kayit)", kapsam: "uygulama dizini", dogrulama: "sema" }, { ad: "bildirimGoster(metin)", kapsam: "yok", dogrulama: "tur" }, ], }, }; function denetle(ad) { const y = YUZEY[ad]; const bulgu = []; console.log("yuzey: " + ad); console.log(" " + "yetenek".padEnd(24) + "kapsam".padEnd(20) + "dogrulama".padEnd(12) + "agirlik"); let toplam = 0; for (const c of y.yetenek) { toplam += AGIRLIK[c.kapsam]; console.log(" " + c.ad.padEnd(24) + c.kapsam.padEnd(20) + c.dogrulama.padEnd(12) + AGIRLIK[c.kapsam]); if (c.kapsam === "sinirsiz") bulgu.push(["K1", c.ad, "kapsam sinirsiz"]); if (c.dogrulama === "yok") bulgu.push(["K2", c.ad, "parametre dogrulanmiyor"]); } if (y.uzakIcerik && !y.kokenDenetimi) bulgu.push(["K3", "(yuzey)", "uzak koken icerigi var, kopru koken denetimi yapmiyor"]); for (const [kural, hedef, ileti] of bulgu) console.log(" " + kural + " " + hedef.padEnd(24) + ileti); console.log(" yuzey agirligi: " + toplam + " ihlal: " + bulgu.length); return bulgu.length; } process.exitCode = denetle(process.argv[2]) > 0 ? 1 : 0;
#!/usr/bin/env bash # Iki kopru yuzeyini de denetler ve cikis kodlarini yazar. node kopru.mjs genis; echo "cikis kodu: $?" node kopru.mjs dar; echo "cikis kodu: $?"
yuzey: genis yetenek kapsam dogrulama agirlik dosyaOku(yol) sinirsiz yok 100 kabukCalistir(komut) sinirsiz yok 100 agIstegi(adres) sinirsiz yok 100 bildirimGoster(metin) yok tur 0 K1 dosyaOku(yol) kapsam sinirsiz K2 dosyaOku(yol) parametre dogrulanmiyor K1 kabukCalistir(komut) kapsam sinirsiz K2 kabukCalistir(komut) parametre dogrulanmiyor K1 agIstegi(adres) kapsam sinirsiz K2 agIstegi(adres) parametre dogrulanmiyor K3 (yuzey) uzak koken icerigi var, kopru koken denetimi yapmiyor yuzey agirligi: 300 ihlal: 7 cikis kodu: 1 yuzey: dar yetenek kapsam dogrulama agirlik olcumDosyasiSec() kullanici secimi tur 1 arsiveYaz(kayit) uygulama dizini sema 2 bildirimGoster(metin) yok tur 0 yuzey agirligi: 3 ihlal: 0 cikis kodu: 0
İki yüzey aynı işleri yapar; ağırlıkları arasında yüz kat fark vardır. Farkı üç kural üretir.
Kapsam, yeteneğin kendisine gömülür. dosyaOku(yol) yerine olcumDosyasiSec() konur:
ikincisinde dosyayı kullanıcı seçer ve seçilen tek nesne dışında hiçbir şey açılmaz. Yetki,
parametreyle değil işlevin tanımıyla sınırlanır.
Parametre şemayla doğrulanır. Köprüye gelen değer, web tarafından geldiği için doğrulanmamış girdidir. Doğrulama kabuk tarafında yapılır; web tarafındaki denetim yalnız kullanıcı deneyimi içindir.
Köken sınanır. Görünüme üçüncü taraf içeriği giriyorsa — gömülü bir harita, bir çerçeve — köprü o içeriğe de açıktır. Köken denetimi yoksa, uygulamanın yerel yetkileri o içeriğin eline geçmiş demektir. Uzak içerik yükleyen görünümlerde doğru karar köprüyü hiç bağlamamak ve o içeriği ayrı bir görünümde tutmaktır.
Güncelleme Kanalı ve Sürüm Ayrışması
Üçüncü kırık, iki bileşenin ayrı kanallardan güncellenmesinden doğar. Kabuk mağaza kanalından, web içeriği kendi kanalından gelir; ikisi aynı anda güncellenmez.
// surum.mjs — kurulu kabuk surumleri ile icerik surumunun erisebildigi pay. // Semantik surumleme: karsilastirma sayi ucluleri uzerinden yapilir. const KURULU = { "1.2.0": 0.08, "1.3.0": 0.14, "2.0.0": 0.33, "2.1.0": 0.31, "3.0.0": 0.14, }; const ICERIK = [ { surum: "2026.02", enAzKabuk: "1.2.0" }, { surum: "2026.03", enAzKabuk: "2.0.0" }, { surum: "2026.04", enAzKabuk: "3.0.0" }, ]; const parcala = (s) => s.split(".").map(Number); const enAzMi = (kabuk, gereken) => { const a = parcala(kabuk), b = parcala(gereken); for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i]; return true; }; console.log("kurulu kabuk dagilimi: " + Object.entries(KURULU).map(([s, p]) => s + " %" + (100 * p).toFixed(0)).join(", ")); console.log("\n" + "icerik surumu".padEnd(16) + "en az kabuk".padEnd(14) + "erisilen pay".padStart(13) + "eski icerikte kalan".padStart(21)); for (const { surum, enAzKabuk } of ICERIK) { const pay = Object.entries(KURULU) .filter(([k]) => enAzMi(k, enAzKabuk)) .reduce((t, [, p]) => t + p, 0); console.log(surum.padEnd(16) + enAzKabuk.padEnd(14) + ("%" + (100 * pay).toFixed(0)).padStart(13) + ("%" + (100 * (1 - pay)).toFixed(0)).padStart(21)); }
$ node surum.mjs kurulu kabuk dagilimi: 1.2.0 %8, 1.3.0 %14, 2.0.0 %33, 2.1.0 %31, 3.0.0 %14 icerik surumu en az kabuk erisilen pay eski icerikte kalan 2026.02 1.2.0 %100 %0 2026.03 2.0.0 %78 %22 2026.04 3.0.0 %14 %86
En az kabuk gereksinimini bir sürüm yükseltmek erişilen payı yüzde 100’den 78’e, bir sürüm daha yükseltmek 14’e indirir. Kurulu sürüm dağılımının ilerleme hızı mağaza kanalının ve kullanıcının güncelleme davranışının işidir; uygulamanın denetiminde değildir. Buradan iki kural çıkar.
İçerik sürümü kabuk sürümünün gerisinden gelir. Yeni bir köprü yeteneğine dayanan içerik, o yeteneği taşıyan kabuk yeterince yayılana kadar yayınlanmaz. Aradaki süre boyunca içerik yeteneği saptayarak çalışır ve bulamazsa eski yolu kullanır.
Uyum iki yönlüdür. Yeni kabuk eski içeriği, eski kabuk da bir süre yeni içeriği çalıştırabilmelidir. Köprü işlevlerinin imzaları bu yüzden geriye dönük uyumlu değişir; bir imzanın değişmesi, o kabuğa düşen bütün eski içeriği kırar.
Geri alma da burada ayrışır. İçerik ağdan geliyorsa bir önceki dersin işaretçisi geçerlidir ve geri alma saniyeler alır. Kabuk tarafında geri alma yoktur: yayımlanmış bir sürüm kanaldan çekilse bile kurulu olanlar kurulu kalır. Kabuk sürümü, dördüncü dersin geri alınamayan değişiklikler sınıfına aittir ve bu yüzden kabuğa konan mantık en aza indirilir — değişme olasılığı olan her şey içerik tarafında tutulur.
Özet
- Hedef değiştiğinde dört varsayım kırılır: yükleme kökü, köken, güncelleme kanalı ve ağın varlığı. Kod bölmenin kazancı ilk boyamadan güncelleme boyutuna kayar.
- Kök-göreli bağlar dosya başına göreli bağlara çevrilir; modül belirteçleri
./öneki ister, çalışma zamanında birleştirilen yollar tek bir tabandan çözülmelidir. - Köprüde açılan her yetenek, açtığı yetkinin tamamını açar; kapsam yeteneğin tanımına gömülür, parametre kabuk tarafında şemayla doğrulanır, köken sınanır.
- Örnekte aynı işleri gören iki köprü yüzeyi arasında yüz kat ağırlık farkı vardır; uzak içerik yükleyen bir görünüme köprü bağlanmaz.
- Kurulu kabuk dağılımı uygulamanın denetiminde değildir: en az kabuk gereksinimini bir sürüm yükseltmek erişilen payı yüzde 100’den 78’e indirir.
- Kabuk sürümü geri alınamaz; değişme olasılığı olan mantık içerik tarafında tutulur ve köprü imzaları geriye dönük uyumlu değişir.
Kurs Kapanışı
Bu kurs tek bir soruyla başladı: bir arayüzün işaretlemesi nerede ve ne zaman üretilir? Oluşturma modelleri bu soruyu altı ayrı yanıtla ölçtü — istemci, sunucu, derleme zamanı, artımlı yeniden üretim, akışlı gönderim ve kenar — ve yanıtların hiçbirinin genel olarak üstün olmadığını gösterdi; seçim içerik tazeliği, kişiselleştirme ve ölçek eksenlerinde yapılan bir karardır.
Derleme ve paketleme konusu bu kararın çıktısını üretti: bağımlılık çizgesinden parçalar, kullanılmayan kodun elenmesi, varlık boru hattı, içeriğe bağlı adlar, geliştirme kipinin ayrı ekonomisi ve çıktıyı ortamına bağlayan derleme zamanı değişkenleri. O konunun son dersi tek bir sınır kurdu ve bu kurs boyunca geçerli kaldı: istemci paketine giren her değer herkese açıktır.
Dağıtım konusu çıktıyı kullanıcıya ulaştırdı. Statik barındırma her isteği bir dosyaya eşledi ve önbellek politikasının istek isabetine çevrimini ölçtü; sunucu gerektiren dağıtım üç barındırma biçimini ayrılmış kapasite ve soğuk başlatma eksenlerinde ayırdı; önizleme dağıtımları her değişikliğe kendi ortamını verdi; aşamalı yayın riski trafiğin payıyla sınırladı; bu ders de aynı çıktının web dışı hedeflerde hangi varsayımları kaybettiğini gösterdi.
Ortak bir yöntem üç konuyu da birbirine bağlar: her karar bir ölçüye bağlandı. İlk bayt süresi, yeniden inen bayt, isabet oranı, ayrılmış örnek-saniye, saptama süresi, etkilenen kullanıcı sayısı. Ölçüye bağlanmayan bir dağıtım kararı, savunulabilir bir karar değildir.
Geriye ölçmenin kendisi kalıyor. Uygulama artık üretiliyor, paketleniyor ve yayınlanıyor; sıra yayınlananın nitelikte olup olmadığını sınamakta. M14/K08 · Frontend Kalitesi kursu bunu dört başlıkta yapar: başarım bütçesi tanımlamak ve ölçümle doğrulamak, erişilebilirlik ölçütlerini standart düzeyinde uygulamak, istemci tarafındaki güvenlik risklerini savunma katmanlarıyla azaltmak ve bütün bunları yayına girmeden yakalayan bir test düzeni kurmak. Bu kursta kurulan sürekli tümleştirme hattı, oradaki bütçelerin ve ölçütlerin uygulanacağı yer olacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.