Développer des Progressive Web Apps 02.0 : démarrage rapide hors connexion

Cet atelier de programmation fait partie du cours de formation "Développer des progressive web apps", développé par l'équipe de formation Google Developers. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre.

Pour en savoir plus sur le cours, consultez la présentation du développement de progressive web apps.

Introduction

Dans cet atelier, vous allez utiliser Lighthouse pour auditer un site Web en fonction des normes des progressive web apps (PWA). Vous ajouterez également des fonctionnalités hors connexion avec l'API Service Worker.

Points abordés

  • Auditer des sites avec Lighthouse
  • Ajouter des fonctionnalités hors connexion à une application

À savoir

  • Connaissances de base en HTML, CSS et JavaScript
  • Vous maîtrisez les Promesses ES2015.

Ce dont vous avez besoin

  • Un ordinateur avec accès au terminal/shell
  • Connexion à Internet
  • Navigateur Chrome (pour utiliser Lighthouse)
  • Un éditeur de texte
  • Facultatif : Chrome sur un appareil Android

Téléchargez ou clonez le dépôt pwa-training-labs depuis GitHub et installez la version LTS de Node.js, si nécessaire.

Accédez au répertoire offline-quickstart-lab/app/ et démarrez un serveur de développement local :

cd offline-quickstart-lab/app
npm install
node server.js

Vous pouvez arrêter le serveur à tout moment avec Ctrl-c.

Ouvrez votre navigateur et accédez à localhost:8081/. Vous devriez voir que le site est une page Web simple et statique.

Remarque : Annulez l'enregistrement de tous les service workers et effacez tous les caches de service workers pour localhost afin qu'ils n'interfèrent pas avec l'atelier. Dans les outils pour les développeurs Chrome, vous pouvez y parvenir en cliquant sur Effacer les données du site dans la section Effacer le stockage de l'onglet Application.

Ouvrez le dossier offline-quickstart-lab/app/ dans l'éditeur de texte de votre choix. Le dossier app/ est celui dans lequel vous allez créer l'atelier.

Ce dossier contient :

  • Le dossier images/ contient des exemples d'images.
  • styles/main.css est la feuille de style principale.
  • index.html est la page HTML principale de notre site exemple.
  • package-lock.json et package.json suivent les dépendances de l'application (les seules dépendances dans ce cas sont celles du serveur de développement local).
  • server.js est un serveur de développement local pour les tests.
  • service-worker.js est le fichier du service worker (actuellement vide).

Avant de commencer à modifier le site, effectuons un audit avec Lighthouse pour identifier les points à améliorer.

Retournez à l'application (dans Chrome) et ouvrez l'onglet Audits des outils pour les développeurs. L'icône Lighthouse et les options de configuration devraient s'afficher. Sélectionnez "Mobile" pour Appareil, sélectionnez tous les Audits, sélectionnez l'une des options Limitation du débit, puis choisissez Effacer le stockage :

Cliquez sur Effectuer des audits. L'exécution des audits prend quelques instants.

Explication

Une fois l'audit terminé, un rapport avec des scores devrait s'afficher dans les outils de développement. Vous devriez voir des scores, comme ceci (les scores peuvent ne pas être exactement les mêmes) :

Remarque : Les scores Lighthouse sont une approximation et peuvent être influencés par votre environnement (par exemple, si vous avez un grand nombre de fenêtres de navigateur ouvertes). Il est possible que vos scores ne soient pas exactement identiques à ceux présentés ici.

La section Progressive Web App doit ressembler à ceci :

Le rapport comporte des scores et des métriques dans cinq catégories :

  • Progressive web app
  • Performances
  • Accessibilité
  • Bonnes pratiques
  • SEO

Comme vous pouvez le voir, notre application obtient un mauvais score dans la catégorie Progressive Web App (PWA). Améliorons notre score !

Prenez le temps de parcourir la section PWA du rapport et de voir ce qui manque.

Enregistrer un service worker

L'un des échecs listés dans le rapport indique qu'aucun service worker n'est enregistré. Nous avons actuellement un fichier de service worker vide à l'adresse app/service-worker.js.

