Service Worker sa TypeScript
Ipinaliwanag ng artikulong ito ang Service Workers sa TypeScript.
Ipapaliwanag namin ang Service Workers sa TypeScript, kasama ang mga praktikal na halimbawa.
YouTube Video
Service Worker sa TypeScript
Ang Service Worker ay isang "request proxy" na namamagitan sa pagitan ng browser at ng network. Ipinapahintulot nito ang fetch interception, control sa cache, suporta kahit offline, at background processing (sync at push). Ang paggamit ng TypeScript ay nagbibigay ng type safety at nagpapataas ng maintainability.
Pagse-setup ng TypeScript
tsconfig.json
(I-enable ang WebWorker types)
Tingnan natin ang isang halimbawa ng pag-enable ng WebWorker type sa 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}
- Sa pagdagdag ng
WebWorker
salib
array, maaari mong gamitin ang mga types tulad ngServiceWorkerGlobalScope
. Magkaiba ang mga uri ng \
DOM` at `WebWorker`, kaya karaniwan nang ihiwalay ang mga setting ng `tsconfig.json` para sa browser (main app) at sa `Service Worker`.Service Worker
na mga file ay inilalabas sa landas na tumutugma sa scope (karaniwan ay ang root ng site/sw.js
).- Dahil sa seguridad, ang Service Workers ay gumagana lamang sa HTTPS (o sa
localhost
).
Code ng Pagrehistro sa Panig ng Browser
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 );
- Ang prosesong ito ay nagrerehistro ng isang
Service Worker
. Angscope
ay tumutukoy sa saklaw ng mga landas na maaaring kontrolin ngService Worker
. Halimbawa, kung ilalagay mo ang/sw.js
sa ilalim ng root at itatakda angscope
sa root directory (/
), makokontrol mo ang lahat ng resources ng buong site. Sa kabilang banda, kung itutukoy mo ang isang partikular na directory gaya ng/app/
, tanging ang mga nilalaman lamang sa ilalim ng directory na iyon ang makokontrol.
1 // If there's a waiting worker, notify the user.
2 if (registration.waiting) {
3 promptUserToUpdate(registration);
4 }
- Ang
waiting
ay tumutukoy sa estado kung saan ang bagong Service Worker ay na-install na at naghihintay na ma-activate. Sa yugtong ito, ang mga kasalukuyang pahina ay kinokontrol pa rin ng lumangService Worker
, kaya karaniwan ang humiling ng kumpirmasyon mula sa user, at pagkatapos ng pag-apruba, tawagin angskipWaiting()
upang agad na ma-activate ang bagongService Worker
. Dahil dito, maipapakita ang pinakabagong mga proseso nang hindi na kailangang maghintay ng muling pag-reload ng pahina.
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 });
- Ang
updatefound
ay na-trigger kapag nagsimula na ang pag-install ng isang bagong Service Worker. Kapag nangyari ang event na ito, may bagong worker na nakatakda saregistration.installing
, kaya't sa pagmamanman ngstatechange
nito, matutukoy mo kung kailan natapos ang pag-install (installed
). Dagdag pa rito, kung umiiral ang `navigator.serviceWorker.controller`, nangangahulugan ito na may lumang Service Worker na kumokontrol sa pahina, kaya ito ay pagkakataon upang abisuhan ang user tungkol sa bagong bersyon.
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}
- Ang
controllerchange
event ay nag-trigger kapag nagsimulang kontrolin ng bagong Service Worker ang kasalukuyang pahina. Kapag nireload sa puntong ito, agad na maa-apply ang mga bagong cache na estratehiya at proseso. Gayunpaman, ang awtomatikong pag-reload ay maaaring makasama sa karanasan ng user, kaya mas mainam na mag-reload lamang pagkatapos makuha ang pahintulot ng user.
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();
- Sa pamamagitan ng pagpapadala ng
postMessage({ type: 'SKIP_WAITING' })
mula sa client papunta saService Worker
at pagtawag saself.skipWaiting()
, maaari mong simulan agad ang pag-update.
Pahayag ng Scope sa sw.ts
Susunod, tingnan natin ang isang karaniwang halimbawa ng Service Worker na may app shell caching.
Kapag gumagamit ng Service Workers sa TypeScript, mahalagang bigyan ng tamang type ang self
.
1// sw.ts
2export default null;
3declare const self: ServiceWorkerGlobalScope;
- Sa TypeScript, itinuturing na `any` ang `self` bilang default, kaya kung walang karagdagang uri, hindi ka makakakuha ng type completion o type checking para sa mga partikular na API ng Service Worker gaya ng `skipWaiting()` o `clients`.
- Kung iko-convert sa
ServiceWorkerGlobalScope
, magkakaroon ng auto-completion, iiwas sa maling paggamit, at magbibigay-daan sa mas ligtas na development na hiwalay sa regular na DOM scripts.
Pangunahing Service Worker (Install/Activate/Fetch)
Ipinapakita nito ang simpleng pamamahala ng cache version, pag-precash sa install, pagtanggal ng lumang cache kapag activate, at mga cache strategy sa fetch (cache-first para sa static assets, network-first para sa APIs).
sw.ts
(Minimal na Setup + Balangkas ng Cache)
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});
- Sa panahon ng `install` event, ang mga static resources (App Shell) ng app ay naka-pre-cache. Sa pagtawag ng `self.skipWaiting()`, agad na maa-activate ang bagong `Service Worker` at agad magagamit ang pinakabagong cache nang hindi na kailangang hintayin ang susunod na access.
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});
- Sa `activate` event, binubura ang mga lumang bersyon ng cache at napapanatiling napapanahon ang `Service Worker`. Dagdag pa, sa pagtawag ng `self.clients.claim()`, maaaring kontrolin ng bagong `Service Worker` ang lahat ng kliyente nang hindi hinihintay na ma-reload ang pahina.
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});
- Sa
fetch
, maaari mong salubungin at kontrolin ang tugon sa request. Maaaring magpatupad ng mga strategy tulad ng cache-first at network-first, na mahalaga para sa offline support at performance.
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});
- Kung matatanggap ang
SKIP_WAITING
, ang pagtawag ngself.skipWaiting()
ay nagbibigay-daan sa iyo na agad i-activate ang naghihintay na Service Worker. Bilang resulta, ang bagong bersyon ay maipapatupad na agad sa susunod na request nang hindi na kailangan pang i-reload ang pahina.
Pangkalahatang tanaw sa Mga Praktikal na Cache Strategy
cache-first
Cache-first ay unang tumitingin sa cache at agad na bumabalik ng sagot kung mayroon. Kung wala, kukuha ito mula sa network at icacache ang resulta. Ito ay akma para sa mga static na file.
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}
- Ipinapakita ng code na ito ang cache-first na implementasyon. Kung may cache, ito ay ipinapadala agad; kung wala, kukuha mula sa network at ise-save ito sa cache. Angkop ito para sa mga static na resource na bihirang magbago, tulad ng mga larawan o CSS.
network-first
Network-first ay unang sumusubok sa network at babalik sa cache kapag nabigo. Ito ay gamit para sa mga API na mahalaga ang pagiging bago ng data.
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}
- Ipinapakita ng code na ito ang halimbawa ng network-first. Kung may sagot sa network, ise-save ito sa cache; kung wala, babalik sa cached version. Angkop ito para sa mga resource na kailangan ng sariwang data, gaya ng mga balitang artikulo o API na sagot.
stale-while-revalidate
Ang stale-while-revalidate ay ibinabalik muna ang cache at sabay na ina-update ito mula sa network sa background. Binabalanse nito ang bilis ng tugon at pagiging bago.
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}
- Ang code na ito ay agad nagbabalik ng cache kapag meron, habang tahimik ang pagkuha ng bagong datos mula sa network para i-update ang cache. Nagbibigay ito ng mabilis na response sa users at ginagamit ang updated na content sa susunod na access, kaya angkop para sa UI o magaan na paghahatid ng data.
Pag-optimize ng Daloy ng Update (Update Notification at Ligtas na Reload)
Ang mga update ng Service Worker
ay hindi agad-agad; ang bagong bersyon ay mananatiling naghihintay hangga't bukas pa ang mga kasalukuyang tab.
Dito natin ipinatutupad ang sistemang mag-aabiso sa client kung handa na ang bagong version at ligtas na magre-reload ng page ayon sa aksyon ng user.
Ipaalam sa client mula sa panig ng Service Worker
kapag handa na ang bagong bersyon.
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});
- Sa code na ito, tinatawag ang
notifyClientsUpdated
sa dulo ngactivate
event upang abisuhan ang lahat ng naka-konektang client na handa na ang bagong version. Angclients.claim()
ay isang pamamaraan na agad nagdadala sa mga kasalukuyang bukas na pahina (mga client) sa ilalim ng kontrol ng bagong-activate na Service Worker. Karaniwan, angService Worker
ay nagsisimulang kontrolin ang pahina sa susunod na load lamang, ngunit sa paggamit ngclients.claim()
, maaari mong agad makontrol ang pahina nang hindi nire-reload.
Ipakita ang update UI sa client, at i-reload ang page sa aksyon ng user
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});
- Tumatanggap ang client ng
SW_UPDATED
sa pamamagitan ngmessage
event at ipinapakita ang abiso ng update sa UI. Kapag pinili ng user na i-reload, e-execute ang `window.location.reload()`, ini-update ang mga luma nang HTML, CSS, at iba pang resources sa pahina sa pinakabagong bersyon. Tinitiyak nito na ang cache at ang kontrol ng `Service Worker` na nailipat gamit ang `clients.claim()` ay naipapakita sa buong pahina.
Offline Fallback
Maghanda ng /offline.html
para sa mahalagang pag-navigate, at magbigay ng minimal na UI na may sapat na impormasyon kahit walang larawan o font. Kapag nabigo ang API call, ipakita ang huling naka-cache na estado kung maaari at subukang mag-refetch sa background para mapabuti ang UX.
Halimbawa ng Pagpapatupad
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});
- I-precache ang
/offline.html
habang nasainstall
event upang may maibalik kahit minimal na pahina kapag walang network. - Sa `fetch` event, maaari mong subaybayan ang mga navigation request gamit ang `request.mode === 'navigate'` at tiyak na i-target ang mga paglipat ng pahina.
- Mag-fallback sa
/offline.html
kapag nabigo ang network, upang sigurado na may lalabas na pahina kahit walang internet.
Pagpapalitan ng mensahe sa pagitan ng client at ng Service Worker
Dahil ang `Service Worker` ay gumagana nang hiwalay sa page lifecycle, mahalaga ang bidirectional messaging para mag-abiso ng mga estado at magsagawa ng mga utos. Ang pag-define ng mga uri para sa mga mensahe ay nakakatulong upang maiwasan ang maling pagpapadala ng mensahe, nagbibigay ng code completion, at nagpapalakas ng iyong implementasyon.
Halimbawa ng Code
- Paglalarawan ng Mga Uri ng Mensahe
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 };
- Ang
SwToClient
ay uri ng mensahe na ipinapadala mula Service Worker papuntang client. - Ang
ClientToSw
ay uri ng mensahe na ipinapadala mula client papunta sa Service Worker. - Dahil dito, maipapaliwanag mo nang malinaw ang mga uri ng event na maaaring ipalitan sa pamamagitan ng two-way na komunikasyon.
- Pagpoproseso sa Bahagi ng Service Worker
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});
- Tumatanggap ang Service Worker ng mga mensahe mula sa client at hinahati ang proseso depende sa uri ng mensahe.
- Para sa
CLEAR_CACHE
, binubura nito ang cache saka ipinapaalam sa lahat ng client gamit angCACHE_CLEARED
. - Para sa
PING
, sumasagot ito sa orihinal na client ngPING
na mensahe na may kasamang timestamp.
- Magaabiso sa Lahat ng Client mula sa 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}
- Gamitin ang
clients.matchAll
para makuha lahat ng window tab. - Sa pagpapadala ng
postMessage
sa bawat isa, maaari kang mag-broadcast ng mga mensahe. - Magagamit ito sa update notification (gaya ng
SW_UPDATED
) at error notification.
- Pagpoproseso sa Bahagi ng Client
1navigator.serviceWorker.controller?.postMessage({
2 type: 'PING',
3 ts: Date.now()
4} as ClientToSw);
- Sa pamamagitan ng pagpapadala ng `PING` mula sa kliyente at pagtanggap ng tugon mula sa `Service Worker`, maaari mong matiyak na maayos na gumagana ang two-way na komunikasyon. Napapadali nito ang pag-test ng mga koneksyon at paghawak ng mga mensahe.
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 の更新やリロード、通知表示などを行います。^}
Mga Benepisyo ng Typed Messaging
- Ang paggamit ng typed messages ay nagpapalinaw kung alinsong mga mensahe ang maaaring ipadala at matanggap, at pinapabuti ng auto-completion at type checking ang kaligtasan.
- Pinapaganang ng
postMessage
ang one-to-one na komunikasyon at pinapaganang ngbroadcast
ang one-to-many na komunikasyon. - Madali mong maisasama ang mahahalagang features tulad ng update notifications (
SW_UPDATED
), cache management (CACHE_CLEARED
), at health check (PING
).
Buod
- Ang paggamit ng TypeScript ay nagdadagdag ng type safety sa Service Worker API at messaging, kaya napapabuti nang husto ang development efficiency at maintainability.
- Ang pag-unawa sa
install
,activate
, atfetch
na lifecycle events, at pagpili ng tamang caching strategy (tulad ng cache-first o network-first) para sa bawat sitwasyon ay nagdadala ng mas magandang karanasan sa user. - Para sa mga operasyon, mahalagang maintindihan ang cache version management at update flow (
updatefound
,waiting
,SKIP_WAITING
, atbp.). - Sa pamamagitan ng paggamit ng typed messaging para sa komunikasyon ng client at
Service Worker
, maiiwasan mo ang maling pagpapatupad at makakapagtatag ka ng sistemang madaling palawakin at panatilihin sa pangmatagalang panahon.
Maaari mong sundan ang artikulo sa itaas gamit ang Visual Studio Code sa aming YouTube channel. Paki-check din ang aming YouTube channel.