TypeScript'te Service Worker
Bu makale, TypeScript'te Service Worker’ları açıklar.
TypeScript'te Service Worker’ları, pratik örneklerle birlikte açıklayacağız.
YouTube Video
TypeScript'te Service Worker
Bir Service Worker, tarayıcı ile ağ arasında yer alan bir 'istek vekili'dir. Fetch yakalama, önbellek kontrolü, çevrimdışı destek ve arka plan işlemleri (senkronizasyon ve push) sağlar. TypeScript kullanmak, tip güvenliği sağlar ve bakım kolaylığını artırır.
TypeScript Kurulumu
tsconfig.json (WebWorker türlerini etkinleştir)
tsconfig.json dosyasında WebWorker türünü etkinleştirme örneğine bakalım.
1{
2 "compilerOptions": {
3 "target": "ES2020",
4 "module": "ES2020",
5 "lib": ["ES2020", "WebWorker"],
6 "moduleResolution": "Bundler",
7 "strict": true,
8 "noEmitOnError": true,
9 "outDir": "out",
10 "skipLibCheck": true
11 },
12 "include": ["sw.ts"]
13}libdizisineWebWorkerekleyerekServiceWorkerGlobalScopegibi türleri kullanabilirsiniz.DOMveWebWorkerfarklı türlere sahiptir, bu nedenle tarayıcı (ana uygulama) veService Workeriçintsconfig.jsonayarlarını ayırmak yaygın bir uygulamadır.Service Workerdosyaları nihayetinde scope ile eşleşen bir yola (genellikle site kök dizini/sw.js) çıktı olarak verilir.- Güvenlik nedeniyle, Service Worker’lar yalnızca HTTPS (veya
localhost) üzerinden çalışır.
Tarayıcı Tarafında Kayıt Kodu
register-sw.ts
1// register-sw.ts
2async function registerServiceWorker() {
3 if (!('serviceWorker' in navigator)) return;
4
5 try {
6 const registration = await navigator.serviceWorker.register(
7 '/sw.js', { scope: '/' }
8 );- Bu işlem bir
Service Workerkaydeder.scope,Service Workerın denetleyebileceği yolların aralığını ifade eder. Örneğin,/sw.jsdosyasını doğrudan kök dizine yerleştirir vescopeu kök dizin (/) olarak ayarlarsanız, sitenin tüm kaynaklarını kontrol edebilirsiniz. Öte yandan,/app/gibi belirli bir dizin belirtirseniz, yalnızca o dizin altındaki içerikler kontrol altında olacaktır.
1 // If there's a waiting worker, notify the user.
2 if (registration.waiting) {
3 promptUserToUpdate(registration);
4 }waiting, yeni bir Service Worker'ın kurulduğu ve etkinleştirilmek için beklediği durumunu gösterir. Bu aşamada, mevcut sayfalar hâlâ eskiService Workertarafından kontrol edilir, bu nedenle genellikle kullanıcıdan onay istenir ve onay alındıktan sonra yeniService Workerı hemen etkinleştirmek içinskipWaiting()çağrılır. Bu, bir sonraki sayfa yenilemesini beklemeden en son işlemleri yansıtmanıza olanak tanır.
1 // When a new SW is installing, monitor its state changes
2 registration.addEventListener('updatefound', () => {
3 const newWorker = registration.installing;
4 if (!newWorker) return;
5 newWorker.addEventListener('statechange', () => {
6 if (newWorker.state === 'installed' &&
7 navigator.serviceWorker.controller) {
8 // New content available, prompt the user
9 promptUserToUpdate(registration);
10 }
11 });
12 });updatefound, yeni bir Service Worker yüklemesi başladığında tetiklenir. Bu olay meydana geldiğinde,registration.installingiçine yeni bir worker atanır, bu nedenle onunstatechangedeğişikliğini izleyerek yüklemenin ne zaman tamamlandığını (installed) tespit edebilirsiniz. Ayrıca,navigator.serviceWorker.controllermevcutsa, bu sayfanın zaten eski bir Service Worker tarafından kontrol edildiği anlamına gelir, bu nedenle kullanıcıya yeni bir sürümün mevcut olduğu bildirilmelidir.
1 // When the active worker changes (e.g., after skipWaiting), reload if desired
2 navigator.serviceWorker.addEventListener('controllerchange', () => {
3 // Optionally reload to let the new SW take over
4 window.location.reload();
5 });
6 } catch (err) {
7 console.error('Service Worker registration failed: ', err);
8 }
9}controllerchangeolayı, yeni Service Worker mevcut sayfayı kontrol etmeye başladığı anda tetiklenir. Bu noktada sayfanın yenilenmesi, yeni önbellek stratejilerini ve işlemleri hemen uygular. Ancak, otomatik yenileme kullanıcı deneyimini bozabilir; bu nedenle kullanıcının onayını aldıktan sonra sayfanın yenilenmesi tercih edilir.
1function promptUserToUpdate(reg: ServiceWorkerRegistration) {
2 // Show UI to user. If user accepts:
3 if (reg.waiting) {
4 reg.waiting.postMessage({ type: 'SKIP_WAITING' });
5 }
6}
7
8registerServiceWorker();Service Workerın istemcidenpostMessage({ type: 'SKIP_WAITING' })mesajı alması ve ardındanself.skipWaiting()çağrısıyla güncellemeyi hemen başlatmasını sağlayabilirsiniz.
sw.ts'de Scope Bildirimi
Şimdi, uygulama kabuğu önbelleklemesi uygulayan tipik bir Service Worker örneğine bakalım.
TypeScript'te Service Worker kullanırken, self'e doğru türü atamak faydalıdır.
1// sw.ts
2export default null;
3declare const self: ServiceWorkerGlobalScope;- TypeScript'te
selfvarsayılan olarakanyolarak kabul edilir, bu yüzden ek tip tanımlamaları olmadan,skipWaiting()veyaclientsgibi Service Worker'a özgü API'ler için otomatik tamamlama veya tür kontrolü alamazsınız. ServiceWorkerGlobalScopebelirterek otomatik tamamlama elde edersiniz, hatalı kullanım engellenir ve normal DOM betiklerinden ayrılmış daha güvenli bir geliştirme yapabilirsiniz.
Temel Service Worker (Kurulum/Aktif Etme/Getirme)
Bu örnek, basit önbellek sürüm yönetimini, kurulumda önbelleğe alma, etkinleştirmede eski önbellekleri silme ve getirme sırasında önbellek stratejilerini (statik dosyalar için önbellek-öncelikli, API'ler için ağ-öncelikli) gösterir.
sw.ts (Minimum Kurulum + Önbellek İskeleti)
1const CACHE_NAME = 'app-shell-v1';
2const STATIC_ASSETS = [
3 '/',
4 '/index.html',
5 '/styles.css',
6 '/main.js',
7 '/fallback.png'
8];
9
10self.addEventListener('install', (event: ExtendableEvent) => {
11 // Pre-cache application shell
12 event.waitUntil(
13 caches.open(CACHE_NAME)
14 .then(cache => cache.addAll(STATIC_ASSETS))
15 // Activate immediately (optional: coordinate with client)
16 .then(() => self.skipWaiting())
17 );
18});installolayı sırasında, uygulamanın statik kaynakları (App Shell) önbelleğe alınır.self.skipWaiting()fonksiyonu çağrılarak, yeniService Workerhemen etkinleştirilir ve bir sonraki erişimi beklemeden en güncel önbellek kullanılabilir hale gelir.
1self.addEventListener('activate', (event: ExtendableEvent) => {
2 // Clean up old caches and take control of clients immediately
3 event.waitUntil(
4 caches.keys().then(keys =>
5 Promise.all(keys
6 .filter(key => key !== CACHE_NAME)
7 .map(key => caches.delete(key)))
8 ).then(() => self.clients.claim())
9 );
10});activateolayı sırasında, eski önbellek sürümleri silinir veService Workergüncel tutulur. Ayrıca,self.clients.claim()fonksiyonu çağrılarak, yeniService Workersayfa yeniden yüklenmeden tüm istemcileri kontrol edebilir.
1self.addEventListener('fetch', (event: FetchEvent) => {
2 const request = event.request;
3 const url = new URL(request.url);
4
5 // Navigation requests (SPA) -> network-first with fallback to cached index.html
6 if (request.mode === 'navigate') {
7 event.respondWith(
8 fetch(request).catch(() => caches.match('/index.html') as Promise<Response>)
9 );
10 return;
11 }
12
13 // Simple API routing: network-first for /api/
14 if (url.pathname.startsWith('/api/')) {
15 event.respondWith(networkFirst(request));
16 return;
17 }
18
19 // Static assets: cache-first
20 event.respondWith(cacheFirst(request));
21});fetchaşamasında, istekleri yakalayıp cevabı kontrol edebilirsiniz. Çevrimdışı destek ve performans için faydalı olan önbellek-öncelikli veya ağ-öncelikli gibi stratejiler uygulayabilirsiniz.
1self.addEventListener('message', (event: ExtendableMessageEvent) => {
2 const data = (event as any).data;
3 if (!data) return;
4
5 if (data.type === 'SKIP_WAITING') {
6 // Force the waiting service worker to become active
7 self.skipWaiting();
8 }
9});SKIP_WAITINGmesajı alındığında,self.skipWaiting()çağrısı ile beklemedeki Service Worker'ı anında etkinleştirebilirsiniz. Sonuç olarak, sayfayı yeniden yüklemeye gerek olmadan yeni sürüm bir sonraki istekten itibaren uygulanacaktır.
Pratik Önbellek Stratejisi Özeti
cache-first
Önce önbellek (Cache-first) önce önbelleği kontrol eder ve varsa hemen yanıt döndürür. Yoksa, ağdan getirir ve sonucu önbelleğe kaydeder. Bu, statik dosyalar için uygundur.
1async function cacheFirst(request: Request): Promise<Response> {
2 const cache = await caches.open(CACHE_NAME);
3 const cached = await cache.match(request);
4 if (cached) {
5 return cached;
6 }
7 const response = await fetch(request);
8 if (response && response.ok) {
9 cache.put(request, response.clone());
10 }
11 return response;
12}- Bu kod, önbellek-öncelikli bir uygulamayı gösterir. Önbellekte varsa döndürür; yoksa ağdan getirip önbelleğe kaydeder. Nadiren değişen resim veya CSS gibi statik kaynaklar için uygundur.
network-first
Önce ağ (Network-first) önce ağı dener ve başarısız olursa önbelleğe döner. Bu, tazelik gerektiren API’ler için uygundur.
1async function networkFirst(request: Request): Promise<Response> {
2 const cache = await caches.open(CACHE_NAME);
3 try {
4 const response = await fetch(request);
5 if (response && response.ok) {
6 cache.put(request, response.clone());
7 }
8 return response;
9 } catch (err) {
10 const cached = await cache.match(request);
11 if (cached) return cached;
12 return new Response(JSON.stringify({ error: 'offline' }), {
13 status: 503,
14 headers: { 'Content-Type': 'application/json' }
15 });
16 }
17}- Bu kod, ağ-öncelikli bir uygulamayı gösterir. Ağdan yanıt alınırsa önbelleğe kaydeder; başarısız olursa önbellektekini döndürür. Haberler veya API yanıtları gibi güncel veri gerektiren kaynaklar için uygundur.
stale-while-revalidate
stale-while-revalidate önce önbelleği döndürür ve aynı anda arka planda ağı kullanarak önbelleği günceller. Bu, yanıt hızı ile tazeliği dengeler.
1async function staleWhileRevalidate(request: Request, cacheName = CACHE_NAME): Promise<Response> {
2 const cache = await caches.open(cacheName);
3 const cachedResponse = await cache.match(request);
4 const networkFetch = fetch(request).then(networkResponse => {
5 if (networkResponse && networkResponse.ok) {
6 cache.put(request, networkResponse.clone());
7 }
8 return networkResponse;
9 }).catch(() => undefined);
10
11 // Return cached immediately if exists, otherwise wait network
12 return cachedResponse || (await networkFetch) || new Response('offline', { status: 503 });
13}- Bu kod, önbellekte varsa hemen döndürürken arka planda ağdan yeni veriyi alır ve önbelleği günceller. Kullanıcıya hızlı yanıt sağlar ve bir sonraki erişimde güncellenmiş içeriği kullanarak UI veya hafif veri aktarımı için uygun hale getirir.
Güncelleme Akışını Optimize Etme (Güncelleme Bildirimi ve Güvenli Yeniden Yükleme)
Service Worker güncellemeleri hemen devreye girmez; yeni sürüm, mevcut sekmeler kapatılıncaya kadar beklemede kalır.
Burada, yeni sürüm hazır olduğunda istemciye bildiren ve kullanıcı eylemine göre sayfayı güvenle yeniden yükleyen bir sistem uyguluyoruz.
Yeni sürüm hazır olduğunda istemciyi Service Worker tarafından bilgilendirin.
1// In sw.ts: after 'activate' or when new version is ready, broadcast a message
2async function notifyClientsUpdated() {
3 const all = await self.clients.matchAll({ type: 'window' });
4 for (const client of all) {
5 client.postMessage({ type: 'SW_UPDATED' });
6 }
7}
8
9// e.g., call this at the end of 'activate'
10self.addEventListener('activate', (event) => {
11 event.waitUntil((async () => {
12 if ('navigationPreload' in self.registration) {
13 await self.registration.navigationPreload.enable();
14 }
15 // cache cleanup
16 const cacheNames = await caches.keys();
17 await Promise.all(
18 cacheNames.map((name) => {
19 if (name !== CACHE_NAME) {
20 return caches.delete(name);
21 }
22 })
23 );
24
25 await self.clients.claim();
26 await notifyClientsUpdated();
27 })());
28});- Bu örnekte,
activateolayının sonundanotifyClientsUpdatedçağrılarak tüm bağlı istemcilere yeni sürümün hazır olduğu bildirilir.clients.claim(), şu anda açık olan sayfaları (istemcileri) yeni etkinleştirilen Service Worker'ın kontrolüne anında almayı sağlayan bir yöntemdir. Normalde, birService Workersayfayı yalnızca bir sonraki yükleme sırasında kontrol etmeye başlar, ancakclients.claim()kullanarak sayfayı yeniden yüklemeden hemen kontrol altına alabilirsiniz.
İstemcide güncelleme arayüzünü gösterin ve kullanıcı işlemiyle yeniden yükleyin
1// in app startup
2navigator.serviceWorker.addEventListener('message', (e) => {
3 if (e.data?.type === 'SW_UPDATED') {
4 // Show a non-intrusive toast or banner: "New version available"
5 // When user clicks "Reload", call:
6 window.location.reload();
7 }
8});- İstemci,
messageolayıylaSW_UPDATEDalır ve kullanıcı arayüzünde bir güncelleme bildirimi gösterir. Kullanıcı yeniden yüklemeyi seçtiğinde,window.location.reload()çalıştırılır ve sayfadaki eski HTML, CSS ve diğer kaynaklar en son sürüme güncellenir. Bu, önbelleğin veclients.claim()ile devreye alınanService Workerkontrolünün tüm sayfada geçerli olmasını sağlar.
Çevrimdışı Yedek Sayfa
Kritik gezinme için /offline.html hazırlayın ve resim ya da font olmadan da anlamı ileten minimal bir arayüz sunun. Bir API çağrısı başarısız olursa, mümkünse son önbellek durumunu gösterin ve arka planda tekrar getirmeye çalışarak kullanıcı deneyimini artırın.
Uygulama Örneği
1// sw.ts
2const CACHE_NAME = 'app-cache-v1';
3
4// Cache offline.html during install
5self.addEventListener('install', (event) => {
6 event.waitUntil((async () => {
7 const cache = await caches.open(CACHE_NAME);
8 await cache.addAll(['/offline.html']);
9 })());
10});
11
12// Handle fetch requests
13self.addEventListener('fetch', (event) => {
14 const request = event.request;
15
16 // Navigation requests (e.g., page transitions)
17 if (request.mode === 'navigate') {
18 event.respondWith((async () => {
19 try {
20 // Try to fetch from the network as usual
21 return await fetch(request);
22 } catch (err) {
23 // On failure, return offline fallback page
24 const cache = await caches.open(CACHE_NAME);
25 return await cache.match('/offline.html') as Response;
26 }
27 })());
28 }
29});installaşamasında/offline.htmlönbelleğe alın, böylece ağ yoksa en azından minimal bir sayfa döndürebilirsiniz.fetcholayında,request.mode === 'navigate'ile yönlendirme isteklerini izleyebilir ve özellikle sayfa geçişlerini hedefleyebilirsiniz.- Ağ başarısız olduğunda
/offline.html’e yönlendirin, böylece çevrimdışıyken de gösterilir.
İstemci ile Service Worker arasındaki mesajlaşma
Service Worker sayfa yaşam döngüsünden bağımsız çalıştığı için, durumları bildirmek ve komutları yürütmek için çift yönlü mesajlaşma önemlidir. Mesajlar için tip belirtmek, yanlış mesaj göndermeyi önler, kod tamamlama sağlar ve uygulamanızın daha sağlam olmasına yardımcı olur.
Kod Örneği
- Mesaj Türlerinin Tanımı
1type SwToClient =
2 | { type: 'SW_READY' }
3 | { type: 'SW_UPDATED' }
4 | { type: 'CACHE_CLEARED' }
5 | { type: 'PING'; ts: number };
6
7type ClientToSw =
8 | { type: 'CLEAR_CACHE' }
9 | { type: 'PING'; ts: number };SwToClient, Service Worker'dan istemciye gönderilen mesajın türüdür.ClientToSw, istemciden Service Worker'a gönderilen mesajın türüdür.- Bu, çift yönlü iletişim yoluyla hangi türde olayların değiş tokuş edilebileceğini netleştirmenizi sağlar.
- Service Worker Tarafında İşleme
1self.addEventListener('message', (event) => {
2 const data = event.data as ClientToSw;
3 if (data?.type === 'CLEAR_CACHE') {
4 event.waitUntil((async () => {
5 const keys = await caches.keys();
6 await Promise.all(keys.map((k) => caches.delete(k)));
7 await broadcast({ type: 'CACHE_CLEARED' });
8 })());
9 } else if (data?.type === 'PING') {
10 event.source?.postMessage({ type: 'PING', ts: data.ts } as SwToClient);
11 }
12});- Service Worker, istemciden mesajları alır ve türüne göre işlem dallandırır.
CLEAR_CACHEiçin, önbelleği siler ve ardından tüm istemcilereCACHE_CLEAREDile bildirir.PINGiçin, orijinal istemciye zaman damgası içeren birPINGmesajıyla yanıt verir.
- Tüm İstemcileri Service Worker'dan Bildirme
1async function broadcast(msg: SwToClient) {
2 const clients = await self.clients.matchAll({ includeUncontrolled: true });
3 for (const c of clients) c.postMessage(msg);
4}- Tüm pencere sekmelerini almak için
clients.matchAllkullanın. - Her birine
postMessagegöndererek mesajı yayınlayabilirsiniz. - Bu, güncelleme bildirimleri (
SW_UPDATEDgibi) ve hata bildirimleri için kullanılabilir.
- İstemci Tarafında İşleme
1navigator.serviceWorker.controller?.postMessage({
2 type: 'PING',
3 ts: Date.now()
4} as ClientToSw);- İstemciden bir
PINGgönderip,Service Worker'dan yanıt alarak çift yönlü iletişimin düzgün çalıştığını doğrulayabilirsiniz. Bu yöntem, bağlantı durumlarını ve mesaj işleyişini test etmeyi kolaylaştırır.
1navigator.serviceWorker.addEventListener('message', (e) => {
2 const msg = e.data as SwToClient;
3 switch (msg.type) {
4 case 'SW_READY':
5 console.log('Service Worker is ready');
6 // Example: hide loading spinner or enable offline UI
7 break;
8 case 'SW_UPDATED':
9 console.log('A new version of the Service Worker is available');
10 // Example: show update notification or reload prompt
11 const shouldReload = confirm('A new version is available. Reload now?');
12 if (shouldReload) {
13 window.location.reload();
14 }
15 break;
16 case 'CACHE_CLEARED':
17 console.log('Cache cleared');
18 // Example: show confirmation message to user
19 alert('Cache has been successfully cleared.');
20 break;
21 case 'PING':
22 console.log(`Received PING response, ts=${msg.ts}`);
23 break;
24 }
25});- {^ i18n_speak
クライアント側では
Service Workerから送信されるメッセージを受信し、種類に応じて処理を分岐します。SW_READYは初期化完了、SW_UPDATEDは新バージョン検出、CACHE_CLEAREDはキャッシュ削除完了、PINGは通信確認を示します。各メッセージに応じて、UI の更新やリロード、通知表示などを行います。^}
Tipli Mesajlaşmanın Faydaları
- Tipli mesajlar kullanmak, hangi mesajların gönderilip alınabileceğini netleştirir ve otomatik tamamlama ile tip kontrolü güvenliği artırır.
postMessage, birebir iletişim;broadcast, birden çoğa iletişim sağlar.- Güncelleme bildirimleri (
SW_UPDATED), önbellek yönetimi (CACHE_CLEARED) ve sağlık denetimleri (PING) gibi temel özellikleri kolayca uygulayabilirsiniz.
Özet
- TypeScript kullanmak, Service Worker API çağrılarına ve mesajlaşmaya tip güvenliği ekler; bu da geliştirme verimliliğini ve bakım kolaylığını büyük ölçüde artırır.
install,activatevefetchyaşam döngüsü olaylarını anlamak ve her durum için doğru önbellekleme stratejisini (cache-first veya network-first gibi) seçmek, daha iyi bir kullanıcı deneyimi sağlar.- Operasyonlar için, önbellek sürüm yönetimini ve güncelleme akışlarını (
updatefound,waiting,SKIP_WAITINGvb.) anlamak çok önemlidir. - İstemci-
Service Workeriletişimi için tipli mesajlaşma kullanarak yanlış uygulamaları önleyebilir ve uzun vadede kolayca genişletilip bakım yapılabilecek bir sistem oluşturabilirsiniz.
Yukarıdaki makaleyi, YouTube kanalımızda Visual Studio Code'u kullanarak takip edebilirsiniz. Lütfen YouTube kanalını da kontrol edin.