Ajoutez le script suivant en bas de index.html, juste avant la balise de fermeture </body> :

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('service-worker.js')
      .then(reg => {
        console.log('Service worker registered! 😎', reg);
      })
      .catch(err => {
        console.log('😥 Service worker registration failed: ', err);
      });
  });
}
</script>

Explication

Ce code enregistre le fichier de service worker service-worker.js vide une fois la page chargée. Toutefois, le fichier de service worker actuel est vide et ne fera rien. Nous ajouterons le code de service à l'étape suivante.

Mise en cache préalable des ressources

Un autre échec listé dans le rapport est que l'application ne répond pas avec un code d'état 200 lorsqu'elle est hors connexion. Pour résoudre ce problème, nous devons mettre à jour notre service worker.

Ajoutez le code suivant au fichier du service worker (service-worker.js) :

const cacheName = 'cache-v1';
const precacheResources = [
  '/',
  'index.html',
  'styles/main.css',
  'images/space1.jpg',
  'images/space2.jpg',
  'images/space3.jpg'
];

self.addEventListener('install', event => {
  console.log('Service worker install event!');
  event.waitUntil(
    caches.open(cacheName)
      .then(cache => {
        return cache.addAll(precacheResources);
      })
  );
});

self.addEventListener('activate', event => {
  console.log('Service worker activate event!');
});

self.addEventListener('fetch', event => {
  console.log('Fetch intercepted for:', event.request.url);
  event.respondWith(caches.match(event.request)
    .then(cachedResponse => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(event.request);
      })
    );
});

Revenez maintenant dans le navigateur et actualisez le site. Vérifiez dans la console que le service worker :

  • enregistré
  • application installée
  • Activé

Remarque : Si vous avez déjà enregistré le service worker ou si vous rencontrez des difficultés pour déclencher tous les événements, désenregistrez tous les service workers et actualisez la page. Si cela ne fonctionne pas, fermez toutes les instances de l'application et rouvrez-la.

Ensuite, arrêtez le serveur de développement local dans votre ligne de commande en exécutant Ctrl + c. Actualisez à nouveau le site et constatez qu'il se charge même si le serveur est hors connexion.

Remarque : Il est possible qu'une erreur de console s'affiche pour indiquer que le service worker n'a pas pu être récupéré : An unknown error occurred when fetching the script. service-worker.js Failed to load resource: net::ERR_CONNECTION_REFUSED. Cette erreur s'affiche, car le navigateur n'a pas pu récupérer le script du service worker (car le site est hors connexion). Toutefois, cela est normal, car nous ne pouvons pas utiliser le service worker pour mettre en cache le service worker lui-même. Sinon, le navigateur de l'utilisateur serait bloqué avec le même service worker pour toujours.

Explication

Une fois le service worker enregistré par le script d'enregistrement dans index.html, l'événement install du service worker se produit. Lors de cet événement, l'écouteur d'événements install ouvre un cache nommé et met en cache les fichiers spécifiés avec la méthode cache.addAll. On parle de "pré-mise en cache" car cela se produit lors de l'événement install, qui correspond généralement à la première visite d'un utilisateur sur votre site.

Une fois qu'un service worker est installé et qu'aucun autre service worker ne contrôle actuellement la page, le nouveau service worker est "activé" (l'écouteur d'événements activate est déclenché dans le service worker) et commence à contrôler la page.

Lorsqu'une page contrôlée par un service worker activé demande des ressources, les requêtes passent par le service worker, comme un proxy réseau. Un événement fetch est déclenché pour chaque requête. Dans notre service worker, l'écouteur d'événements fetch recherche les caches et répond avec la ressource mise en cache si elle est disponible. Si la ressource n'est pas mise en cache, elle est demandée normalement.

La mise en cache des ressources permet à l'application de fonctionner hors connexion en évitant les requêtes réseau. Notre application peut désormais répondre avec un code d'état 200 en mode hors connexion.

Remarque : Dans cet exemple, l'événement d'activation n'est utilisé que pour la connexion. L'événement a été inclus pour aider à déboguer les problèmes liés au cycle de vie des service workers.

