Créer une PWA chez Google (1re partie)

Ce que l'équipe Bulletin a appris sur les service workers lors du développement d'une PWA

Douglas Parker
Douglas Parker
Joel Riley
Joel Riley
Dikla Cohen
Dikla Cohen

Ce message est le premier d'une série d'articles de blog sur les enseignements tirés par l'équipe Google Bulletin tout en créant une PWA externe. Dans ces publications, nous partagerons certains des défis auxquels nous avons été confrontés, les approches que nous avons adoptées pour les surmonter et des conseils généraux pour éviter les pièges. C'est par non signifie une vue d'ensemble complète des PWA. L'objectif est de partager les enseignements tirés de l'expérience de notre équipe.

Pour ce premier article, nous aborderons d'abord quelques informations de base, puis nous examinerons toutes sur les service workers.

Contexte

Bulletin a été activement développé entre mi-2017 et mi-2019.

Pourquoi nous avons choisi de créer une PWA

Avant d'examiner en détail le processus de développement, voyons pourquoi la création d'une PWA était intéressante. pour ce projet:

  • Capacité à effectuer des itérations rapidement : Particulièrement utile puisque Bulletin serait testé dans plusieurs marchés.
  • Code base unique : Nos utilisateurs étaient répartis à peu près à parts égales entre Android et iOS. Une PWA signifiait nous pourrions créer une seule application web qui fonctionnerait sur les deux plateformes. Cela a augmenté la vitesse et de l'impact de l'équipe.
  • Mises à jour rapides, indépendamment du comportement des utilisateurs : Les PWA peuvent automatiquement mettre à jour réduit le nombre de clients obsolètes dans la nature. Nous avons pu déployer la rupture du backend les modifications avec un temps de migration très court pour les clients.
  • Intégration facile aux applications propriétaires et tierces Ces intégrations étaient indispensables pour l'application. Une PWA revient souvent à ouvrir une URL.
  • Il n'est plus facile d'installer une application.

Notre cadre

Pour Bulletin, nous avons utilisé Polymer, mais toute version moderne et compatible le framework fonctionnera.

Ce que nous avons appris sur les service workers

Vous ne pouvez pas avoir de PWA sans service ou un nœud de calcul. Service workers des fonctionnalités avancées, telles que des stratégies avancées de mise en cache, des fonctionnalités hors connexion, la synchronisation en arrière-plan, Bien que les service workers ajoutent une certaine complexité, nous avons constaté que leurs avantages l'emportent sur les avantages supplémentaires la complexité.

Générez-le si vous le pouvez

Évitez d'écrire manuellement le script d'un service worker. L'écriture manuelle des service workers nécessite la gestion des ressources mises en cache et la logique de réécriture commune à la plupart des bibliothèques de service workers, Workbox.

Cela dit, en raison de notre pile technologique interne, nous ne pouvions pas utiliser de bibliothèque pour générer et gérer notre service worker. Les enseignements que nous en avons tirés ci-dessous peuvent parfois en tenir compte. Accédez aux pièges liés aux service workers non générés pour en savoir plus.

Toutes les bibliothèques ne sont pas compatibles avec un nœud de calcul de service

Certaines bibliothèques JavaScript reposent sur des hypothèses qui ne fonctionnent pas comme prévu lorsqu'elles sont exécutées par un service worker. Pour en supposant que window ou document sont disponibles, ou que vous utilisez une API non disponible pour le service (XMLHttpRequest, stockage local, etc.). Assurez-vous que toutes les bibliothèques essentielles sont compatibles avec un service worker. Pour cette PWA en particulier, nous voulions utiliser gapi.js pour l'authentification, mais étaient car il n'est pas compatible avec les service workers. Les auteurs de bibliothèques doivent également réduire ou supprimer des hypothèses inutiles sur le contexte JavaScript lorsque cela est possible pour permettre l'utilisation des service workers dans certains cas, par exemple en évitant les API incompatibles avec un service worker et en évitant les opérations l'état.

Éviter d'accéder à IndexedDB pendant l'initialisation

