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}
lib
dizisineWebWorker
ekleyerekServiceWorkerGlobalScope
gibi türleri kullanabilirsiniz.DOM
veWebWorker
farklı türlere sahiptir, bu nedenle tarayıcı (ana uygulama) veService Worker
içintsconfig.json
ayarlarını ayırmak yaygın bir uygulamadır.Service Worker
dosyaları 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 Worker
kaydeder.scope
,Service Worker
ın denetleyebileceği yolların aralığını ifade eder. Örneğin,/sw.js
dosyasını doğrudan kök dizine yerleştirir vescope
u 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 Worker
tarafı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.installing
içine yeni bir worker atanır, bu nedenle onunstatechange
değişikliğini izleyerek yüklemenin ne zaman tamamlandığını (installed
) tespit edebilirsiniz. Ayrıca,navigator.serviceWorker.controller
mevcutsa, 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}
controllerchange
olayı, 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
self
varsayılan olarakany
olarak kabul edilir, bu yüzden ek tip tanımlamaları olmadan,skipWaiting()
veyaclients
gibi Service Worker'a özgü API'ler için otomatik tamamlama veya tür kontrolü alamazsınız. ServiceWorkerGlobalScope
belirterek 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});
install
olayı sırasında, uygulamanın statik kaynakları (App Shell) önbelleğe alınır.self.skipWaiting()
fonksiyonu çağrılarak, yeniService Worker
hemen 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});
activate
olayı sırasında, eski önbellek sürümleri silinir veService Worker
güncel tutulur. Ayrıca,self.clients.claim()
fonksiyonu çağrılarak, yeniService Worker
sayfa 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});
fetch
aş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_WAITING
mesajı 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,
activate
olayı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 Worker
sayfayı 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,
message
olayıylaSW_UPDATED
alı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 Worker
kontrolü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});
install
aşamasında/offline.html
önbelleğe alın, böylece ağ yoksa en azından minimal bir sayfa döndürebilirsiniz.fetch
olayı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_CACHE
için, önbelleği siler ve ardından tüm istemcilereCACHE_CLEARED
ile bildirir.PING
için, orijinal istemciye zaman damgası içeren birPING
mesajı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.matchAll
kullanın. - Her birine
postMessage
göndererek mesajı yayınlayabilirsiniz. - Bu, güncelleme bildirimleri (
SW_UPDATED
gibi) 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
PING
gö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
,activate
vefetch
yaş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_WAITING
vb.) anlamak çok önemlidir. - İstemci-
Service Worker
iletiş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.