Service Worker i TypeScript
Denne artikkelen forklarer Service Workers i TypeScript.
Vi vil forklare Service Workers i TypeScript, inkludert praktiske eksempler.
YouTube Video
Service Worker i TypeScript
En Service Worker er en “forespørsels-proxy” som sitter mellom nettleseren og nettverket. Den muliggjør avskjæring av forespørsler, cache-kontroll, frakoblet støtte og bakgrunnsprosessering (synk og push). Å bruke TypeScript gir typesikkerhet og øker vedlikeholdbarheten.
Sette opp TypeScript
tsconfig.json
(Aktiver WebWorker-typer)
La oss se på et eksempel på hvordan WebWorker-typen aktiveres 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}
- Ved å legge til
WebWorker
ilib
-arrayet kan du bruke typer somServiceWorkerGlobalScope
. DOM
ogWebWorker
har ulike typer, så det er vanlig praksis å skilletsconfig.json
-innstillingene for nettleseren (hovedapplikasjonen) ogService Worker
.Service Worker
-filer blir til slutt lagt ut på en sti som samsvarer med omfanget (vanligvis nettstedets rot/sw.js
).- Av sikkerhetsgrunner kjører Service Workers kun over HTTPS (eller på
localhost
).
Registreringskode på nettlesersiden
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 );
- Denne prosessen registrerer en
Service Worker
.scope
refererer til området av stier somService Worker
kan kontrollere. For eksempel, hvis du plasserer/sw.js
direkte under roten og setterscope
til rotkatalogen (/
), kan du kontrollere alle ressursene på hele nettstedet. På den annen side, hvis du spesifiserer en bestemt katalog som/app/
, vil kun innholdet under denne katalogen bli kontrollert.
1 // If there's a waiting worker, notify the user.
2 if (registration.waiting) {
3 promptUserToUpdate(registration);
4 }
waiting
indikerer tilstanden hvor en ny Service Worker er installert og venter på å bli aktivert. På dette stadiet kontrolleres eksisterende sider fortsatt av den gamleService Worker
, så det er vanlig å be brukeren om bekreftelse, og etter godkjenning kalleskipWaiting()
for å umiddelbart aktivere den nyeService Worker
. Dette gjør at du kan bruke de siste endringene uten å vente på neste sideoppdatering.
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
trigges når installasjonen av en ny Service Worker har startet. Når denne hendelsen oppstår, settes en ny worker iregistration.installing
, så ved å overvåke densstatechange
kan du oppdage når installasjonen er fullført (installed
). Videre, hvisnavigator.serviceWorker.controller
finnes, betyr det at en gammel Service Worker allerede kontrollerer siden, så dette er en mulighet til å varsle brukeren om at en ny versjon finnes.
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
-hendelsen utløses i det øyeblikket den nye Service Workeren begynner å kontrollere den nåværende siden. Å laste siden på nytt på dette tidspunktet vil umiddelbart ta i bruk de nye cache-strategiene og prosessene. Automatisk omlasting kan imidlertid forringe brukeropplevelsen, så det er bedre å laste siden på nytt etter å ha fått brukerens samtykke.
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();
- Ved å la
Service Workeren
motta enpostMessage({ type: 'SKIP_WAITING' })
fra klienten og deretter kalleself.skipWaiting()
, kan du fremtvinge en oppdatering.
Områdeerklæring i sw.ts
La oss deretter se på et vanlig Service Worker-eksempel som implementerer app-shell-caching.
Når du bruker Service Workers i TypeScript, er det nyttig å tildele riktig type til self
.
1// sw.ts
2export default null;
3declare const self: ServiceWorkerGlobalScope;
- I TypeScript blir
self
behandlet somany
som standard, så uten ekstra typifisering får du verken autofullføring eller typekontroll for Service Worker-spesifikke API-er somskipWaiting()
ellerclients
. - Ved å spesifisere
ServiceWorkerGlobalScope
får du autoutfylling, unngår feilbruk, og utviklingen blir tryggere og mer atskilt fra vanlige DOM-skript.
Grunnleggende Service Worker (Installer/Aktiver/Hent)
Eksemplet viser enkel versjonshåndtering av cache, forhåndslagring ved installering, sletting av gamle cacher ved aktivering og cachestrategier ved henting (for eksempel cache-first for statiske ressurser, network-first for API-er).
sw.ts
(Minimal oppsett + Cache-skjelett)
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
-hendelsen blir appens statisk ressurser (App Shell) forhåndslagret i cache. Ved å kalleself.skipWaiting()
, blir den nyeService Worker
aktivert umiddelbart, slik at den nyeste cachen er tilgjengelig uten å vente på neste tilgang.
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});
- I
activate
-hendelsen slettes gamle versjoner av cacher, ogService Worker
holdes oppdatert. Videre, ved å kalleself.clients.claim()
, kan den nyeService Worker
kontrollere alle klienter uten å vente på at siden lastes på nytt.
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 avskjære forespørsler og kontrollere responsen. Du kan implementere strategier som cache-first eller network-first, som er nyttige for frakoblet støtte og ytelse.
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});
- Hvis
SKIP_WAITING
mottas, gjør et kall tilself.skipWaiting()
at du umiddelbart kan aktivere den ventende Service Workeren. Som resultat vil den nye versjonen brukes fra neste forespørsel uten behov for å laste inn siden på nytt.
Praktisk oversikt over cachestrategier
cache-first
Cache-first sjekker cachen først og returnerer responsen med en gang hvis den finnes. Hvis ikke, henter den fra nettverket og lagrer resultatet i cachen. Dette passer for statiske 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}
- Koden under viser en cache-first-implementasjon. Hvis det finnes en cache, returneres den; hvis ikke, hentes den fra nettverket og lagres i cachen. Dette er egnet for statiske ressurser som sjelden endres, som bilder eller CSS-filer.
network-first
Network-first forsøker nettverket først og faller tilbake til cachen hvis det mislykkes. Dette passer for API-er hvor ferskhet er viktig.
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}
- Koden under viser en network-first-implementasjon. Hvis nettverksrespons mottas, lagres den i cachen; hvis det feiler, returneres den bufrede versjonen. Det egner seg for ressurser hvor ferske data kreves, som nyhetsartikler eller API-responser.
stale-while-revalidate
stale-while-revalidate returnerer først cachen og oppdaterer den samtidig fra nettverket i bakgrunnen. Dette balanserer svartid og datatilgjengelighet.
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}
- Denne koden returnerer cachen umiddelbart hvis den finnes, mens nye data hentes fra nettverket i bakgrunnen for å oppdatere cachen. Dette gir raske svar til brukeren og oppdaterer innholdet for neste tilgang, noe som gjør det godt egnet for brukergrensesnitt eller lett datalevering.
Optimalisering av oppdateringsflyten (oppdateringsvarsel og sikker omlasting)
Service Worker
-oppdateringer skjer ikke umiddelbart; den nye versjonen forblir ventende til eksisterende faner er lukket.
Her implementerer vi et system for å varsle klienten når den nye versjonen er klar og laste siden trygt på nytt basert på brukerens handling.
Varsle klienten fra Service Worker
-siden når den nye versjonen er klar.
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 denne koden kalles
notifyClientsUpdated
på slutten avactivate
-hendelsen for å varsle alle tilkoblede klienter om at ny versjon er klar.clients.claim()
er en metode som umiddelbart bringer åpne sider (klienter) under kontroll av den nylig aktiverte Service Worker. Normalt vil enService Worker
begynne å kontrollere siden først ved neste lasting, men ved å brukeclients.claim()
kan du umiddelbart ta kontroll over siden uten å laste den inn på nytt.
Vis oppdateringsgrensesnitt på klienten, og last inn på nytt etter brukerens handling
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 mottar
SW_UPDATED
viamessage
-hendelsen og viser et oppdateringsvarsel i brukergrensesnittet. Når brukeren velger å laste inn siden på nytt, kjøreswindow.location.reload()
, og gamle HTML-, CSS- og andre ressurser på siden oppdateres til den nyeste versjonen. Dette sikrer at cache og kontroll medService Worker
, som er byttet medclients.claim()
, blir gjeldende på hele siden.
Frakoblet reserve
Forbered /offline.html
for kritisk navigasjon, og tilby et minimalt brukergrensesnitt som formidler mening selv uten bilder eller skrifttyper. Hvis et API-kall feiler, vis siste bufrede tilstand om mulig og prøv å hente på nytt i bakgrunnen for en bedre brukeropplevelse.
Implementeringseksempel
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});
- Forhåndslagre
/offline.html
underinstall
-hendelsen slik at du kan returnere minst en enkel side når nettverk ikke er tilgjengelig. - I
fetch
-hendelsen kan du overvåke navigasjonsforespørsler medrequest.mode === 'navigate'
og spesifikt rette deg mot sideoverganger. - Gå tilbake til
/offline.html
når nettverket feiler, slik at det vises selv når brukeren er offline.
Meldingsutveksling mellom klienten og Service Worker
Siden Service Worker
opererer uavhengig av sidelivssyklusen, er toveis kommunikasjon viktig for å varsle om tilstander og utføre kommandoer. Å spesifisere typer for meldinger hjelper til med å forhindre feil sending av meldinger, gjør det mulig med autofullføring av kode, og gjør implementeringen mer robust.
Kodeeksempel
- Definisjon av meldingstyper
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
er meldingstypen sendt fra Service Worker til klienten.ClientToSw
er meldingstypen sendt fra klienten til Service Worker.- Dette gjør det mulig å tydeliggjøre hvilke typer hendelser som kan utveksles gjennom toveis kommunikasjon.
- Behandling på Service Worker-siden
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 mottar meldinger fra klienten og behandler videre avhengig av meldingstype.
- For
CLEAR_CACHE
slettes cachen og alle klienter varsles medCACHE_CLEARED
. - For
PING
svarer den til opprinnelig klient med enPING
-melding med tidsstempel.
- Varsle alle klienter fra 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}
- Bruk
clients.matchAll
for å hente alle åpne vinduer/faner. - Ved å sende
postMessage
til hver, kan du kringkaste meldinger. - Dette kan brukes til oppdateringsvarsler (som
SW_UPDATED
) og feilmeldinger.
- Behandling på klientsiden
1navigator.serviceWorker.controller?.postMessage({
2 type: 'PING',
3 ts: Date.now()
4} as ClientToSw);
- Ved å sende en
PING
fra klienten og motta et svar fraService Worker
, kan du bekrefte at toveis kommunikasjon fungerer som den skal. Dette gjør det enklere å teste tilstand for tilkobling og håndtering av meldinger.
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 の更新やリロード、通知表示などを行います。^}
Fordeler med typet meldingsutveksling
- Typet meldingsutveksling gjør det tydelig hvilke meldinger som kan sendes og mottas, og autoutfylling og typekontroll øker sikkerheten.
postMessage
muliggjør en-til-en kommunikasjon, mensbroadcast
gir en-til-mange kommunikasjon.- Du kan enkelt implementere viktige funksjoner som oppdateringsvarsler (
SW_UPDATED
), cache-håndtering (CACHE_CLEARED
) og statuskontroll (PING
).
Sammendrag
- Ved å bruke TypeScript får du typesikkerhet ved både Service Worker API-kall og meldingsutveksling, noe som gir betydelig bedre utviklingseffektivitet og vedlikeholdbarhet.
- Å forstå livssyklus-hendelsene
install
,activate
ogfetch
, og å velge riktig cachestrategi (for eksempel cache-first eller network-first) for hver situasjon, gir bedre brukeropplevelse. - For drift er det viktig å forstå cache-versjonshåndtering og oppdateringsflyt (
updatefound
,waiting
,SKIP_WAITING
m.m.). - Ved å bruke typet meldingsutveksling for kommunikasjon mellom klient og
Service Worker
, kan du forhindre feilimplementering og etablere et system som er enkelt å utvide og vedlikeholde på lang sikt.
Du kan følge med på artikkelen ovenfor ved å bruke Visual Studio Code på vår YouTube-kanal. Vennligst sjekk ut YouTube-kanalen.