Ne lisez pas IndexedDB lorsque Initialisez le script de votre service worker. Dans le cas contraire, vous pouvez rencontrer ce problème:

  1. L'utilisateur dispose d'une application Web avec IndexedDB (IDB) version N
  2. La nouvelle application Web est publiée avec la version N+1 de l'IDB
  3. L'utilisateur visite la PWA, ce qui déclenche le téléchargement d'un nouveau service worker
  4. Le nouveau service worker lit l'IDB avant d'enregistrer le gestionnaire d'événements install, déclenchant ainsi une Cycle de mise à niveau de la IdP pour passer de N à N+1
  5. Étant donné que l'utilisateur dispose d'un ancien client avec la version N, le processus de mise à niveau d'un service worker se bloque en tant qu'actif. les connexions restent ouvertes à l'ancienne version de la base de données
  6. Le service worker se bloque et ne s'installe jamais

Dans notre cas, le cache a été invalidé lors de l'installation d'un service worker. Par conséquent, si le service worker n'a jamais installé, les utilisateurs n'ont jamais reçu la mise à jour de l'application.

Résilience

Bien que les scripts de service worker s'exécutent en arrière-plan, ils peuvent également être arrêtés à tout moment, même lors d'opérations d'E/S (réseau, IDB, etc.). Tout processus de longue durée doit être avec reprise à tout moment.

Dans le cas d’un processus de synchronisation qui a importé des fichiers volumineux sur le serveur et enregistré dans IDB, notre solution des importations partielles interrompues a consisté à exploiter le service avec reprise de notre bibliothèque interne enregistre l'URL d'importation avec reprise dans l'IDB avant l'importation et utilise cette URL pour reprendre une mettre en ligne s'il n'a pas terminé la première fois. Avant toute opération d'E/S de longue durée, a été enregistré dans IDB pour indiquer où nous en étions dans le processus pour chaque enregistrement.

Ne pas dépendre de l'état global

Étant donné que les service workers existent dans un contexte différent, de nombreux symboles auxquels on peut s'attendre à l'heure actuelle. Une grande partie de notre code s'exécutait à la fois dans un contexte window et dans un contexte de service worker (tel que comme la journalisation, les options, la synchronisation, etc.). Le code doit être défensif vis-à-vis des services qu'il utilise, par exemple le stockage local ou les cookies. Vous pouvez utiliser globalThis pour faire référence à l'objet global d'une manière qui fonctionne dans tous les contextes. Utiliser aussi les données stockées avec parcimonie, car il n'y a aucune garantie quant au moment où le script sera arrêté et l'État évincé.

Développement local

L'un des composants majeurs des service workers consiste à mettre en cache les ressources localement. Cependant, au cours du développement, est exactement l'inverse de ce que vous souhaitez, en particulier lorsque les mises à jour sont effectuées de manière différée. Vous souhaitez toujours le serveur worker installé afin que vous puissiez le déboguer ou travailler avec d'autres API comme la synchronisation en arrière-plan ou les notifications. Pour cela, utilisez les outils pour les développeurs Chrome : en cochant la case Ignorer pour le réseau (panneau Application > volet Service workers, cochez la case Désactiver le cache dans le panneau Réseau pour que désactiver le cache de la mémoire. Afin de couvrir davantage de navigateurs, nous avons opté pour une autre solution en avec un indicateur permettant de désactiver la mise en cache dans notre service worker, qui est activé par défaut sur les comptes de développement compilations. Les développeurs bénéficient ainsi toujours de leurs modifications les plus récentes, sans problème de mise en cache. Il est important d'inclure également l'en-tête Cache-Control: no-cache pour empêcher le navigateur et mettre en cache des éléments.

Phare

Lighthouse propose différents outils de débogage utiles pour les PWA. Il analyse un site et génère des rapports sur les PWA, les performances, l'accessibilité, le SEO et d'autres bonnes pratiques. Nous vous recommandons d'exécuter Lighthouse en continu intégration pour vous avertir si vous ne respectez pas l'un des pour devenir une PWA. Cela nous est arrivé une fois, lorsque le service worker n'installait pas et nous ne l’avions pas réalisé avant de passer en production. Le fait d'avoir Lighthouse dans notre intégration continue aurait l'ont empêché.

Adopter la livraison continue

