`Service Worker` en JavaScript

`Service Worker` en JavaScript

Cet article explique le concept de Service Worker en JavaScript.

Nous expliquerons étape par étape les bases du Service Worker jusqu’au contrôle pratique du 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 en JavaScript

Service Worker est une fonctionnalité JavaScript qui se situe entre le navigateur et le réseau, permettant la mise en cache des requêtes et la prise en charge hors ligne. C’est une technologie clé des PWA (Progressive Web Apps) et elle apporte une expérience similaire à une application native aux applications web.

Qu’est-ce qu’un Service Worker ?

Service Worker est un fichier JavaScript qui s’exécute dans le thread d’arrière-plan du navigateur. Il s’exécute sur un thread séparé de la page, ne peut pas accéder à l’interface utilisateur, mais peut intercepter les requêtes réseau, gérer la mise en cache et traiter les notifications push.

Les principales fonctionnalités d’un Service Worker incluent :.

  • Il ne fonctionne que via HTTPS, sauf sur localhost.
  • Il utilise une API asynchrone basée sur les Promises.
  • Il est basé sur des événements, utilisant des événements comme install, activate, fetch, et push.

Enregistrer un Service Worker

Tout d’abord, écrivons le code pour enregistrer un Service Worker dans le navigateur.

 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}

Explication

  • Utilisez navigator.serviceWorker.register() pour enregistrer /sw.js (le fichier du Service Worker).
  • Vous pouvez utiliser then pour la gestion du succès et catch pour la gestion des erreurs lors de l’enregistrement.
  • registration.scope représente l’étendue (scope) du chemin affectée par le Service Worker.
  • Par défaut, le scope correspond au répertoire où se trouve le fichier enregistré (dans ce cas, /sw.js) et à ses sous-répertoires.

Périmètre du Service Worker

Si vous souhaitez limiter le scope, vous pouvez le spécifier avec le deuxième argument de 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});

Explication

  • Avec ce paramètre, seules les pages sous /app/ seront contrôlées par le Service Worker.

Créer le fichier Service Worker

Ensuite, créez un fichier nommé sw.js et implémentez les événements de 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];

Ce code définit une liste de ressources à mettre en cache.

Rôles et mécanismes de chaque événement

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') est déclenché lors du premier enregistrement du Service Worker. À ce stade, les fichiers nécessaires sont pré-cacheés.

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});
  • Lors de l’événement activate, les anciens caches sont supprimés pour optimiser le stockage. Seul le cache de la nouvelle version est conservé.

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

Toutes les requêtes HTTP sont interceptées—si une version en cache existe, elle est renvoyée ; sinon, elle est récupérée depuis le réseau. En mode hors ligne, une page alternative (par ex., offline.html) est renvoyée.

Confirmation de l’opération

Voyons concrètement comment fonctionne le 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});
  • Ici, nous vérifions l’enregistrement du Service Worker et le comportement de la récupération des ressources en cliquant sur le bouton de test.

Exemples de stratégies de mise en cache

Voici des stratégies de mise en cache courantes :.

Cache d’abord (Cache First)

Voici un exemple d’implémentation pour la stratégie 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});
  • Ce code implémente une stratégie « cache-first », où la ressource demandée est renvoyée depuis le cache si elle est disponible ; sinon, elle est récupérée depuis le réseau.

Réseau d’abord (Network First)

Voici un exemple d’implémentation pour la stratégie 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});
  • Ce code implémente une stratégie « network-first », où la ressource demandée est d'abord récupérée depuis le réseau, et en cas d'échec, elle est obtenue à partir du cache.

Mettre en cache uniquement les styles et JavaScript, accéder aux APIs en temps réel

Voici un exemple d’implémentation où les fichiers de styles et JavaScript sont mis en cache, tandis que les APIs sont consultées en temps réel :.

 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});
  • Ce code accède toujours aux requêtes API en temps réel et applique une stratégie « cache-first » aux fichiers statiques tels que les feuilles de style et les fichiers JavaScript.

Processus de mise à jour

Le processus de mise à jour d’un Service Worker est le suivant :.

  1. Un nouveau sw.js est détecté.
  2. L’événement install est déclenché.
  3. Le système attend que l’ancien Service Worker soit inactif.
  4. L’événement activate est déclenché.
  5. Il passe au nouveau Service Worker.
  6. L’événement controllerchange est déclenché.

Détection de mise à jour

Une fois un Service Worker installé, l’ancien continue d’être utilisé jusqu’à la prochaine visite. Pour appliquer les mises à jour, il est courant d’utiliser un code qui détecte les mises à jour et recharge la page.

1navigator.serviceWorker.addEventListener('controllerchange', () => {
2    window.location.reload();
3});
  • L’événement controllerchange est déclenché lorsque le contrôleur du Service Worker, c’est-à-dire le Service Worker contrôlant la page actuelle, change.
  • Les pages déjà ouvertes continuent d’utiliser le Service Worker actuel, et le nouveau Service Worker installé ne prend pas effet immédiatement sur ces pages. Par conséquent, une technique consiste à utiliser l’événement controllerchange pour détecter qu’un nouveau contrôleur est actif, puis à recharger la page pour appliquer immédiatement la mise à jour.

Précautions et bonnes pratiques

Lorsque vous utilisez un Service Worker, gardez les points suivants à l’esprit :.

  • HTTPS requis En raison de restrictions de sécurité, il ne fonctionne pas via http:// sauf sur localhost.

  • Noms de fichiers hachés Le nom du cache peut inclure le nom du fichier, l’URL et des informations de version.

  • Communication avec les clients Utilisez postMessage pour communiquer entre le Service Worker et le JavaScript de la page.

Résumé

Service Worker est une technologie essentielle pour la prise en charge hors ligne et l’amélioration des performances des applications web. En comprenant le déroulement de l’installation, de l’activation et de la gestion des requêtes, puis en mettant en œuvre des stratégies de mise en cache appropriées, vous pouvez développer des applications web de meilleure qualité.

Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.

YouTube Video