Service Worker i TypeScript
Denna artikel förklarar Service Workers i TypeScript.
Vi kommer att förklara Service Workers i TypeScript, inklusive praktiska exempel.
YouTube Video
Service Worker i TypeScript
En Service Worker är en 'begäran-proxy' som ligger mellan webbläsaren och nätverket. Den möjliggör fångst av hämtningar, cachekontroll, offline-stöd och bakgrundsbehandling (synkronisering och push). Att använda TypeScript ger typsäkerhet och ökar underhållbarheten.
Konfigurera TypeScript
tsconfig.json
(Aktivera WebWorker-typer)
Låt oss titta på ett exempel på hur man aktiverar WebWorker-typ i tsconfig.json
.
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}
- Genom att lägga till
WebWorker
tilllib
-arrayen kan du använda typer somServiceWorkerGlobalScope
. DOM
ochWebWorker
har olika typer, så det är vanligt att separera inställningarna avtsconfig.json
för webbläsaren (huvudapplikationen) ochService Worker
.Service Worker
-filerna hamnar slutligen på en sökväg som matchar scope (vanligtvis webbplatsens rot,/sw.js
).- Av säkerhetsskäl körs Service Workers endast över HTTPS (eller på
localhost
).
Registreringskod på webbläsarsidan
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 );
- Den här processen registrerar en
Service Worker
.scope
syftar på de sökvägar somService Worker
kan kontrollera. Till exempel, om du placerar/sw.js
direkt under roten och sätterscope
till rotkatalogen (/
), kan du kontrollera alla resurser på hela webbplatsen. Å andra sidan, om du anger en särskild katalog, till exempel/app/
, kommer endast innehållet under den katalogen att kontrolleras.
1 // If there's a waiting worker, notify the user.
2 if (registration.waiting) {
3 promptUserToUpdate(registration);
4 }
waiting
anger det tillstånd där en ny Service Worker har installerats och väntar på att aktiveras. I det här skedet kontrolleras befintliga sidor fortfarande av den gamlaService Worker
, så det är vanligt att be användaren om bekräftelse och därefter anropaskipWaiting()
för att genast aktivera den nyaService Worker
. Detta gör att du kan återspegla de senaste processerna utan att vänta på nästa omladdning av sidan.
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
utlöses när installationen av en ny Service Worker har påbörjats. När denna händelse äger rum sätts en ny worker iregistration.installing
, så genom att lyssna på dessstatechange
kan du upptäcka när installationen har slutförts (installed
). Vidare, omnavigator.serviceWorker.controller
finns, betyder det att en äldre Service Worker redan kontrollerar sidan, så det här är ett tillfälle att meddela användaren om att det finns en ny version.
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}
- Händelsen
controllerchange
utlöses i det ögonblick den nya Service Worker börjar kontrollera den aktuella sidan. Om sidan laddas om vid denna tidpunkt kommer nya cache-strategier och processer att tillämpas omedelbart. Automatisk omladdning kan dock försämra användarupplevelsen, så det är att föredra att ladda om först efter att ha fått användarens godkännande.
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();
- Genom att låta
Service Worker
ta emot ettpostMessage({ type: 'SKIP_WAITING' })
från klienten och sedan anropaself.skipWaiting()
, kan du påskynda en uppdatering.
Scope-deklaration i sw.ts
Låt oss nu titta på ett typiskt exempel på Service Worker som implementerar app shell-cachelagring.
När du använder Service Workers i TypeScript är det användbart att tilldela rätt typ till self
.
1// sw.ts
2export default null;
3declare const self: ServiceWorkerGlobalScope;
- I TypeScript behandlas
self
somany
som standard, så utan extra typning får du varken typkomplettering eller typkontroll för Service Worker-specifika API:er somskipWaiting()
ellerclients
. - Genom att ange
ServiceWorkerGlobalScope
möjliggörs autokomplettering, förhindrar felanvändning och ger en säkrare utveckling separerad från vanliga DOM-skript.
Grundläggande Service Worker (Install/Activate/Fetch)
Det visar enkel cacheversionshantering, förcachelagring vid installation, borttagning av gamla cacheminnen vid aktivering och cache-strategier vid hämtning (cache-first för statiska resurser, network-first för API:er).
sw.ts
(Minimal uppsättning + cache-skelett)
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});
- Under
install
-händelsen blir appens statisk resurser (App Shell) förlagrade i cache. Genom att anropaself.skipWaiting()
aktiveras den nyaService Worker
omedelbart, vilket gör den senaste cachen tillgänglig utan att behöva vänta till nästa åtkomst.
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});
- Vid
activate
-händelsen raderas gamla versioner av cacher, ochService Worker
hålls uppdaterad. Dessutom kan nyaService Worker
genom att anropaself.clients.claim()
styra alla klienter utan att behöva vänta på att sidan ska laddas om.
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});
- I
fetch
kan du fånga upp begäranden och kontrollera svaret. Du kan implementera strategier som cache-first eller network-first, vilket är användbart för offlinestöd och prestanda.
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});
- Om
SKIP_WAITING
tas emot tillåter anropet avself.skipWaiting()
att du omedelbart aktiverar den väntande Service Worker. Som ett resultat kommer den nya versionen att tillämpas från nästa begäran utan behov av att ladda om sidan.
Översikt över praktiska cache-strategier
cache-first
Cache-first kontrollerar cacheminnet först och returnerar svaret omedelbart om det finns. Om inte, hämtar den från nätverket och cachelagrar resultatet. Detta passar statiska filer.
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}
- Denna kod visar en cache-first-implementering. Om det finns i cacheminnet returneras det; om inte, hämtas det från nätverket och sparas i cacheminnet. Det är lämpligt för statiska resurser som sällan ändras, såsom bilder eller CSS.
network-first
Network-first försöker med nätverket först och återgår till cacheminnet vid fel. Detta är lämpligt för API:er där färskhet är viktigt.
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}
- Denna kod visar en network-first-implementering. Om ett nätverkssvar tas emot sparas det i cacheminnet; om det misslyckas returneras den cachelagrade versionen. Det passar för resurser som kräver färsk data, som nyhetsartiklar eller API-svar.
stale-while-revalidate
stale-while-revalidate returnerar först cachead data och uppdaterar den samtidigt i bakgrunden från nätverket. Detta balanserar svarshastighet och färskhet.
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}
- Denna kod returnerar cacheminnet omedelbart om det finns, samtidigt som den hämtar ny data från nätverket i bakgrunden för att uppdatera cacheminnet. Det ger snabba svar till användare och använder uppdaterat innehåll vid nästa åtkomst, vilket gör det lämpligt för användargränssnitt eller lätt dataleverans.
Optimera uppdateringsflödet (uppdateringsavisering och säker omladdning)
Service Worker
-uppdateringar sker inte omedelbart; den nya versionen förblir väntande tills befintliga flikar stängs.
Här implementerar vi ett system för att meddela klienten när den nya versionen är redo och ladda om sidan säkert baserat på användarens åtgärd.
Meddela klienten från Service Worker
-sidan när den nya versionen är redo.
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});
- I denna kod anropas
notifyClientsUpdated
i slutet avactivate
-händelsen för att meddela alla anslutna klienter att den nya versionen är redo.clients.claim()
är en metod som omedelbart placerar öppna sidor (klienter) under kontroll av den nyaktiverade Service Worker. Normalt börjar enService Worker
styra sidan först vid nästa omladdning, men medclients.claim()
kan du ta kontroll över sidan omedelbart utan omladdning.
Visa uppdateringsgränssnitt på klienten och ladda om vid användaråtgärd
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});
- Klienten tar emot
SW_UPDATED
viamessage
-händelsen och visar ett uppdateringsmeddelande i användargränssnittet. När användaren väljer att ladda om sidan körswindow.location.reload()
, vilket uppdaterar gammal HTML, CSS och andra resurser på sidan till den senaste versionen. Detta säkerställer att cachen och kontrollen frånService Worker
medclients.claim()
återspeglas på hela sidan.
Offline-backup
Förbered /offline.html
för kritisk navigering och tillhandahåll ett minimalt gränssnitt som förmedlar mening även utan bilder eller typsnitt. Om ett API-anrop misslyckas, visa det senast cachelagrade tillståndet om möjligt och försök att hämta igen i bakgrunden för att förbättra användarupplevelsen.
Implementeringsexempel
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});
- Förcachelagra
/offline.html
underinstall
-händelsen så att du kan återge åtminstone en minimal sida när nätverket är otillgängligt. - Under
fetch
-händelsen kan du observera navigeringsförfrågningar medrequest.mode === 'navigate'
och rikta in dig specifikt på sidövergångar. - Falla tillbaka på
/offline.html
när nätverket misslyckas, vilket säkerställer att den visas även offline.
Meddelandeutbyte mellan klienten och Service Worker
Eftersom Service Worker
fungerar oberoende av sidans livscykel är bidirektionell meddelandehantering viktig för att meddela tillstånd och utföra kommandon. Genom att ange typer för meddelanden undviks felaktig meddelandehantering, möjliggör kodkomplettering och gör din implementering mer robust.
Kodexempel
- Definition av meddelandetyper
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
är typen av meddelande som skickas från Service Worker till klient.ClientToSw
är typen av meddelande som skickas från klient till Service Worker.- Detta gör det möjligt att förtydliga vilken typ av händelser som kan utbytas genom tvåvägskommunikation.
- Behandling på Service Worker-sidan
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 Workern tar emot meddelanden från klienten och förgrenar behandlingen baserat på typ.
- För
CLEAR_CACHE
raderas cacheminnet och alla klienter informeras medCACHE_CLEARED
. - För
PING
svarar den ursprungliga klienten med ettPING
-meddelande inklusive tidsstämpel.
- Meddela alla klienter från Service Worker
1async function broadcast(msg: SwToClient) {
2 const clients = await self.clients.matchAll({ includeUncontrolled: true });
3 for (const c of clients) c.postMessage(msg);
4}
- Använd
clients.matchAll
för att hämta alla fönsterflikar. - Genom att skicka
postMessage
till varje kan du sända ut meddelanden. - Detta kan användas för uppdateringsaviseringar (som
SW_UPDATED
) och felmeddelanden.
- Behandling på klientsidan
1navigator.serviceWorker.controller?.postMessage({
2 type: 'PING',
3 ts: Date.now()
4} as ClientToSw);
- Genom att skicka en
PING
från klienten och ta emot ett svar frånService Worker
kan du bekräfta att den tvåvägskommunikationen fungerar korrekt. Detta gör det enklare att testa anslutningstillstånd och meddelandehantering.
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 の更新やリロード、通知表示などを行います。^}
Fördelar med typade meddelanden
- Genom att använda typade meddelanden blir det tydligt vilka meddelanden som kan skickas och tas emot, och autokomplettering samt typkontroll förbättrar säkerheten.
postMessage
möjliggör en-till-en kommunikation ochbroadcast
möjliggör en-till-många kommunikation.- Du kan enkelt implementera viktiga funktioner som uppdateringsaviseringar (
SW_UPDATED
), cachehantering (CACHE_CLEARED
) och hälsokontroller (PING
).
Sammanfattning
- Att använda TypeScript lägger till typsäkerhet till Service Worker API-anrop och meddelanden, vilket kraftigt förbättrar utvecklingseffektiviteten och underhållbarheten.
- Att förstå livscykelhändelserna
install
,activate
ochfetch
, samt att välja rätt cachestrategi (såsom cache-first eller network-first) för varje situation leder till en bättre användarupplevelse. - För drift är det viktigt att förstå cacheversionshantering och uppdateringsflöden (
updatefound
,waiting
,SKIP_WAITING
, etc.). - Genom att använda typade meddelanden för kommunikationen mellan klient och
Service Worker
kan du undvika felimplementationer och skapa ett system som är lätt att bygga ut och underhålla på lång sikt.
Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.