Facultatif : Vous pouvez également afficher les ressources mises en cache dans l'onglet Application des outils de développement en développant la section Cache Storage (Stockage du cache) :

Redémarrez le serveur de développement avec node server.js et actualisez le site. Ouvrez ensuite à nouveau l'onglet Audits dans les outils de développement, puis réexécutez l'audit Lighthouse en sélectionnant New Audit (Nouvel audit, le signe plus en haut à gauche). Une fois l'audit terminé, vous devriez constater que le score de notre PWA est nettement meilleur, mais qu'il peut encore être amélioré. Nous continuerons à améliorer notre score dans la section suivante.

Remarque : Cette section est facultative, car le test de la bannière d'installation de l'application Web ne fait pas partie de l'atelier. Vous pouvez essayer vous-même en utilisant le débogage à distance.

Notre score PWA n'est toujours pas excellent. Parmi les autres échecs listés dans le rapport, on trouve le fait que l'utilisateur ne sera pas invité à installer notre application Web et que nous n'avons pas configuré d'écran de démarrage ni de couleurs de marque dans la barre d'adresse. Nous pouvons résoudre ces problèmes et implémenter progressivement la fonctionnalité Ajouter à l'écran d'accueil en respectant certains critères supplémentaires. Plus important encore, nous devons créer un fichier manifeste.

Créer un fichier manifeste

Créez un fichier nommé manifest.json dans app/ et ajoutez le code suivant :

{
  "name": "Space Missions",
  "short_name": "Space Missions",
  "lang": "en-US",
  "start_url": "/index.html",
  "display": "standalone",
  "theme_color": "#FF9800",
  "background_color": "#FF9800",
  "icons": [
    {
      "src": "images/touch/icon-128x128.png",
      "sizes": "128x128"
    },
    {
      "src": "images/touch/icon-192x192.png",
      "sizes": "192x192"
    },
    {
      "src": "images/touch/icon-256x256.png",
      "sizes": "256x256"
    },
    {
      "src": "images/touch/icon-384x384.png",
      "sizes": "384x384"
    },
    {
      "src": "images/touch/icon-512x512.png",
      "sizes": "512x512"
    }
  ]
}

Les images référencées dans le fichier manifeste sont déjà fournies dans l'application.

Ajoutez ensuite le code HTML suivant en bas de la balise <head> dans index.html :

<link rel="manifest" href="manifest.json">

<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Space Missions">
<meta name="apple-mobile-web-app-title" content="Space Missions">
<meta name="theme-color" content="#FF9800">
<meta name="msapplication-navbutton-color" content="#FF9800">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="/index.html">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<link rel="icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="apple-touch-icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="icon" sizes="192x192" href="icon-192x192.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/touch/icon-192x192.png">
<link rel="icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="apple-touch-icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="apple-touch-icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="icon" sizes="512x512" href="/images/touch/icon-512x512.png">
<link rel="apple-touch-icon" sizes="512x512" href="/images/touch/icon-512x512.png">

Retournez sur le site. Dans l'onglet Application des outils de développement, sélectionnez la section Effacer le stockage, puis cliquez sur Effacer les données du site. Actualisez ensuite la page. Sélectionnez ensuite la section Manifeste. Vous devriez voir les icônes et les options de configuration définies dans le fichier manifest.json. Si vous ne voyez pas vos modifications, ouvrez le site dans une fenêtre de navigation privée et vérifiez à nouveau.

Explication