Comme les service workers peuvent se mettre à jour automatiquement, les utilisateurs ne peuvent pas limiter les mises à niveau. Ce permet de réduire considérablement le nombre de clients obsolètes. Lorsque l'utilisateur a ouvert notre application, le service worker diffuserait l'ancien client alors qu'il téléchargeait le nouveau client en différé. Une fois que téléchargé un nouveau client, il serait invité à actualiser la page pour accéder aux nouvelles fonctionnalités. Même si l'utilisateur a ignoré cette demande. La prochaine fois qu'il actualiserait la page, il recevrait le nouveau du client. Il est donc assez difficile pour un utilisateur de refuser les mises à jour pour les applications iOS/Android.

Nous avons pu déployer des modifications destructives du backend en un temps de migration très court pour clients. En règle générale, nous accordons un mois aux utilisateurs pour effectuer une mise à jour vers les nouveaux clients avant de faire des modifications destructives. Étant donné que l'application était diffusée alors qu'elle n'était pas actualisée, il était possible pour les clients plus anciens d’exister dans la nature si l’utilisateur n’avait pas ouvert l’application depuis longtemps. Sur iOS, les service workers sont évincée au bout de quelques semaines donc ce cas n'arrive pas. Pour Android, ce problème pourrait être atténué en ne diffusant pas qu'ils soient obsolètes ou qu'ils arrivent à expiration manuellement au bout de quelques semaines. En pratique, nous n'avons jamais rencontré des clients obsolètes. Le niveau de rigueur d'une équipe donnée dépend de son utilisation spécifique. mais les PWA offrent beaucoup plus de flexibilité que les applications iOS/Android.

Obtenir les valeurs des cookies dans un service worker

Il est parfois nécessaire d'accéder aux valeurs des cookies dans le contexte d'un service worker. Dans notre cas, nous pour accéder aux valeurs des cookies afin de générer un jeton permettant d'authentifier les requêtes API propriétaires. Dans un les API synchrones telles que document.cookies ne sont pas disponibles. Vous pouvez toujours envoyer un un message du service worker aux clients actifs (en mode fenêtré) pour demander les valeurs des cookies, le service worker peut s'exécuter en arrière-plan sans passer par des clients disponibles, par exemple lors d'une synchronisation en arrière-plan. Pour contourner ce problème, nous avons créé un point de terminaison frontend qui renvoie simplement la valeur du cookie au client. Le service worker a effectué la requête réseau à ce point de terminaison et lire la réponse pour obtenir les valeurs des cookies.

Avec le lancement API Cookie Store cette solution de contournement ne devrait plus être nécessaire pour les navigateurs compatibles, car elle fournit un accès asynchrone aux cookies du navigateur et peut être utilisé directement par le service worker.

Pièges pour les service workers non générés

Assurez-vous que le script de service worker change en cas de modification d'un fichier statique mis en cache

Un modèle de PWA courant consiste à installer tous les fichiers d'application statiques La phase install, qui permet aux clients d'appeler directement le cache de l'API Cache Storage pour tous visites suivantes . Les service workers sont installés uniquement lorsque le navigateur détecte que le service Le script n'a pas changé. Nous avons donc dû nous assurer que le fichier de script du service worker lui-même modifié d’une manière ou d’une autre lorsqu’un fichier mis en cache a été modifié. Nous l'avons fait manuellement en intégrant un hachage de fichiers de ressources statiques dans le script Service Worker. Ainsi, chaque version produit un ensemble distinct fichier JavaScript de service worker. Les bibliothèques de service workers telles que Workbox automatise ce processus.

Tests unitaires

Les API Service Workers fonctionnent en ajoutant des écouteurs d'événements à l'objet global. Exemple :

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

Cela peut être pénible à tester, car vous devez simuler le déclencheur d'événement, l'objet événement, attendre le rappel respondWith(), puis attendez la promesse avant d'effectuer une assertion sur le résultat. Une la plus simple consiste à déléguer toute l'implémentation dans un autre fichier, ce qui est plus facile. sont testées.

import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));

Comme il était difficile de réaliser des tests unitaires sur un script de service worker, nous avons conservé le service worker principal le script le plus simple possible, en divisant la majeure partie de l'implémentation en d'autres modules. Depuis ces fichiers n'étaient que des modules JS standards, ils peuvent donc être plus facilement testés bibliothèques.

Restez à l'écoute pour les parties 2 et 3

Dans les parties 2 et 3 de cette série, nous aborderons la gestion multimédia et les problèmes spécifiques à iOS. Si vous si vous souhaitez nous en savoir plus sur la création d'une PWA chez Google, consultez nos profils d'auteurs comment nous contacter: