`Service Worker` in JavaScript

`Service Worker` in JavaScript

Dit artikel legt het concept van Service Worker in JavaScript uit.

We leggen stap voor stap uit, van de basis van Service Worker tot praktische cachecontrole.

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 in JavaScript

Service Worker is een JavaScript-functie die zich tussen de browser en het netwerk bevindt, waardoor verzoeken kunnen worden gecached en offline-ondersteuning mogelijk is. Het is een kerntechnologie van PWA's (Progressive Web Apps) en biedt een app-achtige ervaring aan webapplicaties.

Wat is een Service Worker?

Service Worker is een JavaScript-bestand dat draait in de achtergrondthread van de browser. Het draait op een aparte thread van de pagina, heeft geen toegang tot de UI, maar kan netwerkverzoeken onderscheppen, caching beheren en pushmeldingen verwerken.

De belangrijkste kenmerken van een Service Worker zijn onder andere:.

  • Het werkt alleen over HTTPS, behalve op localhost.
  • Het gebruikt een Promise-gebaseerde asynchrone API.
  • Het is gebeurtenisgestuurd, met gebeurtenissen zoals install, activate, fetch en push.

Service Worker registreren

Laten we eerst de code schrijven om een Service Worker in de browser te registreren.

 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}

Uitleg

  • Gebruik navigator.serviceWorker.register() om /sw.js (het Service Worker-bestand) te registreren.
  • Je kunt then gebruiken voor het afhandelen van succes en catch voor het afhandelen van fouten tijdens de registratie.
  • registration.scope vertegenwoordigt het padbereik (scope) dat door de Service Worker wordt beïnvloed.
  • Standaard is de scope de map waarin het geregistreerde bestand zich bevindt (in dit geval /sw.js) en de submappen daarvan.

Service Worker-scope

Als je de scope wilt beperken, kun je de scope opgeven met het tweede argument van 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});

Uitleg

  • Met deze instelling worden alleen pagina’s onder /app/ beheerd door de Service Worker.

Het Service Worker-bestand aanmaken

Maak vervolgens een bestand genaamd sw.js aan en implementeer de basisevenementen.

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];

Deze code definieert een lijst met resources die in de cache moeten worden opgeslagen.

Rollen en mechanismen van elk evenement

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') wordt geactiveerd wanneer de Service Worker voor de eerste keer wordt geregistreerd. In deze fase worden de benodigde bestanden vooraf in de cache geplaatst.

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});
  • Tijdens het activate-evenement worden oude caches verwijderd om opslag te optimaliseren. Alleen de cache van de nieuwe versie blijft behouden.

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-verzoeken worden onderschept: als er een gecachte versie bestaat, wordt deze teruggegeven; anders wordt deze van het netwerk opgehaald. Als je offline bent, wordt een alternatieve pagina (bijvoorbeeld offline.html) teruggegeven.

De werking bevestigen

Laten we daadwerkelijk controleren hoe de Service Worker werkt.

 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});
  • Hier controleren we de registratie van de Service Worker en het ophalen van middelen door op de testknop te klikken.

Voorbeelden van cachingstrategieën

De volgende zijn veelgebruikte cachingstrategieën:.

Cache First

Hier is een voorbeeldimplementatie voor de Cache First-strategie:.

1self.addEventListener('fetch', event => {
2    event.respondWith(
3        caches.match(event.request).then(response => {
4            return response || fetch(event.request);
5        })
6    );
7});
  • Deze code implementeert een cache-first strategie, waarbij de opgevraagde bron uit de cache wordt teruggegeven als deze beschikbaar is; zoniet, wordt de bron van het netwerk gehaald.

Network First

Hier is een voorbeeldimplementatie voor de Network First-strategie:.

 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});
  • Deze code implementeert een network-first strategie, waarbij de opgevraagde bron eerst van het netwerk wordt gehaald en, als dat mislukt, uit de cache wordt opgehaald.

Cache alleen stijlen en JavaScript, benader API's in realtime

Hier is een voorbeeldimplementatie waarbij stijlen en JavaScript worden gecached, terwijl API's in realtime worden benaderd:.

 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});
  • Deze code maakt altijd real-time verbinding met API-aanvragen en past een cache-first strategie toe op statische bestanden zoals stylesheets en JavaScript.

Update-proces

Het updateproces van een Service Worker verloopt als volgt:.

  1. Een nieuwe sw.js wordt gedetecteerd.
  2. Het install-evenement wordt geactiveerd.
  3. Er wordt gewacht tot de vorige Service Worker geen taken meer heeft.
  4. Het activate-evenement wordt geactiveerd.
  5. Er wordt overgeschakeld naar de nieuwe Service Worker.
  6. Het controllerchange-event wordt getriggerd.

Update-detectie

Zodra een Service Worker is geïnstalleerd, blijft de oude tot het volgende bezoek in gebruik. Om updates toe te passen, is het gebruikelijk om code te gebruiken die updates detecteert en de pagina vernieuwt.

1navigator.serviceWorker.addEventListener('controllerchange', () => {
2    window.location.reload();
3});
  • Het controllerchange-event wordt getriggerd wanneer de controller van de Service Worker, dus de Service Worker die de huidige pagina bestuurt, verandert.
  • Pagina's die al openstaan, blijven de huidige Service Worker gebruiken en de nieuw geïnstalleerde Service Worker wordt niet direct actief op die pagina's. Daarom wordt een techniek gebruikt waarbij het controllerchange-event wordt gebruikt om te detecteren dat er een nieuwe controller actief is geworden; vervolgens wordt de pagina vernieuwd om de update onmiddellijk toe te passen.

Let op en best practices

Houd bij het gebruik van een Service Worker rekening met het volgende:.

  • HTTPS vereist Vanwege beveiligingsbeperkingen werkt het niet via http:// behalve op localhost.

  • Gehashte bestandsnamen De cchenaam kan de bestandsnaam, URL en versiesinformatie bevatten.

  • Communicatie met clients Gebruik postMessage om te communiceren tussen de Service Worker en de JavaScript van de pagina.

Samenvatting

Service Worker is een essentiële technologie voor offline-ondersteuning en prestatieverbeteringen in webapps. Door het basisproces van installatie, activatie en verwerking van fetch te begrijpen en geschikte cachingstrategieën te implementeren, kun je webapplicaties van hogere kwaliteit bouwen.

Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.

YouTube Video