Le package workbox-window
est un ensemble de modules destinés à s'exécuter dans le contexte window
, c'est-à-dire à l'intérieur de vos pages Web. Ils complètent les autres packages
de la boîte de travail s'exécutant dans le service worker.
Principales fonctionnalités et objectifs de workbox-window
:
- Simplifiez le processus d'enregistrement et de mise à jour des service workers en aidant les développeurs à identifier les moments les plus critiques de leur cycle de vie et en facilitant la réponse à ces moments.
- Pour éviter que les développeurs commettent les erreurs les plus courantes.
- Pour permettre une communication plus facile entre le code exécuté dans le service worker et le code exécuté dans la fenêtre.
Importer et utiliser workbox-window
Le principal point d'entrée du package workbox-window
est la classe Workbox
. Vous pouvez l'importer dans votre code à partir de notre CDN ou à l'aide de n'importe quel outil de regroupement JavaScript courant.
Utiliser notre CDN
Le moyen le plus simple d'importer la classe Workbox
sur votre site est d'utiliser notre CDN:
<script type="module">
import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
</script>
Notez que cet exemple utilise <script type="module">
et l'instruction import
pour charger la classe Workbox
. Vous pensez peut-être devoir transcompiler ce code pour qu'il fonctionne dans des navigateurs plus anciens, mais ce n'est en réalité pas nécessaire.
Tous les principaux navigateurs qui acceptent le service worker acceptent également les modules JavaScript natifs. Il est donc parfaitement adapté de diffuser ce code sur tous les navigateurs (les navigateurs plus anciens l'ignoreront).
Chargement de Workbox avec des bundles JavaScript
Bien qu'aucun outil ne soit requis pour utiliser workbox-window
, si votre infrastructure de développement inclut déjà un bundler comme webpack ou Rollup qui fonctionne avec les dépendances npm, vous pouvez les utiliser pour charger workbox-window
.
La première étape consiste à installer
workbox-window
en tant que dépendance de votre application:
npm install workbox-window
Ensuite, dans l'un des fichiers JavaScript de votre application, utilisez la zone de travail import
en référençant le nom du package workbox-window
:
import {Workbox} from 'workbox-window';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
Si votre bundler accepte le division du code via des instructions d'importation dynamique, vous pouvez également charger workbox-window
de manière conditionnelle, ce qui devrait aider à réduire la taille du bundle principal de votre page.
Même si workbox-window
est assez petit, il n'y a aucune raison de le charger avec la logique d'application de base de votre site, car les service workers, de par leur nature, constituent une amélioration progressive.
if ('serviceWorker' in navigator) {
const {Workbox} = await import('workbox-window');
const wb = new Workbox('/sw.js');
wb.register();
}
Concepts avancés du regroupement
Contrairement aux packages Workbox qui s'exécutent dans le service worker, les fichiers de compilation référencés par les champs main
et module
de workbox-window
dans package.json
sont transpilés dans ES5. Elles sont ainsi compatibles avec les outils de compilation actuels, dont certains ne permettent pas aux développeurs de transpiler leurs dépendances node_module
.
Si votre système de compilation vous permet de transcompiler vos dépendances (ou si vous n'avez pas besoin de transpiler votre code), il est préférable d'importer un fichier source spécifique plutôt que le package lui-même.
Voici les différentes façons d'importer Workbox
, ainsi qu'une explication de ce que chacune renvoie:
// Imports a UMD version with ES5 syntax
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');
// Imports the module version with ES5 syntax
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';
// Imports the module source file with ES2015+ syntax
import {Workbox} from 'workbox-window/Workbox.mjs';
Exemples
Une fois la classe Workbox
importée, vous pouvez l'utiliser pour enregistrer votre service worker et interagir avec celui-ci. Voici quelques exemples d'utilisation de Workbox
dans votre application:
Enregistrer un service worker et avertir l'utilisateur la première fois qu'il est actif
De nombreuses applications Web utilisent le service worker pour effectuer la mise en cache préalable des éléments afin que leur application fonctionne hors connexion lors des chargements de page suivants. Dans certains cas, il peut être judicieux d'informer l'utilisateur que l'application est désormais disponible hors connexion.
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// `event.isUpdate` will be true if another version of the service
// worker was controlling the page when this version was registered.
if (!event.isUpdate) {
console.log('Service worker activated for the first time!');
// If your service worker is configured to precache assets, those
// assets should all be available now.
}
});
// Register the service worker after event listeners have been added.
wb.register();
Avertir l'utilisateur si un service worker a été installé, mais reste en attente d'activation
Lorsqu'une page contrôlée par un service worker existant enregistre un nouveau service worker, celui-ci ne s'active par défaut que lorsque tous les clients contrôlés par le service worker initial ont été entièrement déchargés.
C'est une source de confusion courante pour les développeurs, en particulier dans les cas où l'actualisation de la page actuelle n'entraîne pas l'activation du nouveau service worker.
Pour minimiser la confusion et clarifier les circonstances, la classe Workbox
fournit un événement waiting
que vous pouvez écouter:
const wb = new Workbox('/sw.js');
wb.addEventListener('waiting', event => {
console.log(
`A new service worker has installed, but it can't activate` +
`until all tabs running the current version have fully unloaded.`
);
});
// Register the service worker after event listeners have been added.
wb.register();
Informer l'utilisateur des mises à jour du cache du package workbox-broadcast-update
Le package workbox-broadcast-update
est un excellent moyen de diffuser du contenu à partir du cache (pour une diffusion rapide) tout en étant en mesure d'informer l'utilisateur des mises à jour de ce contenu (à l'aide de la stratégie d'actualisation non actualisée).
Pour recevoir ces mises à jour à partir de la fenêtre, vous pouvez écouter les événements message
de type CACHE_UPDATED
:
const wb = new Workbox('/sw.js');
wb.addEventListener('message', event => {
if (event.data.type === 'CACHE_UPDATED') {
const {updatedURL} = event.data.payload;
console.log(`A newer version of ${updatedURL} is available!`);
}
});
// Register the service worker after event listeners have been added.
wb.register();
Envoyer au service worker une liste d'URL à mettre en cache
Pour certaines applications, il est possible de connaître tous les éléments qui doivent être mis en pré-cache au moment de la compilation, mais certaines applications diffusent des pages complètement différentes, en fonction de l'URL vers laquelle l'utilisateur est redirigé en premier.
Pour les applications de cette dernière catégorie, il peut être judicieux de ne mettre en cache que les éléments dont l'utilisateur a besoin pour la page qu'il a consultée. Lorsque vous utilisez le package workbox-routing
, vous pouvez envoyer à votre routeur une liste d'URL à mettre en cache, et il mettra en cache ces URL conformément aux règles définies sur le routeur lui-même.
Cet exemple envoie une liste d'URL chargées par la page au routeur à chaque activation d'un nouveau service worker. Notez que vous pouvez envoyer toutes les URL, car seules celles qui correspondent à un itinéraire défini dans le service worker seront mises en cache:
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// Get the current page URL + all resources the page loaded.
const urlsToCache = [
location.href,
...performance.getEntriesByType('resource').map(r => r.name),
];
// Send that list of URLs to your router in the service worker.
wb.messageSW({
type: 'CACHE_URLS',
payload: {urlsToCache},
});
});
// Register the service worker after event listeners have been added.
wb.register();
Moments importants du cycle de vie d'un service worker
Le cycle de vie d'un service worker est complexe et peut être difficile à comprendre. Cette complexité s'explique en partie par le fait qu'il doit gérer tous les cas limites pour toutes les utilisations possibles du service worker (par exemple, enregistrement de plusieurs service workers, enregistrement de différents service workers dans différents frames, enregistrement de service workers avec des noms différents, etc.).
Toutefois, la plupart des développeurs qui mettent en œuvre un service worker ne devraient pas avoir à se soucier de tous ces cas particuliers, car leur utilisation est assez simple. La plupart des développeurs n'enregistrent qu'un seul service worker par chargement de page, et ne modifient pas le nom du fichier de service worker qu'ils déploient sur leur serveur.
La classe Workbox
intègre cette vue simplifiée du cycle de vie des service workers en divisant tous les enregistrements de service worker en deux catégories: la propre à l'instance, le service worker enregistré et un service worker externe:
- Service worker enregistré: service worker ayant démarré l'installation à la suite de l'instance
Workbox
appelantregister()
, ou service worker déjà actif si l'appel deregister()
n'a pas déclenché d'événementupdatefound
lors de l'enregistrement. - Service worker externe:service worker ayant démarré l'installation indépendamment de l'instance
Workbox
, en appelantregister()
. Cela se produit généralement lorsqu'un utilisateur a ouvert une nouvelle version de votre site dans un autre onglet. Lorsqu'un événement provient d'un service worker externe, la propriétéisExternal
de l'événement est définie surtrue
.
En gardant ces deux types de service workers à l'esprit, vous trouverez ci-dessous une analyse de tous les moments importants du cycle de vie des service workers, ainsi que des recommandations pour les développeurs sur la façon de les gérer:
La toute première installation d'un service worker
Vous souhaiterez probablement traiter la toute première installation d'un service worker différemment de l'ensemble des mises à jour ultérieures.
Dans workbox-window
, vous pouvez différencier la première installation de la version des mises à jour futures en vérifiant la propriété isUpdate
sur l'un des événements suivants. Pour la toute première installation, isUpdate
sera false
.
const wb = new Workbox('/sw.js');
wb.addEventListener('installed', event => {
if (!event.isUpdate) {
// First-installed code goes here...
}
});
wb.register();
Lorsqu'une version mise à jour du service worker est trouvée
Lorsqu'un nouveau service worker lance l'installation, mais qu'une version existante contrôle actuellement la page, la propriété isUpdate
de tous les événements suivants est définie sur true
.
Votre réaction dans cette situation est généralement différente de celle de la toute première installation, car vous devez gérer quand et comment l'utilisateur obtient cette mise à jour.
Lorsqu'une version inattendue du service worker est détectée
Il peut arriver que les utilisateurs gardent votre site ouvert dans un onglet en arrière-plan pendant très longtemps. Ils peuvent même ouvrir un nouvel onglet et accéder à votre site sans se rendre compte qu'ils l'ont déjà ouvert dans un onglet en arrière-plan. Dans ce cas, il est possible que deux versions de votre site s'exécutent en même temps, ce qui peut poser des problèmes intéressants pour vous, en tant que développeur.
Imaginons que l'onglet A exécute la version 1 de votre site et l'onglet B exécutant la version 2. Lors du chargement de l'onglet B, il est contrôlé par la version de votre service worker fourni avec la version 1, mais la page renvoyée par le serveur (si vous utilisez une stratégie de mise en cache de type réseau pour vos requêtes de navigation) contiendra tous vos éléments v2.
Toutefois, ce n'est généralement pas un problème pour l'onglet B, car vous savez comment fonctionnait votre code v1 lorsque vous avez écrit votre code v2. Toutefois, cela pourrait poser problème pour l'onglet A,car votre code v1 n'aurait pas pu prédire les modifications que votre code v2 pourrait introduire.
Pour faciliter la gestion de ces situations, workbox-window
envoie également des événements de cycle de vie lorsqu'il détecte une mise à jour d'un service worker "externe", où "externe" signifie simplement toute version qui n'est pas la version enregistrée par l'instance Workbox
actuelle.
À partir de la version 6 de Workbox, ces événements sont équivalents aux événements décrits ci-dessus, avec l'ajout d'une propriété isExternal: true
définie sur chaque objet d'événement. Si votre application Web doit implémenter une logique spécifique pour gérer un service worker "externe", vous pouvez vérifier la propriété dans vos gestionnaires d'événements.
Éviter les erreurs courantes
L'une des fonctionnalités les plus utiles de Workbox est la journalisation pour les développeurs. Cela est particulièrement vrai pour workbox-window
.
Nous savons que le développement avec un service worker peut souvent être déroutant, et lorsque les choses ne vous conviennent pas, il peut être difficile d'en comprendre la raison.
Par exemple, lorsque vous modifiez votre service worker et actualisez la page, il est possible que ce changement ne s'affiche pas dans votre navigateur. La raison la plus probable est que votre service worker attend toujours d'être activé.
Toutefois, lors de l'enregistrement d'un service worker avec la classe Workbox
, vous serez informé de tous les changements d'état du cycle de vie dans la Play Console. Cela devrait vous aider à résoudre les problèmes qui se produisent.
De plus, les développeurs commettent souvent l'erreur d'enregistrer un service worker lors de leur première utilisation : enregistrer un service worker dans le mauvais champ d'application.
Pour éviter cela, la classe Workbox
vous avertit si la page qui enregistre le service worker ne fait pas partie du champ d'application de ce service worker. Vous recevez également un avertissement si votre service worker est actif, mais ne contrôle pas encore la page:
Communication entre fenêtre et service worker
L'utilisation la plus avancée d'un service worker implique un grand nombre de messages entre le service worker et la fenêtre. La classe Workbox
vous aide également en fournissant une méthode messageSW()
, qui postMessage()
renvoie le service worker enregistré de l'instance et attend une réponse.
Bien que vous puissiez envoyer des données au service worker dans n'importe quel format, le format partagé par tous les packages Workbox est un objet possédant trois propriétés (les deux dernières étant facultatives):
Les messages envoyés via la méthode messageSW()
utilisent MessageChannel
afin que le destinataire puisse y répondre. Pour répondre à un message, vous pouvez appeler event.ports[0].postMessage(response)
dans votre écouteur d'événements de message. La méthode messageSW()
renvoie une promesse qui correspond à l'élément response
avec lequel vous répondez.
Voici un exemple d'envoi de messages de la fenêtre au service worker et d'obtention d'une réponse. Le premier bloc de code est l'écouteur de messages du service worker, et le second bloc utilise la classe Workbox
pour envoyer le message et attendre la réponse:
Code dans sw.js:
const SW_VERSION = '1.0.0';
addEventListener('message', event => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
Code dans main.js (s'exécute dans la fenêtre):
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
Gérer les incompatibilités de versions
L'exemple ci-dessus montre comment implémenter la vérification de la version du service worker dans la fenêtre. Cet exemple est utilisé, car lorsque vous envoyez des messages dans les deux sens entre la fenêtre et le service worker, il est essentiel de savoir que votre service worker n'exécute peut-être pas la même version de votre site que celle que votre code de page exécute. La solution à ce problème varie selon que vous diffusez vos pages en priorité réseau ou avec le cache.
Priorité au réseau
Lorsque vous diffusez vos pages en premier sur le réseau, vos utilisateurs reçoivent toujours la dernière version de votre code HTML sur votre serveur. Toutefois, la première fois qu'un utilisateur accède à votre site pour la première fois (après le déploiement d'une mise à jour), le code HTML qu'il obtient correspond à la version la plus récente, mais le service worker exécuté dans son navigateur sera une version installée précédemment (peut-être de nombreuses versions antérieures).
Il est important de comprendre cette possibilité, car si le code JavaScript chargé par la version actuelle de votre page envoie un message à une ancienne version de votre service worker, cette version risque de ne pas savoir comment répondre (ou de répondre avec un format incompatible).
Par conséquent, il est recommandé de toujours gérer les versions de votre service worker et de vérifier les versions compatibles avant d'effectuer toute tâche critique.
Par exemple, dans le code ci-dessus, si la version du service worker renvoyée par cet appel messageSW()
est antérieure à la version attendue, il est judicieux d'attendre qu'une mise à jour soit trouvée (ce qui devrait se produire lorsque vous appelez register()
). Vous pouvez alors notifier l'utilisateur ou une mise à jour, ou ignorer la phase d'attente manuellement pour activer immédiatement le nouveau service worker.
Mettre d'abord en cache
Contrairement à la diffusion des pages avec la priorité réseau, lorsque vous diffusez d'abord le cache des pages, vous savez que votre page sera initialement toujours la même version que votre service worker (car c'est ce qui l'a diffusée). Par conséquent, vous pouvez utiliser messageSW()
sans risque immédiatement.
Toutefois, si une version mise à jour de votre service worker est détectée et s'active lorsque votre page appelle register()
(c'est-à-dire que vous ignorez intentionnellement la phase d'attente), il est possible que vous ne puissiez plus lui envoyer des messages en toute sécurité.
Une stratégie pour gérer cette possibilité consiste à utiliser un schéma de gestion des versions qui vous permet de faire la différence entre les mises à jour destructives et les mises à jour non destructives. En cas de mise à jour destructive, vous savez qu'il n'est pas sûr d'envoyer un message au service worker. À la place, vous devez avertir l'utilisateur qu'il utilise une ancienne version de la page et lui suggérer d'actualiser la page pour obtenir la mise à jour.
Ignorer l'assistant d'attente
Une convention d'utilisation courante pour l'envoi de messages de fenêtre à service worker consiste à envoyer un message {type: 'SKIP_WAITING'}
pour demander à un service worker installé d'ignorer la phase d'attente et d'activer le service.
À partir de Workbox v6, la méthode messageSkipWaiting()
peut être utilisée pour envoyer un message {type: 'SKIP_WAITING'}
au service worker en attente associé à l'enregistrement actuel du service worker. Il ne fait rien en silence s'il n'y a pas de service worker en attente.
Types
Workbox
Classe permettant de gérer l'enregistrement et les mises à jour d'un service worker, et la réponse apportée aux événements de cycle de vie d'un service worker.
Propriétés
-
constructor
void
Crée une instance Workbox avec une URL de script et des options de service worker. L'URL et les options de script sont identiques à celles utilisées lors de l'appel de navigator.serviceWorker.register(scriptURL, options).
La fonction
constructor
se présente comme suit :(scriptURL: string | TrustedScriptURL, registerOptions?: object) => {...}
-
scriptURL
chaîne | TrustedScriptURL
Script de service worker associé à cette instance. Il est possible d'utiliser un
TrustedScriptURL
. -
registerOptions
objet facultatif
-
retours
-
-
actif
Promise<ServiceWorker>
-
du contrôle
Promise<ServiceWorker>
-
getSW
void
Résolvez le problème avec une référence à un service worker correspondant à l'URL de script de cette instance, dès que celle-ci est disponible.
Si, au moment de l'enregistrement, il existe déjà un service worker actif ou en attente avec une URL de script correspondante, il est utilisé (le service worker en attente prévalant sur le service worker actif si les deux correspondent, car il est enregistré plus récemment). S'il n'y a aucun service worker correspondant actif ou en attente au moment de l'enregistrement, la promesse ne se résout pas tant qu'une mise à jour n'a pas été trouvée et que l'installation n'a pas commencé. Le service worker d'installation est alors utilisé.
La fonction
getSW
se présente comme suit :() => {...}
-
retours
Promise<ServiceWorker>
-
-
messageSW
void
Envoie l'objet de données transmis au service worker enregistré par cette instance (via
workbox-window.Workbox#getSW
) et renvoie une réponse (le cas échéant).Une réponse peut être définie dans un gestionnaire de messages du service worker en appelant
event.ports[0].postMessage(...)
, ce qui résout la promesse renvoyée parmessageSW()
. Si aucune réponse n'est définie, la promesse ne sera jamais résolue.La fonction
messageSW
se présente comme suit :(data: object) => {...}
-
données
objet
Objet à envoyer au service worker
-
retours
Promettre<tout>
-
-
messageSkipWaiting
void
Envoie un message
{type: 'SKIP_WAITING'}
au service worker dont l'état est actuellementwaiting
associé à l'enregistrement actuel.S'il n'y a pas d'enregistrement actuel ou si aucun service worker n'est
waiting
, l'appel de cette méthode n'aura aucun effet.La fonction
messageSkipWaiting
se présente comme suit :() => {...}
-
register
void
Enregistre un service worker pour l'URL de script de cette instance et les options de service worker. Par défaut, cette méthode retarde l'enregistrement jusqu'au chargement de la fenêtre.
La fonction
register
se présente comme suit :(options?: object) => {...}
-
options
objet facultatif
-
à proximité immédiate
Booléen facultatif
-
-
retours
Promise<ServiceWorkerRegistration>
-
-
update
void
Recherche les mises à jour du service worker enregistré.
La fonction
update
se présente comme suit :() => {...}
-
retours
Promise<void>
-
WorkboxEventMap
Propriétés
-
Activé
-
activation...
-
du contrôle
-
application installée
-
installer
-
message
-
redondant
-
en attente
WorkboxLifecycleEvent
Propriétés
-
isExternal
Booléen facultatif
-
isUpdate
Booléen facultatif
-
originalEvent
Événement facultatif
-
sw
ServiceWorker facultatif
-
cible
WorkboxEventTarget facultatif
-
Type
typeOperator
WorkboxLifecycleEventMap
Propriétés
-
Activé
-
activation...
-
du contrôle
-
application installée
-
installer
-
redondant
-
en attente
WorkboxLifecycleWaitingEvent
Propriétés
-
isExternal
Booléen facultatif
-
isUpdate
Booléen facultatif
-
originalEvent
Événement facultatif
-
sw
ServiceWorker facultatif
-
cible
WorkboxEventTarget facultatif
-
Type
typeOperator
-
wasWaitingBeforeRegister
Booléen facultatif
WorkboxMessageEvent
Propriétés
-
données
toutes
-
isExternal
Booléen facultatif
-
originalEvent
Événement
-
ports
typeOperator
-
sw
ServiceWorker facultatif
-
cible
WorkboxEventTarget facultatif
-
Type
"message"
Méthodes
messageSW()
workbox-window.messageSW(
sw: ServiceWorker,
data: object,
)
Envoie un objet de données à un service worker via postMessage
et renvoie une réponse (le cas échéant).
Une réponse peut être définie dans un gestionnaire de messages du service worker en appelant event.ports[0].postMessage(...)
, ce qui résout la promesse renvoyée par messageSW()
. Si aucune réponse n'est définie, la promesse ne sera pas résolue.
Paramètres
-
sw
ServiceWorker
Service worker auquel le message est envoyé
-
données
objet
Objet à envoyer au service worker.
Renvoie
-
Promettre<tout>