`Service Worker` i JavaScript
Denne artikel forklarer konceptet Service Worker
i JavaScript.
Vi vil forklare trin for trin fra det grundlæggende i Service Worker
til praktisk cache-kontrol.
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
i JavaScript
Service Worker
er en JavaScript-funktion, der står mellem browseren og netværket og muliggør caching af forespørgsler og offline support. Det er en kerne-teknologi i PWA'er (Progressive Web Apps) og bringer en oplevelse, der ligner native apps, til webapplikationer.
Hvad er en Service Worker
?
Service Worker
er en JavaScript-fil, der kører i browserens baggrundstråd. Den kører på en separat tråd fra siden, kan ikke få adgang til brugerfladen, men kan opsnappe netværksforespørgsler, håndtere caching og håndtere push-notifikationer.
Nøglefunktionerne i en Service Worker
inkluderer følgende:.
- Den fungerer kun over HTTPS, medmindre det er på localhost.
- Den bruger et Promise-baseret asynkront API.
- Den er hændelsesstyret og bruger events som
install
,activate
,fetch
ogpush
.
Registrering af en Service Worker
Lad os først skrive koden til at registrere en Service Worker
i browseren.
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}
Forklaring
- Brug
navigator.serviceWorker.register()
for at registrere/sw.js
(Service Worker
-filen). - Du kan bruge
then
til håndtering af succes ogcatch
til fejlhåndtering under registrering. registration.scope
repræsenterer det stiområde (scope), der påvirkes afService Worker
.- Som standard er scope den mappe, hvor den registrerede fil (i dette tilfælde
/sw.js
) er placeret samt dens undermapper.
Service Worker
-scope
Hvis du vil begrænse scope, kan du angive scope
ved at bruge det andet argument til 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});
Forklaring
- Med denne indstilling vil kun sider under
/app/
blive kontrolleret afService Worker
.
Oprettelse af Service Worker
-filen
Opret derefter en fil med navnet sw.js
og implementér de grundlæggende events.
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];
Denne kode definerer en liste over ressourcer, der skal cachelagres.
Roller og mekanismer for hver begivenhed
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')
udløses, nårService Worker
registreres for første gang. På dette tidspunkt bliver nødvendige filer cachet på forhånd.
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});
- I
activate
-begivenheden bliver gamle caches slettet for at optimere lagerpladsen. Kun cachen for den nye version bevares.
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});
Alle HTTP-forespørgsler opsnappes—hvis der findes en cached version, returneres den; ellers hentes den fra netværket. Når man er offline, returneres en alternativ side (f.eks. offline.html
).
Bekræfter handlingen
Lad os faktisk kontrollere, hvordan Service Worker
fungerer.
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});
- Her tjekker vi registreringen af
Service Worker
og adfærden ved hentning af ressourcer ved at klikke på testknappen.
Eksempler på caching-strategier
Følgende er almindelige caching-strategier:.
Cache først
Her er et eksempel på implementering af Cache First-strategien:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 caches.match(event.request).then(response => {
4 return response || fetch(event.request);
5 })
6 );
7});
- Denne kode implementerer en cache-først strategi, hvor den forespurgte ressource returneres fra cachen, hvis den er tilgængelig; hvis ikke, hentes den fra netværket.
Netværk først
Her er et eksempel på implementering af Network First-strategien:.
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});
- Denne kode implementerer en netværk-først strategi, hvor den forespurgte ressource hentes fra netværket først, og hvis dette mislykkes, hentes den fra cachen.
Cache kun styles og JavaScript, tilgå API'er i realtid
Her er et eksempel på implementering, hvor styles og JavaScript caches, mens API'er tilgås i realtid:.
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});
- Denne kode tilgår altid API-forespørgsler i realtid og anvender en cache-først strategi på statiske filer såsom stylesheets og JavaScript.
Opdateringsforløb
Opdateringsforløbet for en Service Worker er som følger:.
- En ny
sw.js
bliver registreret. install
-begivenheden udløses.- Den venter, indtil den tidligere
Service Worker
er inaktiv. activate
-begivenheden udløses.- Der skiftes til den nye Service Worker.
controllerchange
-begivenheden udløses.
Opdateringsdetektion
Når en Service Worker
er installeret, bruges den gamle fortsat indtil næste besøg. For at anvende opdateringer er det almindeligt at bruge kode, der registrerer opdateringer og genindlæser siden.
1navigator.serviceWorker.addEventListener('controllerchange', () => {
2 window.location.reload();
3});
controllerchange
-begivenheden udløses, når controlleren for Service Workeren, altså Service Workeren der styrer den aktuelle side, ændres.- Sider, der allerede er åbne, fortsætter med at bruge den nuværende Service Worker, og den nyinstallerede Service Worker træder ikke straks i kraft på disse sider. Derfor anvendes en metode, hvor
controllerchange
-begivenheden bruges til at registrere, at en ny controller er blevet aktiv, hvorefter siden genindlæses for straks at anvende opdateringen.
Advarsler og bedste praksis
Når du bruger Service Worker
, skal du være opmærksom på følgende:.
-
HTTPS påkrævet På grund af sikkerhedsbegrænsninger virker det ikke over
http://
, undtagen pålocalhost
. -
Hashade filnavne Cachenavnet kan inkludere filnavn, URL og versionsinformation.
-
Kommunikation med klienter Brug
postMessage
til at kommunikere mellemService Worker
og sidens JavaScript.
Sammendrag
Service Worker
er en essentiel teknologi til offline-support og ydelsesforbedringer i webapplikationer. Ved at forstå det grundlæggende forløb af installation, aktivering og håndtering af fetch samt implementering af passende caching-strategier, kan du bygge webapplikationer af højere kvalitet.
Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.