Service Worker dalam TypeScript
Artikel ini menerangkan tentang Service Worker dalam TypeScript.
Kami akan menerangkan tentang Service Worker dalam TypeScript, termasuk contoh praktikal.
YouTube Video
Service Worker dalam TypeScript
Service Worker ialah “proksi permintaan” yang terletak di antara pelayar dan rangkaian. Ia membolehkan pemintas fetch, kawalan cache, sokongan luar talian, dan pemprosesan latar belakang (sync dan push). Menggunakan TypeScript memberikan keselamatan jenis dan meningkatkan kebolehselenggaraan.
Menetapkan TypeScript
tsconfig.json (Aktifkan jenis WebWorker)
Mari kita lihat contoh cara mengaktifkan jenis WebWorker dalam 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}- Dengan menambah
WebWorkerke dalam senarailib, anda boleh menggunakan jenis sepertiServiceWorkerGlobalScope. DOMdanWebWorkermempunyai jenis yang berbeza, jadi adalah amalan biasa untuk memisahkan tetapantsconfig.jsonuntuk pelayar (aplikasi utama) danService Worker.- Fail
Service Workerakhirnya dihasilkan ke laluan yang sepadan dengan skop (biasanya akar laman/sw.js). - Atas sebab keselamatan, Service Worker hanya berjalan melalui HTTPS (atau pada
localhost).
Kod Pendaftaran di Sisi Pelayar
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 );- Proses ini mendaftarkan sebuah
Service Worker.scopemerujuk kepada julat laluan yang boleh dikawal olehService Worker. Sebagai contoh, jika anda meletakkan/sw.jsterus di bawah akar dan menetapkanscopeke direktori akar (/), anda boleh mengawal semua sumber di seluruh laman web. Sebaliknya, jika anda menentukan direktori tertentu seperti/app/, hanya kandungan di bawah direktori tersebut yang akan dikawal.
1 // If there's a waiting worker, notify the user.
2 if (registration.waiting) {
3 promptUserToUpdate(registration);
4 }waitingmenunjukkan keadaan di mana Service Worker baharu telah dipasang dan sedang menunggu untuk diaktifkan. Pada peringkat ini, halaman sedia ada masih dikawal olehService Workerlama, jadi adalah biasa untuk meminta pengesahan pengguna, dan selepas mendapat kelulusan, panggilskipWaiting()untuk segera mengaktifkanService Workerbaharu. Ini membolehkan anda memaparkan proses terbaharu tanpa menunggu halaman dimuat semula.
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 });updatefounddicetuskan apabila pemasangan Service Worker baharu telah dimulakan. Apabila peristiwa ini berlaku, worker baharu ditetapkan dalamregistration.installing, jadi dengan memantaustatechange-nya, anda boleh mengesan bila pemasangan telah selesai (installed). Selain itu, jikanavigator.serviceWorker.controllerwujud, ini bermakna Service Worker lama sudah mengawal halaman tersebut, jadi ini adalah peluang untuk memberitahu pengguna tentang kewujudan versi baru.
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}- Peristiwa
controllerchangedicetuskan pada saat Service Worker baharu mula mengawal halaman semasa. Memuat ulang pada ketika ini akan serta-merta menggunakan strategi cache dan pemprosesan baharu. Walau bagaimanapun, pemuatan semula secara automatik boleh menjejaskan pengalaman pengguna, jadi adalah lebih baik untuk memuat semula selepas mendapat persetujuan pengguna.
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();- Dengan memastikan
Service WorkermenerimapostMessage({ type: 'SKIP_WAITING' })dari klien dan kemudian memanggilself.skipWaiting(), anda boleh mendorong kemaskini.
Pengisytiharan Skop di dalam sw.ts
Seterusnya, mari lihat contoh biasa Service Worker yang melaksanakan cache app shell.
Apabila menggunakan Service Worker dalam TypeScript, adalah berguna untuk menetapkan jenis yang betul pada self.
1// sw.ts
2export default null;
3declare const self: ServiceWorkerGlobalScope;- Dalam TypeScript,
selfdianggap sebagaianysecara lalai, jadi tanpa penetapan jenis tambahan, anda tidak akan mendapat penyelesaian jenis atau semakan jenis untuk API khusus Service Worker sepertiskipWaiting()atauclients. - Menentukan
ServiceWorkerGlobalScopemembolehkan auto-completion, mencegah salah guna, dan membenarkan pembangunan yang lebih selamat berasingan dari skrip DOM biasa.
Service Worker Asas (Install/Activate/Fetch)
Ia menunjukkan pengurusan versi cache yang ringkas, precache semasa install, memadam cache lama semasa activate, dan strategi cache semasa fetch (cache-first untuk aset statik, network-first untuk API).
sw.ts (Tetapan Minimum + Rangka 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});- Semasa acara
install, sumber statik (App Shell) aplikasi telah dicache terlebih dahulu. Dengan memanggilself.skipWaiting(),Service Workerbaru akan diaktifkan serta-merta, menjadikan cache terkini tersedia tanpa perlu menunggu akses seterusnya.
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});- Dalam acara
activate, versi cache lama akan dipadamkan, danService Workerdikekalkan agar sentiasa terkini. Selain itu, dengan memanggilself.clients.claim(),Service Workerbaru boleh mengawal semua klien tanpa perlu menunggu halaman dimuat semula.
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});- Dalam
fetch, anda boleh memintas permintaan dan mengawal respons. Anda boleh melaksanakan strategi seperti cache-first atau network-first, sesuai untuk sokongan luar talian dan prestasi.
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});- Jika
SKIP_WAITINGditerima, memanggilself.skipWaiting()membolehkan anda mengaktifkan Service Worker yang sedang menunggu dengan segera. Hasilnya, versi baharu akan diterapkan pada permintaan seterusnya tanpa perlu memuat semula halaman.
Gambaran Keseluruhan Strategi Cache Praktikal
cache-first
Cache-first menyemak cache dahulu dan memulangkan respons serta-merta jika tersedia. Jika tiada, ia mengambil dari rangkaian dan menyimpan hasilnya dalam cache. Ini sesuai untuk fail statik.
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}- Kod ini menunjukkan pelaksanaan cache-first. Jika ada cache, ia akan mengembalikan cache tersebut; jika tiada, ia akan mengambil dari rangkaian dan menyimpannya dalam cache. Ia sesuai untuk sumber statik yang jarang berubah, seperti imej atau CSS.
network-first
Network-first mencuba rangkaian dahulu dan kembali kepada cache jika gagal. Ini sesuai untuk API yang memerlukan maklumat terkini.
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}- Kod ini menunjukkan pelaksanaan network-first. Jika respons rangkaian diterima, ia disimpan ke cache; jika gagal, ia mengembalikan versi yang terdapat dalam cache. Ia sesuai untuk sumber yang memerlukan data terkini, seperti artikel berita atau respons API.
stale-while-revalidate
stale-while-revalidate akan mengembalikan cache terlebih dahulu dan serentak mengemaskininya dari rangkaian di latar belakang. Ini mengimbangi kelajuan respons dan kesegaran maklumat.
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}- Kod ini mengembalikan cache dengan serta-merta jika ada, sambil memuat turun data baharu dari rangkaian di latar belakang untuk mengemas kini cache. Ia memberikan respons yang pantas kepada pengguna dan menggunakan kandungan terkini untuk akses berikutnya, sesuai untuk UI atau penghantaran data ringan.
Mengoptimumkan Aliran Kemas Kini (Notifikasi Kemas Kini dan Muat Semula yang Selamat)
Kemas kini Service Worker bukan segera; versi baharu akan kekal menunggu sehingga tab sedia ada ditutup.
Di sini, kami melaksanakan sistem untuk memaklumkan klien apabila versi terbaru telah sedia dan memuat semula halaman dengan selamat berdasarkan tindakan pengguna.
Maklumkan klien dari pihak Service Worker apabila versi baharu sudah siap.
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});- Dalam kod ini,
notifyClientsUpdateddipanggil pada penghujung acaraactivateuntuk memberitahu semua klien yang berhubung bahawa versi baru telah sedia.clients.claim()ialah kaedah yang dengan serta-merta meletakkan halaman (klien) yang sedang dibuka di bawah kawalan Service Worker baharu yang diaktifkan. Kebiasaannya,Service Workermula mengawal halaman hanya pada pemuatan seterusnya, tetapi dengan menggunakanclients.claim(), anda boleh segera mengawal halaman tanpa perlu memuat semula.
Papar UI kemas kini pada klien, dan muat semula apabila pengguna bertindak
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});- Klien menerima
SW_UPDATEDmelalui acaramessagedan memaparkan notifikasi kemas kini dalam UI. Apabila pengguna memilih untuk memuat semula,window.location.reload()akan dilaksanakan, mengemaskini HTML, CSS, dan sumber lain yang lama di halaman kepada versi terkini. Ini memastikan cache dan kawalan olehService Workeryang ditukar denganclients.claim()dipantulkan di seluruh halaman.
Fallback Luar Talian
Sediakan /offline.html untuk navigasi kritikal, dan sediakan UI minimum yang masih memberi makna walaupun tanpa imej atau fon. Jika panggilan API gagal, paparkan keadaan cache terakhir jika boleh dan cuba fetch semula di latar belakang untuk menambah baik UX.
Contoh Pelaksanaan
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});- Precache
/offline.htmlsemasa acarainstallsupaya sekurang-kurangnya anda boleh memaparkan halaman minimum apabila rangkaian tidak tersedia. - Dalam acara
fetch, anda boleh memantau permintaan navigasi denganrequest.mode === 'navigate'dan secara khusus menumpukan kepada peralihan halaman. - Fallback ke
/offline.htmlapabila rangkaian gagal, memastikan ia tetap dipaparkan walaupun tanpa rangkaian.
Pemesejan antara klien dan Service Worker
Kerana Service Worker beroperasi secara bebas daripada kitaran hidup halaman, pemesejan dua hala adalah penting untuk memaklumkan status dan melaksanakan arahan. Menentukan jenis untuk mesej membantu mencegah penghantaran mesej yang salah, membolehkan pelengkapan kod, dan menjadikan implementasi anda lebih kukuh.
Contoh Kod
- Definisi Jenis Mesej
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 };SwToClientadalah jenis mesej yang dihantar dari Service Worker ke klien.ClientToSwadalah jenis mesej yang dihantar dari klien ke Service Worker.- Ini membolehkan anda memperjelas jenis peristiwa yang boleh dipertukarkan melalui komunikasi dua hala.
- Pemprosesan di pihak 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});- Service Worker menerima mesej dari klien dan memproses mengikut jenis mesej.
- Untuk
CLEAR_CACHE, ia akan memadam cache dan kemudian memberitahu semua klien denganCACHE_CLEARED. - Untuk
PING, ia membalas kepada klien asal dengan mesejPINGbeserta cap masa.
- Maklumkan Semua Klien dari 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}- Gunakan
clients.matchAlluntuk mendapatkan semua tab tetingkap. - Dengan menghantar
postMessagekepada setiap satu, anda boleh membuat siaran mesej. - Ini boleh digunakan untuk notifkasi kemas kini (seperti
SW_UPDATED) dan pemberitahuan ralat.
- Pemprosesan di Pihak Klien
1navigator.serviceWorker.controller?.postMessage({
2 type: 'PING',
3 ts: Date.now()
4} as ClientToSw);- Dengan menghantar
PINGdari klien dan menerima respons daripadaService Worker, anda boleh mengesahkan bahawa komunikasi dua hala berfungsi dengan baik. Ini memudahkan ujian keadaan sambungan dan pengendalian mesej.
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 の更新やリロード、通知表示などを行います。^}
Manfaat Pemesejan Bertip
- Menggunakan mesej bertip menjadikan mesej yang boleh dihantar dan diterima lebih jelas, selain auto-completion dan semakan jenis meningkatkan keselamatan.
postMessagemembolehkan komunikasi satu-ke-satu danbroadcastmembolehkan komunikasi satu-ke-banyak.- Anda boleh melaksanakan ciri penting seperti notifikasi kemas kini (
SW_UPDATED), pengurusan cache (CACHE_CLEARED), dan semakan status (PING) dengan mudah.
Ringkasan
- Menggunakan TypeScript menambah keselamatan jenis pada panggilan API dan pemesejan Service Worker, sekaligus meningkatkan kecekapan pembangunan dan kebolehselenggaraan.
- Memahami acara kitaran hayat
install,activate, danfetch, serta memilih strategi cache yang betul (seperti cache-first atau network-first) untuk setiap situasi akan membawa kepada pengalaman pengguna yang lebih baik. - Untuk operasi, memahami pengurusan versi cache dan aliran kemas kini (
updatefound,waiting,SKIP_WAITING, dan lain-lain) adalah penting. - Dengan menggunakan pemesejan beraip untuk komunikasi antara klien dan
Service Worker, anda dapat mengelakkan pelaksanaan yang salah dan membina sistem yang mudah dikembangkan serta diselenggara untuk jangka masa panjang.
Anda boleh mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Sila lihat juga saluran YouTube kami.