`Service Worker` w JavaScript
Ten artykuł wyjaśnia koncepcję Service Worker w JavaScript.
Wyjaśnimy krok po kroku od podstaw Service Worker do praktycznego zarządzania pamięcią podręczną.
YouTube Video
offline.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Offline</title>
6</head>
7<body>
8 <h1>You are offline</h1>
9 <p>This is the offline fallback page.</p>
10</body>
11</html>style.css
1body {
2 font-family: sans-serif;
3 background-color: #f0f0f0;
4 padding: 20px;
5}
6h1 {
7 color: #333;
8}javascript-service-worker.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 .container-flex {
32 display: flex;
33 flex-wrap: wrap;
34 gap: 2em;
35 max-width: 1000px;
36 margin: 0 auto;
37 padding: 1em;
38 background-color: #ffffff;
39 border: 1px solid #ccc;
40 border-radius: 10px;
41 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
42 }
43
44 .left-column, .right-column {
45 flex: 1 1 200px;
46 min-width: 200px;
47 }
48
49 h1, h2 {
50 font-size: 1.2rem;
51 color: #007bff;
52 margin-top: 0.5em;
53 margin-bottom: 0.5em;
54 border-left: 5px solid #007bff;
55 padding-left: 0.6em;
56 background-color: #e9f2ff;
57 }
58
59 button {
60 display: block;
61 margin: 1em auto;
62 padding: 0.75em 1.5em;
63 font-size: 1rem;
64 background-color: #007bff;
65 color: white;
66 border: none;
67 border-radius: 6px;
68 cursor: pointer;
69 transition: background-color 0.3s ease;
70 }
71
72 button:hover {
73 background-color: #0056b3;
74 }
75
76 #output {
77 margin-top: 1em;
78 background-color: #1e1e1e;
79 color: #0f0;
80 padding: 1em;
81 border-radius: 8px;
82 min-height: 200px;
83 font-family: Consolas, monospace;
84 font-size: 0.95rem;
85 overflow-y: auto;
86 white-space: pre-wrap;
87 }
88
89 .highlight {
90 outline: 3px solid #ffc107; /* yellow border */
91 background-color: #fff8e1; /* soft yellow background */
92 transition: background-color 0.3s ease, outline 0.3s ease;
93 }
94
95 .active {
96 background-color: #28a745; /* green background */
97 color: #fff;
98 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
99 transition: background-color 0.3s ease, box-shadow 0.3s ease;
100 }
101 </style>
102</head>
103<body>
104 <div class="container">
105 <h1>JavaScript Console</h1>
106 <button id="executeBtn">Execute</button>
107 <div id="output"></div>
108 </div>
109
110 <div class="container">
111 <h2>HTML Sample</h2>
112 <button id="fetchBtn">Fetch Test</button>
113 </div>
114
115 <script>
116 // Override console.log to display messages in the #output element
117 (function () {
118 // Override console.log
119 const originalLog = console.log;
120 console.log = function (...args) {
121 originalLog.apply(console, args);
122 const message = document.createElement('div');
123 message.textContent = args.map(String).join(' ');
124 output.appendChild(message);
125 };
126
127 // Override console.error
128 const originalError = console.error;
129 console.error = function (...args) {
130 originalError.apply(console, args);
131 const message = document.createElement('div');
132 message.textContent = args.map(String).join(' ');
133 message.style.color = 'red'; // Color error messages red
134 output.appendChild(message);
135 };
136 })();
137
138 document.getElementById('executeBtn').addEventListener('click', () => {
139 // Prevent multiple loads
140 if (document.getElementById('externalScript')) return;
141
142 const script = document.createElement('script');
143 script.src = 'javascript-service-worker.js';
144 script.id = 'externalScript';
145 //script.onload = () => console.log('javascript-service-worker.js loaded and executed.');
146 //script.onerror = () => console.log('Failed to load javascript-service-worker.js.');
147 document.body.appendChild(script);
148 });
149 </script>
150</body>
151</html>Service Worker w JavaScript
Service Worker to funkcja JavaScript, która znajduje się pomiędzy przeglądarką a siecią, umożliwiając buforowanie żądań oraz obsługę trybu offline. Jest to kluczowa technologia aplikacji progresywnych (PWA), która zapewnia użytkownikom wrażenia podobne do aplikacji natywnych.
Czym jest Service Worker?
Service Worker to plik JavaScript, który działa w wątku w tle przeglądarki. Działa w oddzielnym wątku od strony, nie ma dostępu do interfejsu użytkownika, ale może przechwytywać żądania sieciowe, zarządzać pamięcią podręczną oraz obsługiwać powiadomienia push.
Kluczowe cechy Service Worker to:.
- Działa tylko przez HTTPS, z wyjątkiem localhosta.
- Wykorzystuje asynchroniczne API oparte na Promise.
- Jest oparty na zdarzeniach, takich jak
install,activate,fetchipush.
Rejestrowanie Service Worker
Najpierw napiszmy kod rejestrujący Service Worker w przeglądarce.
1if ('serviceWorker' in navigator) {
2 window.addEventListener('load', () => {
3 navigator.serviceWorker.register('/sw.js')
4 .then(registration => {
5 console.log(
6 'Service Worker registered with scope:',
7 registration.scope
8 );
9 })
10 .catch(error => {
11 console.error('Service Worker registration failed:', error);
12 });
13 });
14}Wyjaśnienie
- Użyj
navigator.serviceWorker.register(), aby zarejestrować/sw.js(plikService Worker). - Możesz użyć
thendo obsługi sukcesu orazcatchdo obsługi błędów podczas rejestracji. registration.scopeoznacza zakres ścieżki (obszar), na który wpływaService Worker.- Domyślnie zakresem jest katalog, w którym znajduje się zarejestrowany plik (w tym przypadku
/sw.js) oraz jego podkatalogi.
Zakres działania Service Worker
Jeśli chcesz ograniczyć zakres działania, możesz określić scope jako drugi argument funkcji register.
1navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
2.then(registration => {
3 console.log(
4 'Service Worker registered with scope:',
5 registration.scope
6 );
7});Wyjaśnienie
- Przy tym ustawieniu tylko strony znajdujące się pod
/app/będą kontrolowane przezService Worker.
Tworzenie pliku Service Worker
Następnie utwórz plik o nazwie sw.js i zaimplementuj podstawowe zdarzenia.
1// sw.js
2const CACHE_NAME = 'my-cache-v1';
3const urlsToCache = [
4 '/',
5 '/index.html',
6 '/styles.css',
7 '/script.js',
8 '/offline.html'
9];Ten kod definiuje listę zasobów do zbuforowania.
Role i mechanizmy poszczególnych zdarzeń
install
1// Install event (initial caching)
2self.addEventListener('install', event => {
3 console.log('[ServiceWorker] Install');
4 event.waitUntil(
5 caches.open(CACHE_NAME).then(cache => {
6 console.log('[ServiceWorker] Caching app shell');
7 return cache.addAll(urlsToCache);
8 })
9 );
10});self.addEventListener('install')jest wywoływany, gdyService Workerjest rejestrowany po raz pierwszy. Na tym etapie niezbędne pliki są wstępnie buforowane.
activate
1// Activation event (delete old caches)
2self.addEventListener('activate', event => {
3 console.log('[ServiceWorker] Activate');
4 event.waitUntil(
5 caches.keys().then(keyList => {
6 return Promise.all(keyList.map(key => {
7 if (key !== CACHE_NAME) {
8 console.log('[ServiceWorker] Removing old cache:', key);
9 return caches.delete(key);
10 }
11 }));
12 })
13 );
14 return self.clients.claim();
15});- Podczas zdarzenia
activate, stare pamięci podręczne są usuwane w celu optymalizacji przestrzeni. Zachowywana jest tylko pamięć podręczna nowej wersji.
fetch
1// Fetch event (cache-first strategy)
2self.addEventListener('fetch', event => {
3 console.log('[ServiceWorker] Fetch', event.request.url);
4 event.respondWith(
5 caches.match(event.request).then(response => {
6 return response || fetch(event.request).catch(() => caches.match('/offline.html'));
7 })
8 );
9});Wszystkie żądania HTTP są przechwytywane — jeśli istnieje wersja w pamięci podręcznej, jest zwracana; w przeciwnym razie zostaje pobrana z sieci. W trybie offline zwracana jest strona alternatywna (np. offline.html).
Potwierdzam operację
Sprawdźmy, jak faktycznie działa Service Worker.
1document.getElementById('fetchBtn').addEventListener('click', () => {
2 fetch('/style.css')
3 .then(response => response.text())
4 .then(data => {
5 console.log('Fetched data:', data);
6 })
7 .catch(error => {
8 console.error('Fetch failed:', error);
9 });
10});- Tutaj sprawdzamy rejestrację
Service Workeroraz sposób pobierania zasobów poprzez kliknięcie przycisku testowego.
Przykłady strategii buforowania
Oto najczęściej stosowane strategie buforowania:.
Najpierw pamięć podręczna
Oto przykład implementacji strategii Najpierw pamięć podręczna:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 caches.match(event.request).then(response => {
4 return response || fetch(event.request);
5 })
6 );
7});- Ten kod implementuje strategię cache-first, gdzie żądany zasób jest zwracany z pamięci podręcznej, jeśli jest dostępny; w przeciwnym razie jest pobierany z sieci.
Najpierw sieć
Oto przykład implementacji strategii Najpierw sieć:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 fetch(event.request)
4 .then(response => {
5 return caches.open(CACHE_NAME).then(cache => {
6 cache.put(event.request, response.clone());
7 return response;
8 });
9 })
10 .catch(() => caches.match(event.request))
11 );
12});- Ten kod implementuje strategię network-first, gdzie żądany zasób jest najpierw pobierany z sieci, a jeśli to się nie powiedzie, jest pobierany z pamięci podręcznej.
Buforuj tylko style i JavaScript, uzyskuj dostęp do API w czasie rzeczywistym
Oto przykład implementacji, w której style i JavaScript są buforowane, a API są dostępne w czasie rzeczywistym:.
1self.addEventListener('fetch', event => {
2 if (event.request.url.includes('/api/')) {
3 // Fetch API responses in real-time without caching
4 return;
5 }
6
7 // Use cache-first strategy for static files
8 event.respondWith(
9 caches.match(event.request).then(response => {
10 return response || fetch(event.request);
11 })
12 );
13});- Ten kod zawsze uzyskuje dostęp do zapytań API w czasie rzeczywistym i stosuje strategię cache-first do plików statycznych, takich jak arkusze stylów i JavaScript.
Przebieg aktualizacji
Przebieg aktualizacji Service Workera wygląda następująco:.
- Wykryto nowy plik
sw.js. - Wywoływane jest zdarzenie
install. - Oczekuje, aż poprzedni
Service Workerstanie się bezczynny. - Wywoływane jest zdarzenie
activate. - Następuje przełączenie na nowego Service Worker'a.
- Zdarzenie
controllerchangezostaje wywołane.
Wykrywanie aktualizacji
Po zainstalowaniu Service Worker, stara wersja będzie używana do następnej wizyty. Aby zastosować aktualizacje, często stosuje się kod wykrywający aktualizacje i przeładowujący stronę.
1navigator.serviceWorker.addEventListener('controllerchange', () => {
2 window.location.reload();
3});- Zdarzenie
controllerchangejest wywoływane, gdy kontroler Service Workera, czyli ten, który kontroluje aktualnie otwartą stronę, ulega zmianie. - Już otwarte strony nadal korzystają z obecnego Service Workera, a nowo zainstalowany Service Worker nie działa na tych stronach natychmiastowo. Dlatego stosuje się technikę, w której zdarzenie
controllerchangesłuży do wykrycia, że nowy kontroler stał się aktywny, a następnie strona jest przeładowywana, aby natychmiast zastosować aktualizację.
Ostrzeżenia i dobre praktyki
Podczas korzystania z Service Worker należy pamiętać o następujących kwestiach:.
-
Wymagany HTTPS Ze względów bezpieczeństwa nie działa przez
http://, z wyjątkiemlocalhost. -
Skrócone nazwy plików (hashowane) Nazwa pamięci podręcznej może zawierać nazwę pliku, adres URL oraz informacje o wersji.
-
Komunikacja z klientami Użyj
postMessagedo komunikacji międzyService Workera JavaScriptem strony.
Podsumowanie
Service Worker to niezbędna technologia zapewniająca obsługę trybu offline i poprawę wydajności aplikacji internetowych. Rozumiejąc podstawowy przepływ instalacji, aktywacji i obsługi żądań, oraz wdrażając odpowiednie strategie buforowania, możesz tworzyć aplikacje internetowe wyższej jakości.
Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.