`Service Worker` i JavaScript

`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 &amp; 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 og push.

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 og catch til fejlhåndtering under registrering.
  • registration.scope repræsenterer det stiområde (scope), der påvirkes af Service 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 af Service 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år Service 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:.

  1. En ny sw.js bliver registreret.
  2. install-begivenheden udløses.
  3. Den venter, indtil den tidligere Service Worker er inaktiv.
  4. activate-begivenheden udløses.
  5. Der skiftes til den nye Service Worker.
  6. 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 mellem Service 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.

YouTube Video