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
WebWorker
ke dalam senarailib
, anda boleh menggunakan jenis sepertiServiceWorkerGlobalScope
. DOM
danWebWorker
mempunyai jenis yang berbeza, jadi adalah amalan biasa untuk memisahkan tetapantsconfig.json
untuk pelayar (aplikasi utama) danService Worker
.- Fail
Service Worker
akhirnya 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
.scope
merujuk kepada julat laluan yang boleh dikawal olehService Worker
. Sebagai contoh, jika anda meletakkan/sw.js
terus di bawah akar dan menetapkanscope
ke 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 }
waiting
menunjukkan keadaan di mana Service Worker baharu telah dipasang dan sedang menunggu untuk diaktifkan. Pada peringkat ini, halaman sedia ada masih dikawal olehService Worker
lama, jadi adalah biasa untuk meminta pengesahan pengguna, dan selepas mendapat kelulusan, panggilskipWaiting()
untuk segera mengaktifkanService Worker
baharu. 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 });
updatefound
dicetuskan 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.controller
wujud, 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
controllerchange
dicetuskan 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 Worker
menerimapostMessage({ 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,
self
dianggap sebagaiany
secara lalai, jadi tanpa penetapan jenis tambahan, anda tidak akan mendapat penyelesaian jenis atau semakan jenis untuk API khusus Service Worker sepertiskipWaiting()
atauclients
. - Menentukan
ServiceWorkerGlobalScope
membolehkan 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 Worker
baru 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 Worker
dikekalkan agar sentiasa terkini. Selain itu, dengan memanggilself.clients.claim()
,Service Worker
baru 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_WAITING
diterima, 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,
notifyClientsUpdated
dipanggil pada penghujung acaraactivate
untuk 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 Worker
mula 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_UPDATED
melalui acaramessage
dan 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 Worker
yang 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.html
semasa acarainstall
supaya 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.html
apabila 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 };
SwToClient
adalah jenis mesej yang dihantar dari Service Worker ke klien.ClientToSw
adalah 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 mesejPING
beserta 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.matchAll
untuk mendapatkan semua tab tetingkap. - Dengan menghantar
postMessage
kepada 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
PING
dari 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.
postMessage
membolehkan komunikasi satu-ke-satu danbroadcast
membolehkan 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.