Le fichier manifest.json indique au navigateur comment styliser et mettre en forme certains aspects progressifs de votre application, tels que le chrome du navigateur, l'icône de l'écran d'accueil et l'écran de démarrage. Il peut également être utilisé pour configurer votre application Web afin qu'elle s'ouvre en mode standalone, comme une application native (c'est-à-dire en dehors du navigateur).

La compatibilité avec certains navigateurs est encore en cours de développement au moment de la rédaction de cet article. Les balises <meta> configurent un sous-ensemble de ces fonctionnalités pour certains navigateurs qui ne sont pas encore entièrement compatibles.

Nous avons dû effacer les données du site pour supprimer l'ancienne version en cache de index.html (car cette version ne contenait pas le lien vers le fichier manifeste). Exécutez un autre audit Lighthouse et voyez à quel point le score PWA s'est amélioré.

Activer l'invite d'installation

L'étape suivante pour installer notre application consiste à afficher l'invite d'installation aux utilisateurs. Chrome 67 invitait automatiquement les utilisateurs à installer l'application, mais à partir de Chrome 68, l'invite d'installation doit être activée de manière programmatique en réponse à un geste de l'utilisateur.

Ajoutez un bouton et une bannière "Installer l'application" en haut de index.html (juste après la balise <main>) avec le code suivant :

<section id="installBanner" class="banner">
    <button id="installBtn">Install app</button>
</section>

Ensuite, stylisez la bannière en ajoutant les styles suivants à styles/main.css :

.banner {
  align-content: center;
  display: none;
  justify-content: center;
  width: 100%;
}

Enregistrez le fichier. Enfin, ajoutez la balise de script suivante à index.html :

  <script>
    let deferredPrompt;
    window.addEventListener('beforeinstallprompt', event => {

      // Prevent Chrome 67 and earlier from automatically showing the prompt
      event.preventDefault();

      // Stash the event so it can be triggered later.
      deferredPrompt = event;

      // Attach the install prompt to a user gesture
      document.querySelector('#installBtn').addEventListener('click', event => {

        // Show the prompt
        deferredPrompt.prompt();

        // Wait for the user to respond to the prompt
        deferredPrompt.userChoice
          .then((choiceResult) => {
            if (choiceResult.outcome === 'accepted') {
              console.log('User accepted the A2HS prompt');
            } else {
              console.log('User dismissed the A2HS prompt');
            }
            deferredPrompt = null;
          });
      });

      // Update UI notify the user they can add to home screen
      document.querySelector('#installBanner').style.display = 'flex';
    });
  </script>

Enregistrez le fichier. Ouvrez l'application dans Chrome sur un appareil Android à l'aide du débogage à distance. Lorsque la page se charge, le bouton "Installer l'application" doit s'afficher (il ne s'affiche pas sur un ordinateur, alors assurez-vous de tester sur un appareil mobile). Cliquez sur le bouton. L'invite "Ajouter à l'écran d'accueil" devrait s'afficher. Suivez les étapes pour installer l'application sur votre appareil. Une fois l'installation terminée, vous devriez pouvoir ouvrir l'application Web en mode autonome (en dehors du navigateur) en appuyant sur l'icône de l'écran d'accueil qui vient d'être créée.

Explication

Le code HTML et CSS ajoute une bannière et un bouton masqués que nous pouvons utiliser pour permettre aux utilisateurs d'activer l'invite d'installation.

Une fois l'événement beforeinstallprompt déclenché, nous empêchons l'expérience par défaut (dans laquelle Chrome 67 et les versions antérieures invitent automatiquement les utilisateurs à installer l'application) et capturons l'événement beforeinstallevent dans la variable globale deferredPrompt. Le bouton "Installer l'application" est ensuite configuré pour afficher l'invite avec la méthode prompt() de beforeinstallevent. Une fois que l'utilisateur a fait son choix (installer ou non), la promesse userChoice est résolue avec le choix de l'utilisateur (outcome). Enfin, nous affichons le bouton d'installation une fois que tout est prêt.

Vous avez appris à auditer des sites avec Lighthouse et à implémenter les bases de la fonctionnalité hors connexion. Si vous avez suivi les sections facultatives, vous avez également appris à installer des applications Web sur l'écran d'accueil.

Autres ressources

Lighthouse est Open Source. Vous pouvez le forker, ajouter vos propres tests et signaler des bugs. Lighthouse est également disponible en tant qu'outil de ligne de commande pour l'intégration aux processus de compilation.

Pour voir tous les ateliers de programmation du cours de formation sur les PWA, consultez l'atelier de programmation de bienvenue.