`Service Worker` i JavaScript

`Service Worker` i JavaScript

Den här artikeln förklarar konceptet Service Worker i JavaScript.

Vi förklarar steg för steg från grunderna i Service Worker till praktisk cachehantering.

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 är en JavaScript-funktion som står mellan webbläsaren och nätverket, vilket möjliggör cachelagring av förfrågningar och offline-stöd. Det är en kärnteknik inom PWA (Progressive Web Apps) och ger webbapplikationer en upplevelse liknande den hos en inbyggd app.

Vad är en Service Worker?

Service Worker är en JavaScript-fil som körs i webbläsarens bakgrundstråd. Den körs på en separat tråd från sidan, kan inte komma åt användargränssnittet, men kan snappa upp nätverksförfrågningar, hantera cachelagring och hantera push-notiser.

De viktigaste funktionerna i en Service Worker inkluderar följande:.

  • Den fungerar endast via HTTPS, förutom på localhost.
  • Den använder ett asynkront API baserat på Promise.
  • Den är händelsestyrd och använder event som install, activate, fetch och push.

Registrera en Service Worker

Låt oss först skriva koden för att registrera en Service Worker i webbläsaren.

 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}

Förklaring

  • Använd navigator.serviceWorker.register() för att registrera /sw.js (Service Worker-filen).
  • Du kan använda then för att hantera framgång och catch för felhantering vid registrering.
  • registration.scope representerar det sökvägsområde (scope) som påverkas av Service Worker.
  • Som standard är scopet den katalog där den registrerade filen (i detta fall /sw.js) finns samt dess underkataloger.

Service Worker-scope

Om du vill begränsa scopet kan du ange scope med det andra argumentet i 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});

Förklaring

  • Med denna inställning kommer endast sidor under /app/ att kontrolleras av Service Worker.

Skapa Service Worker-filen

Skapa sedan en fil som heter sw.js och implementera de grundläggande händelserna.

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

Den här koden definierar en lista över resurser som ska cachas.

Roller och mekanismer för varje händelse

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') utlöses när Service Worker registreras för första gången. Vid detta steg för-cachas nödvändiga filer.

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});
  • Vid activate-händelsen tas gammal cache bort för att optimera lagring. Endast cache för den nya versionen sparas.

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});

Alla HTTP-förfrågningar fångas upp—om en cachelagrad version finns returneras den, annars hämtas den från nätverket. Vid offline-läge returneras en alternativ sida (t.ex. offline.html).

Bekräftar åtgärden

Låt oss faktiskt kontrollera hur Service Worker fungerar.

 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});
  • Här kontrollerar vi registreringen av Service Worker och beteendet vid hämtning av resurser genom att klicka på testknappen.

Exempel på cache-strategier

Följande är vanliga cache-strategier:.

Cache först

Här är ett exempel på implementering för 'Cache först'-strategin:.

1self.addEventListener('fetch', event => {
2    event.respondWith(
3        caches.match(event.request).then(response => {
4            return response || fetch(event.request);
5        })
6    );
7});
  • Den här koden implementerar en cache-först-strategi, där den begärda resursen returneras från cachen om den finns tillgänglig; om inte, hämtas den från nätverket.

Nätverk först

Här är ett exempel på implementering för 'Nätverk först'-strategin:.

 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});
  • Den här koden implementerar en nätverk-först-strategi, där den begärda resursen först hämtas från nätverket och om det misslyckas, hämtas den från cachen.

Cacha endast stilmallar och JavaScript, hämta API:er i realtid

Här är ett exempel på implementering där stilmallar och JavaScript cachas medan API:er hämtas 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});
  • Den här koden hanterar alltid API-förfrågningar i realtid och tillämpar en cache-först-strategi för statiska filer som stilmallar och JavaScript.

Uppdateringsflöde

Uppdateringsflödet för en Service Worker är följande:.

  1. En ny sw.js upptäcks.
  2. install-händelsen startas.
  3. Det väntar tills föregående Service Worker blir overksam.
  4. activate-händelsen startas.
  5. Den byter till den nya Service Worker.
  6. Händelsen controllerchange utlöses.

Uppdateringsdetektering

När en Service Worker är installerad fortsätter den gamla användas tills nästa besök. För att tillämpa uppdateringar är det vanligt att använda kod som upptäcker uppdateringar och laddar om sidan.

1navigator.serviceWorker.addEventListener('controllerchange', () => {
2    window.location.reload();
3});
  • controllerchange-händelsen utlöses när kontrollern för Service Workern, det vill säga Service Workern som styr den aktuella sidan, ändras.
  • Sidor som redan är öppna fortsätter att använda den nuvarande Service Workern, och den nyinstallerade Service Workern påverkar inte dessa sidor omedelbart. Därför används en teknik där controllerchange-händelsen används för att upptäcka att en ny controller har blivit aktiv och då laddas sidan om för att omedelbart tillämpa uppdateringen.

Varningar och bästa praxis

När du använder Service Worker, tänk på följande punkter:.

  • HTTPS krävs På grund av säkerhetsbegränsningar fungerar det inte via http:// förutom på localhost.

  • Hashade filnamn Cachens namn kan inkludera filnamn, URL och versionsinformation.

  • Kommunikation med klienter Använd postMessage för att kommunicera mellan Service Worker och sidans JavaScript.

Sammanfattning

Service Worker är en viktig teknik för offline-stöd och prestandaförbättringar i webbappar. Genom att förstå det grundläggande flödet av installation, aktivering och hantering av hämtningar samt implementera lämpliga cache-strategier kan du bygga webbapplikationer av högre kvalitet.

Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.

YouTube Video