`Service Worker` in JavaScript

`Service Worker` in JavaScript

Questo articolo spiega il concetto di Service Worker in JavaScript.

Spiegheremo passo dopo passo dalle basi dei Service Worker al controllo pratico della cache.

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 è una funzionalità JavaScript che si colloca tra il browser e la rete, consentendo la memorizzazione nella cache delle richieste e il supporto offline. È una tecnologia fondamentale per le PWA (Progressive Web Apps) e offre alle applicazioni web un'esperienza simile a quella di un'app nativa.

Cos’è un Service Worker?

Service Worker è un file JavaScript che viene eseguito nel thread di background del browser. Viene eseguito su un thread separato rispetto alla pagina, non può accedere all'interfaccia utente, ma può intercettare le richieste di rete, gestire la cache e gestire le notifiche push.

Le caratteristiche principali di un Service Worker includono:.

  • Funziona solo tramite HTTPS, eccetto su localhost.
  • Utilizza un'API asincrona basata su Promise.
  • È guidato dagli eventi, utilizzando eventi come install, activate, fetch e push.

Registrazione di un Service Worker

Per prima cosa, scriviamo il codice per registrare un Service Worker nel browser.

 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}

Spiegazione

  • Usa navigator.serviceWorker.register() per registrare /sw.js (il file Service Worker).
  • Puoi usare then per gestire il successo e catch per la gestione degli errori durante la registrazione.
  • registration.scope rappresenta l'intervallo di percorso (scope) interessato dal Service Worker.
  • Per impostazione predefinita, lo scope è la directory in cui si trova il file registrato (in questo caso, /sw.js) e le sue sottodirectory.

Ambito del Service Worker

Se vuoi limitare l'ambito, puoi specificare lo scope utilizzando il secondo argomento di 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});

Spiegazione

  • Con questa impostazione, solo le pagine sotto /app/ saranno controllate dal Service Worker.

Creazione del file Service Worker

Successivamente, crea un file chiamato sw.js e implementa gli eventi di base.

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

Questo codice definisce un elenco di risorse da memorizzare nella cache.

Ruoli e meccanismi di ciascun evento

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') viene attivato quando il Service Worker viene registrato per la prima volta. In questa fase, i file necessari vengono pre-memorizzati nella cache.

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});
  • Nell'evento activate, le vecchie cache vengono eliminate per ottimizzare lo spazio di archiviazione. Viene mantenuta solo la cache della nuova versione.

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

Tutte le richieste HTTP vengono intercettate: se esiste una versione in cache, viene restituita; altrimenti viene recuperata dalla rete. Quando sei offline, viene restituita una pagina alternativa (ad esempio, offline.html).

Conferma dell'operazione

Verifichiamo effettivamente come funziona il 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});
  • Qui verifichiamo la registrazione del Service Worker e il comportamento durante il recupero delle risorse facendo clic sul pulsante di prova.

Esempi di strategie di caching

Le seguenti sono strategie di caching comuni:.

Cache First (Cache prima)

Ecco un esempio di implementazione della strategia Cache First:.

1self.addEventListener('fetch', event => {
2    event.respondWith(
3        caches.match(event.request).then(response => {
4            return response || fetch(event.request);
5        })
6    );
7});
  • Questo codice implementa una strategia cache-first, in cui la risorsa richiesta viene restituita dalla cache se disponibile; in caso contrario, viene recuperata dalla rete.

Network First (Rete prima)

Ecco un esempio di implementazione della strategia Network First:.

 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});
  • Questo codice implementa una strategia network-first, in cui la risorsa richiesta viene prima recuperata dalla rete e, se questo fallisce, viene recuperata dalla cache.

Metti in cache solo gli stili e JavaScript, accedi alle API in tempo reale

Ecco un esempio di implementazione in cui stili e JavaScript vengono memorizzati nella cache mentre le API vengono accessibili in tempo reale:.

 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});
  • Questo codice accede sempre alle richieste API in tempo reale e applica una strategia cache-first ai file statici come fogli di stile e JavaScript.

Flusso di aggiornamento

Il flusso di aggiornamento di un Service Worker è il seguente:.

  1. Viene rilevato un nuovo sw.js.
  2. Viene attivato l'evento install.
  3. Attende che il precedente Service Worker diventi inattivo.
  4. Viene attivato l'evento activate.
  5. Passa al nuovo Service Worker.
  6. Viene generato l'evento controllerchange.

Rilevamento aggiornamenti

Una volta installato un Service Worker, quello vecchio continua ad essere utilizzato fino alla visita successiva. Per applicare gli aggiornamenti, è comune utilizzare un codice che rileva gli aggiornamenti e ricarica la pagina.

1navigator.serviceWorker.addEventListener('controllerchange', () => {
2    window.location.reload();
3});
  • L'evento controllerchange viene generato quando cambia il controller del Service Worker, ovvero il Service Worker che controlla la pagina corrente.
  • Le pagine già aperte continuano ad utilizzare il Service Worker attuale e il nuovo Service Worker installato non viene applicato immediatamente a queste pagine. Pertanto, viene utilizzata una tecnica in cui l'evento controllerchange viene utilizzato per rilevare che un nuovo controller è diventato attivo e la pagina viene ricaricata per applicare immediatamente l'aggiornamento.

Avvertenze e best practice

Quando utilizzi i Service Worker, tieni presenti i seguenti punti:.

  • HTTPS richiesto A causa di restrizioni di sicurezza, non funziona tramite http:// a eccezione di localhost.

  • Nomi di file con hash Il nome della cache può includere il nome del file, l’URL e le informazioni sulla versione.

  • Comunicazione con i client Utilizza postMessage per comunicare tra il Service Worker e il JavaScript della pagina.

Riepilogo

Service Worker è una tecnologia essenziale per il supporto offline e il miglioramento delle prestazioni nelle app web. Comprendendo il flusso di base di installazione, attivazione e gestione delle fetch, e implementando strategie di caching appropriate, puoi creare applicazioni web di qualità superiore.

Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.

YouTube Video