Fermer

novembre 27, 2018

Un guide complet pour les applications Web progressives


À propos de l'auteur

Ingénieur en logiciel, essayant de donner un sens à chaque ligne de code qu'elle écrit. Ankita est un passionné de JavaScript et adore ses parties étranges. Elle est aussi une obsédée…
Pour en savoir plus sur Ankita

Dans cet article, nous examinerons les points douloureux des utilisateurs qui naviguent sur d'anciens sites Web non-PWA et la promesse des PWA de rendre le Web grand. Vous apprendrez la plupart des technologies importantes permettant de créer des PWA géniaux, comme les techniciens de maintenance, les notifications push Web et IndexedDB.

C’était l’anniversaire de mon père et je voulais lui commander un gâteau au chocolat et une chemise. Je me suis dirigé vers Google pour rechercher des gâteaux au chocolat et j'ai cliqué sur le premier lien dans les résultats de la recherche. Il y avait un écran vide pendant quelques secondes; Je n'ai pas compris ce qui se passait. Après quelques secondes de patience, mon écran mobile était rempli de délicieux gâteaux. Dès que j'ai cliqué sur l'un d'eux pour en vérifier les détails, j'ai reçu un popup très gras qui me demandait d'installer une application Android pour pouvoir obtenir une expérience lisse et soyeuse lorsque je commandais un gâteau.

C'était décevant. Ma conscience ne me permettait pas de cliquer sur le bouton «Installer». Tout ce que je voulais faire, c'était commander un petit gâteau et être sur mon chemin.

J'ai cliqué sur l'icône représentant une croix à l'extrême droite du menu contextuel pour en sortir le plus rapidement possible. Mais la fenêtre d’installation s’installa au bas de l’écran, occupant un quart de l’espace. Et avec l'interface utilisateur floconneuse, défiler vers le bas était un défi. J'ai réussi à commander un gâteau hollandais.

Après cette terrible expérience, mon prochain défi a été de commander une chemise pour mon père. Comme auparavant, je cherche des chemises sur Google. J'ai cliqué sur le premier lien et, en un clin d'œil, tout le contenu était juste devant moi. Le défilement était lisse. Aucune bannière d'installation. Je me sentais comme si je parcourais une application native. Il y a eu un moment où ma terrible connexion Internet a été abandonnée, mais je pouvais tout de même voir le contenu au lieu d'un jeu de dinosaure. Même avec mon internet janky, j'ai réussi à commander une chemise et un jean pour mon père. Le plus surprenant de tout, je recevais des notifications concernant ma commande.

J'appellerais cela une expérience lisse et soyeuse. Ces gens faisaient quelque chose de bien. Chaque site Web devrait le faire pour leurs utilisateurs. Elle s'appelle une application Web progressive.

Comme l'indique Alex Russell dans l'un de ses blogs :

«Il arrive de temps en temps sur le Web que de puissantes technologies existent sans l'avantage de services de marketing ou emballage lisse. Ils s'attardent et grandissent à la périphérie, devenant un vieil homme pour un groupe minuscule tout en restant presque invisibles pour tous les autres. Jusqu'à ce que quelqu'un les nomme. ”

Une expérience fluide et soyeuse sur le Web, parfois appelée application Web progressive

Les applications Web progressives (PWA) sont plutôt une méthodologie qui associe plusieurs technologies pour créer de puissantes applications Web. Grâce à une expérience utilisateur améliorée, les utilisateurs passeront plus de temps sur les sites Web et verront plus de publicités. Ils ont tendance à acheter plus et, avec les mises à jour des notifications, ils sont plus susceptibles de se rendre souvent. Le Financial Times a abandonné ses applications natives en 2011 et a créé une application Web utilisant les meilleures technologies disponibles à l'époque. Maintenant, le produit est devenu une PWA à part entière.

Mais pourquoi, après tout ce temps, créeriez-vous une application Web lorsqu'une application native fait le travail suffisamment bien?

Voyons quelques-uns des indicateurs. partagé dans Google IO 17.

Cinq milliards d'appareils sont connectés au Web, ce qui en fait la plus grande plate-forme de l'histoire de l'informatique. Sur le Web mobile, 11,4 millions de visiteurs uniques par mois accèdent aux 1 000 meilleurs sites Web et 4 millions aux 1 000 meilleures applications. Le Web mobile recueille environ quatre fois plus d'utilisateurs que les applications natives. Mais ce nombre diminue fortement lorsqu'il s'agit d'engagement.

Un utilisateur passe en moyenne 188,6 minutes dans des applications natives et à seulement 9,3 minutes sur le Web mobile. Les applications natives exploitent la puissance des systèmes d'exploitation pour envoyer des notifications push afin de fournir aux utilisateurs des mises à jour importantes. Ils offrent une meilleure expérience utilisateur et démarrent plus rapidement que les sites Web dans un navigateur. Au lieu de taper une URL dans le navigateur Web, les utilisateurs doivent simplement appuyer sur l'icône de l'application sur l'écran d'accueil.

Il est peu probable que la plupart des visiteurs du Web reviennent sur le Web. Les développeurs ont donc imaginé la solution de contournement en leur montrant les bannières à installer. applications natives, dans le but de les garder profondément engagés. Mais alors, les utilisateurs devraient passer par la procédure fastidieuse d’installer le binaire d’une application native. Forcer les utilisateurs à installer une application est gênant et réduit davantage les chances de l’installer en premier lieu. L’opportunité pour le Web est claire.

Lectures recommandées : Indigène et PWA: des choix, pas des contestataires!

Si les applications Web sont livrées avec une riche expérience utilisateur , notifications push, support hors ligne et chargement instantané, ils peuvent conquérir le monde. C’est ce que fait une application Web progressive.

Un PWA offre une expérience utilisateur riche car il présente plusieurs avantages:

  • Fast
    L’interface utilisateur n’est pas floconneuse. Le défilement est lisse. Et l'application répond rapidement aux interactions des utilisateurs.

  • Fiable
    Un site Web normal oblige les utilisateurs à attendre sans rien faire alors qu'il est en train de se rendre au serveur. Un PWA, quant à lui, charge les données instantanément à partir du cache. Un PWA fonctionne de manière transparente, même sur une connexion 2G. Chaque demande réseau visant à récupérer un actif ou une donnée passe par un agent de service (nous en parlerons plus tard), qui vérifie d’abord si la réponse à une demande particulière est déjà dans le cache. Lorsque les utilisateurs obtiennent un contenu réel presque instantanément, même sur une mauvaise connexion, ils font davantage confiance à l'application et la considèrent comme plus fiable.

  • S'engager
    Un PWA peut gagner une place sur l'écran d'accueil de l'utilisateur. Il offre une expérience similaire à une application native en fournissant un espace de travail en plein écran. Il utilise les notifications push pour garder les utilisateurs engagés.

Maintenant que nous savons ce que les PWA apportent à la table, expliquons en détail ce qui leur donne un avantage sur les applications natives. Les PWA sont construits avec des technologies telles que les employés de service, les manifestes d'applications Web, les notifications push et la structure de données IndexedDB / local pour la mise en cache. Examinons chaque détail en détail.

Service Workers

Un service worker est un fichier JavaScript qui s’exécute en arrière-plan sans perturber les interactions de l’utilisateur. Toutes les demandes GET au serveur passent par un agent de service. Il agit comme un proxy côté client. En interceptant les requêtes réseau, il prend le contrôle total de la réponse renvoyée au client. Un PWA se charge instantanément car les opérateurs de service éliminent la dépendance au réseau en répondant aux données du cache.

Un agent de maintenance peut uniquement intercepter une requête réseau se trouvant dans son étendue. Par exemple, un opérateur de service à la racine peut intercepter toutes les demandes d'extraction provenant d'une page Web. Un technicien de service fonctionne comme un système événementiel. Il entre dans un état de veille lorsqu'il n'est pas utilisé, ce qui permet de conserver la mémoire. Pour utiliser un technicien de service dans une application Web, nous devons d’abord l’enregistrer sur la page avec JavaScript.

 (function main () {

   / * navigator est une API Web qui permet aux scripts de s’enregistrer et d’exercer leurs activités. * /
    if ('serviceWorker' dans le navigateur) {
        console.log ('Service Worker est pris en charge par votre navigateur')
        / * La méthode register prend le chemin du fichier du service worker et renvoie une promesse, qui renvoie l'objet d'inscription * /
        navigator.serviceWorker.register ('./ service-worker.js'). then (registration => {
            console.log ('Service Worker est enregistré!')
        })
    } autre {
        console.log ('Service Worker n'est pas pris en charge par votre navigateur')
    }

}) ()

Nous vérifions d'abord si le navigateur prend en charge les travailleurs du service. Pour inscrire un technicien de service dans une application Web, nous fournissons son URL en tant que paramètre de la fonction register disponible dans navigator.serviceWorker ( navigator est un site Web. API permettant aux scripts de s’inscrire et d’exercer leurs activités). Un technicien n'est enregistré qu'une seule fois. L'enregistrement ne se produit pas à chaque chargement de page. Le navigateur télécharge le fichier de travailleur de service ( ./ service-worker.js ) uniquement s’il existe une différence entre les octets entre le travailleur de service activé et le plus récent, ou si son adresse URL a été modifiée.

L'employé de service ci-dessus interceptera toutes les demandes provenant de la racine ( / ). Pour limiter la portée d'un ouvrier de service, nous passons un paramètre facultatif avec l'une des clés comme portée.

 if ('serviceWorker' dans le navigateur) {
    La méthode / * register prend en compte un second paramètre optionnel. Pour restreindre la portée d'un travailleur de service, la portée doit être fournie.
        scope: '/ books' interceptera les requêtes avec '/ books' dans l'URL. * /
    navigator.serviceWorker.register ('./ service-worker.js', {scope: '/ books'}). then (registration => {
        console.log ('Service Worker pour la portée / les livres est enregistré', enregistrement)
    })
}

Le technicien de service ci-dessus interceptera les demandes contenant / livres dans l'URL. Par exemple, il n'interceptera pas de requête avec / products mais il pourrait très bien intercepter des requêtes avec / books / products .

Comme mentionné, un technicien de service agit comme un événement. système à moteur. Il écoute les événements (installer, activer, récupérer, pousser) et appelle en conséquence le gestionnaire d'événements correspondant. Certains de ces événements font partie du cycle de vie d'un technicien de maintenance, qui les exécute dans l'ordre pour être activés.

Installation

Une fois qu'un technicien de maintenance a été enregistré avec succès, un événement d'installation est déclenché. C’est un bon endroit pour effectuer le travail d’initialisation, comme la configuration du cache ou la création de librairies dans IndexedDB. (IndexedDB vous semblera plus logique une fois les détails détaillés. Pour le moment, nous pouvons simplement dire qu’il s’agit d’une structure de paire clé-valeur.)

 self.addEventListener ('install', (event) => {
    let CACHE_NAME = 'xyz-cache'
    let urlsToCache = [
        '/',
        '/styles/main.css',
        '/scripts/bundle.js'
    ]
    event.waitUntil (
        / * La méthode open disponible sur les caches prend le nom du cache comme premier paramètre. Il retourne une promesse qui résout l'instance de cache
        Toutes les URL ci-dessus peuvent être ajoutées au cache à l'aide de la méthode addAll. * /
        caches.open (CACHE_NAME)
        .then (cache => cache.addAll (urlsToCache))
    )
})

Ici, nous mettons en cache certains fichiers pour que le prochain chargement soit instantané. self fait référence à l'instance de service worker. event.waitUntil oblige le technicien à attendre que tout le code qu'il contient ait été exécuté.

Activation

Une fois qu'un technicien de service a été installé, il ne peut pas encore écouter les demandes d'extraction. Au lieu de cela, un événement activer est déclenché. Si aucun agent de service actif ne fonctionne sur le site Web dans le même périmètre, l'agent de service installé est immédiatement activé. Cependant, si un site Web a déjà un opérateur de service actif, l'activation d'un nouvel agent de service est retardée jusqu'à ce que tous les onglets fonctionnant sur l'ancien agent de service soient fermés. Cela est logique car l'ancien agent de service utilise peut-être l'instance du cache qui est maintenant modifiée dans la nouvelle. Ainsi, l'étape d'activation est un bon endroit pour se débarrasser des vieux caches.

 self.addEventListener ('activate', (event) => {
    let cacheWhitelist = ['products-v2'] // products-v2 est le nom du nouveau cache

    event.waitUntil (
        caches.keys (). then (cacheNames => {
            retourne Promise.all (
                cacheNames.map (cacheName => {
                    / * Suppression de tous les caches sauf ceux qui sont dans le tableau cacheWhitelist * /
                    if (cacheWhitelist.indexOf (cacheName) === -1) {
                        retourne caches.delete (cacheName)
                    }
                })
            )
        })
    )
})

Dans le code ci-dessus, nous supprimons l’ancien cache. Si le nom d’un cache ne correspond pas à la liste cacheWhitelist il est alors supprimé. Pour ignorer la phase d’attente et activer immédiatement le prestataire de services, nous utilisons skip.waiting () .

 self.addEventListener ('activate', (event) => {
    self.skipWaiting ()
    // le truc habituel
})

Une fois que l'agent de service est activé, il peut écouter les demandes d'extraction et les événements Push.

Le gestionnaire d'événements Fetch

Chaque fois qu'une page Web déclenche une demande d'extraction d'une ressource sur le réseau, l'événement d'extraction du prestataire de services se fait appeler. Le gestionnaire d'événements fetch recherche d'abord la ressource demandée dans le cache. S'il est présent dans le cache, il renvoie la réponse avec la ressource mise en cache. Sinon, il envoie une demande d'extraction au serveur et, lorsque celui-ci renvoie la réponse contenant la ressource demandée, il la met en cache pour les demandes suivantes.

 / * Gestionnaire d'événement Fetch permettant de répondre aux demandes GET avec la mise en cache les atouts */
self.addEventListener ('fetch', (event) => {
    event.respondWith (
        caches.open ('products-v2')
            .then (cache => {
                / * Vérifier si la demande est déjà présente dans le cache. S'il est présent, envoyez-le directement au client * /
                retourne cache.match (event.request) .then (response => {
                    si (réponse) {
                        console.log ('Cache hit! Récupération de la réponse du cache', event.request.url)
                        retour réponse
                    }
                    / * Si la requête n'est pas présente dans le cache, nous allons la chercher sur le serveur, puis nous la mettons en cache pour les requêtes suivantes. * /
                    chercher (event.request) .then (response => {
                        cache.put (event.request, response.clone ())
                        retour réponse
                    })
                })
            })
    )
})

event.respondWith permet au prestataire de services d'envoyer une réponse personnalisée au client.

Offline-first est maintenant chose courante. Pour toute demande non critique, nous devons servir la réponse du cache, au lieu de nous rendre au serveur. Si aucun élément d'actif n'est présent dans le cache, nous le récupérons du serveur, puis nous le mettons en cache pour les demandes suivantes.

Les employés de service travaillent uniquement sur les sites Web HTTPS car ils ont le pouvoir de manipuler la réponse de toute demande d'extraction. Une personne ayant une intention malveillante pourrait altérer la réponse à une demande sur un site Web HTTP. Ainsi, l'hébergement d'un PWA sur HTTPS est obligatoire. Les techniciens de maintenance n'interrompent pas le fonctionnement normal du DOM. Ils ne peuvent pas communiquer directement avec la page Web. Pour envoyer un message à une page Web, il utilise des messages postaux.

Notifications Web Push

Supposons que vous soyez en train de jouer à un jeu sur votre mobile et qu'une notification s'affiche vous annonçant un rabais de 30% sur votre marque préférée. Sans plus tarder, vous cliquez sur la notification et vous restez bouche bée. Obtenir des mises à jour en direct sur, par exemple, un match de cricket ou de football, ou recevoir des courriels et des rappels importants en tant que notifications, représente un gros problème lorsqu'il s'agit de faire participer les utilisateurs à un produit. Cette fonctionnalité n'était disponible que dans les applications natives jusqu'à l'arrivée de PWA. Un PWA utilise les notifications Web push pour concurrencer cette fonctionnalité puissante fournie directement par les applications natives. Un utilisateur recevrait toujours une notification Web push même si le PWA n'est ouvert dans aucun des onglets du navigateur et même si le navigateur n'est pas ouvert.

Une application Web doit demander à l'utilisateur la permission de lui envoyer des notifications push.


 Navigateur Invite à demander l'autorisation pour les notifications Web Push
Navigateur Invite à demander l'autorisation pour les notifications Web Push. ( Grand aperçu )

Une fois que l'utilisateur a confirmé en cliquant sur le bouton “Autoriser”, un jeton d'abonnement unique est généré par le navigateur. Ce jeton est unique pour ce périphérique. Le format du jeton d'abonnement généré par Chrome est le suivant:

 {
     "Point final": "https://fcm.googleapis.com/fcm/send/c7Veb8VpyM0:APA91bGnMFx8GIxf__UVy6vJ-n9i728CUJSR1UHBPAKOCE_SrwgyP2N8jL4MBXf8NxIqW6NCCBg01u8c5fcY0kIZvxpDjSBA75sVz64OocQ-DisAWoW7PpTge3SwvQAx5zl_45aAXuvS",
     "expirationTime": null,
     "clés": {
          "p256dh": "BJsj63kz8RPZe8Lv1uu-6VSzT12RjxtWyWCzfa18RZ0-8sc5j80pmSF1YXAj0HnnrkyIimRgLo8ohhkzNA7lX4w",
          "auth": "TJXqKozSJxcWvtQasEUZpQ"
     }
}

Le point final contenu dans le jeton ci-dessus sera unique pour chaque abonnement. Sur un site Web moyen, des milliers d'utilisateurs accepteraient de recevoir des notifications push et, pour chacun d'entre eux, ce point final serait unique. Ainsi, à l'aide de ce point final l'application est en mesure de cibler ces utilisateurs à l'avenir en leur envoyant des notifications push. Le expirationTime est la durée de validité de l'abonnement pour un appareil particulier. Si expirationTime est de 20 jours, cela signifie que l’abonnement push de l’utilisateur expirera au bout de 20 jours et que l’utilisateur ne pourra pas recevoir de notifications push sur l’ancien abonnement. Dans ce cas, le navigateur générera un nouveau jeton d'abonnement pour ce périphérique. Les clés auth et p256dh sont utilisées pour le cryptage.

Désormais, pour envoyer des notifications push à ces milliers d'utilisateurs, nous devons d'abord sauvegarder leurs jetons d'abonnement respectifs. C’est le travail du serveur d’application (le serveur principal, peut-être un script Node.js) d’envoyer des notifications push à ces utilisateurs. Cela peut sembler aussi simple que de faire une demande POST à l'URL du noeud final avec les données de notification dans le contenu de la demande. Toutefois, il convient de noter que si un utilisateur n'est pas en ligne lorsqu'une notification push qui lui est destinée est déclenchée par le serveur, il doit néanmoins recevoir cette notification une fois qu'il est revenu en ligne. Le serveur devrait prendre en charge de tels scénarios et envoyer des milliers de demandes aux utilisateurs. Un serveur surveillant la connexion de l’utilisateur semble compliqué. Ainsi, quelque chose au milieu serait responsable de l'acheminement des notifications Web push du serveur au client. Cela s'appelle un service push, et chaque navigateur a sa propre implémentation d'un service push. Le navigateur doit transmettre les informations suivantes au service Push pour pouvoir envoyer toute notification:

  1. Heure de vie
    C'est la durée pendant laquelle un message doit être mis en file d'attente s'il n'est pas remis à l'utilisateur. Une fois ce délai écoulé, le message sera supprimé de la file d'attente.
  2. Urgence du message
    De manière à ce que le service push conserve la batterie de l'utilisateur en envoyant uniquement des messages de priorité élevée.

achemine les messages au client. Comme le client doit recevoir le push même si son application Web n'est pas ouverte dans le navigateur, les événements du push doivent être écoutés par un élément surveillé en permanence en arrière-plan. Vous l’avez deviné: c’est le travail du technicien. Le technicien de service écoute les événements Push et affiche les notifications à l'utilisateur.

Nous savons donc maintenant que le navigateur, le service Push, le travailleur de service et le serveur d'applications travaillent en harmonie pour envoyer des notifications push à l'utilisateur. Examinons maintenant les détails de la mise en oeuvre.

Web Push Client

Demander l’autorisation de l’utilisateur est unique. Si un utilisateur a déjà accordé l’autorisation de recevoir des notifications push, nous ne devrions plus demander. La valeur d'autorisation est enregistrée dans Notification.permission .

 / * Notification.permission peut avoir l'une des trois valeurs suivantes: par défaut, accordé ou refusé. * /
if (Notification.permission === 'default') {
    / * La méthode Notification.requestPermission () affiche une invite d'autorisation de notification à l'utilisateur. Il retourne une promesse qui résout la valeur de permission * /
    Notification.requestPermission (). Then (result => {
        if (résultat === 'refusé') {
            console.log ('Autorisation refusée')
            revenir
        }

        if (résultat === 'accordé') {
            console.log ('autorisation accordée')
            / * Cela signifie que l'utilisateur a cliqué sur le bouton Autoriser. Nous devons récupérer le jeton d’abonnement généré par le navigateur et le stocker dans notre base de données.

            Le jeton d'abonnement peut être récupéré à l'aide de la méthode getSubscription disponible dans pushManager de l'objet serviceWorkerRegistration. Si l'abonnement n'est pas disponible, nous nous abonnons en utilisant la méthode subscribe disponible sur pushManager. La méthode subscribe prend un objet.
            * /

            serviceWorkerRegistration.pushManager.getSubscription ()
                .then (subscription => {
                    si (! inscription) {
                        const applicationServerKey = ''
                        serviceWorkerRegistration.pushManager.subscribe ({
                            userVisibleOnly: true, // toutes les notifications push du serveur doivent être affichées à l'utilisateur
                            applicationServerKey // Clé publique VAPID
                        })
                    } autre {
                        saveSubscriptionInDB (subscription, userId) // Une méthode pour enregistrer le jeton d'abonnement dans la base de données
                    }
                })
        }
    })
}
 

Dans la méthode abonnée à la méthode ci-dessus, nous passons à l'utilisateur visible seulement et applicationServerKey pour générer un jeton d'abonnement. La propriété userVisibleOnly doit toujours être vraie, car elle indique au navigateur que toute notification push envoyée par le serveur sera affichée au client. Pour comprendre l’objet de applicationServerKey considérons un scénario.

Si une personne récupère vos milliers de jetons d’abonnement, elle peut très bien envoyer des notifications aux ordinateurs d'extrémité contenus dans ces abonnements. Le système d'extrémité ne peut en aucun cas être lié à votre identité unique. Pour fournir une identité unique aux jetons d’abonnement générés sur votre application Web, nous utilisons le protocole VAPID. Avec VAPID, le serveur d'applications s'identifie volontairement auprès du service Push lors de l'envoi de notifications Push. Nous générons deux clés comme ceci:

 const webpush = require ('web-push')
const vapidKeys = webpush.generateVAPIDKeys ()

web-push est un module npm. vapidKeys aura une clé publique et une clé privée. La clé publique utilisée ci-dessus est la clé publique.

Serveur Web Push

Le travail du serveur Web Push (serveur d'applications) est simple. Il envoie une charge de notification aux jetons d'abonnement.

 const options = {
    TTL: 24 * 60 * 60, // TTL est l'heure de vie, l'heure à laquelle la notification sera mise en file d'attente dans le service push
    vapidDetails: {
        sujet: 'email@example.com',
        publicKey: '',
        privateKey: ''
    }
}
const data = {
    titre: 'Mise à jour',
    body: 'Notification envoyée par le serveur'
}
webpush.sendNotification (abonnement, données, options)
  

Il utilise la méthode sendNotification de la bibliothèque Web Push.

Travailleurs du service

Le travailleur du service montre la notification à l'utilisateur en tant que telle:

 self .addEventListener ('push', (event) => {
    let options = {
        body: event.data.body,
        icon: 'images / example.png',
    }
    event.waitUntil (
        / * La méthode showNotification est disponible sur l'objet d'enregistrement du service worker.
        Le premier paramètre de la méthode showNotification est le titre de la notification et le second paramètre est un objet * /
        self.registration.showNotification (event.data.title, options)
    )
})

Jusqu'à présent, nous avons vu comment un technicien utilise le cache pour stocker des demandes et créer un PWA rapide et fiable, ainsi que des notifications Web push qui maintiennent l'engagement des utilisateurs.

des données côté client pour le support hors ligne, nous avons besoin d’une structure de données géante. Examinons le PWA du Financial Times. Vous devez vous rendre compte de la puissance de cette structure de données. Chargez l'URL dans votre navigateur, puis désactivez votre connexion Internet. Recharge la page. Gah! Est-ce qu'il fonctionne toujours? Il est. (Comme je l'ai dit, le nouveau noir est hors ligne.) Les données ne proviennent pas des câbles. Il est servi de la maison. Rendez-vous sur l'onglet "Applications" des outils de développement Chrome. Sous "Stockage", vous trouverez "IndexedDB".


 IndexedDB stocke les données sur les articles dans le Financial Times PWA
IndexedDB sur le Financial Times PWA. ( Grand aperçu )

Découvrez le magasin d'objets «Articles» et développez n'importe lequel des objets pour voir la magie de vous-même. Le Financial Times a stocké ces données pour une assistance hors ligne. Cette structure de données qui nous permet de stocker une quantité énorme de données est appelée IndexedDB. IndexedDB est une base de données orientée objet basée sur JavaScript pour stocker des données structurées. Nous pouvons créer différents magasins d'objets dans cette base de données à des fins diverses. Par exemple, comme on peut le voir dans l'image ci-dessus, les termes «Ressources», «ArticleImages» et «Articles» sont appelés magasins d'objets. Chaque enregistrement dans un magasin d'objets est identifié de manière unique avec une clé. IndexedDB peut même être utilisé pour stocker des fichiers et des blobs.

Essayons de comprendre IndexedDB en créant une base de données pour stocker des livres.

 let openIdbRequest = window.indexedDB.open ('booksdb', 1)

Si la base de données booksdb n’existe pas déjà, le code ci-dessus crée une base de données booksdb . Le deuxième paramètre de la méthode open est la version de la base de données. La spécification d'une version prend en charge les modifications liées au schéma susceptibles de se produire à l'avenir. Par exemple, booksdb ne contient plus qu'un seul tableau, mais lorsque l'application se développe, nous avons l'intention d'ajouter deux tableaux supplémentaires. Pour vous assurer que notre base de données est synchronisée avec le schéma mis à jour, nous allons spécifier une version plus récente que la précédente.

L'appel de la méthode open n'ouvre pas la base de données immédiatement. C’est une requête asynchrone qui renvoie un objet IDBOpenDBRequest . Cet objet a des propriétés de succès et d'erreur. nous devrons écrire des gestionnaires appropriés pour ces propriétés afin de gérer l’état de notre connexion.

 let dbInstance
openIdbRequest.onsuccess = (event) => {
    dbInstance = event.target.result
    console.log ('booksdb est ouvert avec succès')
}

openIdbRequest.onerror = (event) => {
    console.log ("Une erreur s'est produite lors de l'ouverture de la base de données booksdb")
}

openIdbRequest.onupgradeneeded = (event) => {
    laisser db = event.target.result
    let objectstore = db.createObjectStore ('books', {keyPath: 'id'})
}

Pour gérer la création ou la modification de librairies (les librairies sont analogues aux tables SQL – elles ont une structure clé-valeur), la méthode onupgradeneeded est appelée sur la openIdbRequest objet. La méthode onupgradeneeded sera invoquée chaque fois que la version changera. Dans l'extrait de code ci-dessus, nous créons un magasin d'objets de livres avec une clé unique en tant qu'ID.

Supposons qu'après avoir déployé ce morceau de code, nous devons créer un magasin d'objets supplémentaire, appelé utilisateurs . . La version de notre base de données sera donc 2 .

 laissez openIdbRequest = window.indexedDB.open ('booksdb', 2) // Nouvelle version - 2

/ * Les gestionnaires d'événements de succès et d'erreur restent les mêmes.
La méthode onupgradeneeded est appelée lorsque la version de la base de données change. * /
openIdbRequest.onupgradeneeded = (event) => {
    laisser db = event.target.result
    if (! db.objectStoreNames.contains ('books'))) {
        let objectstore = db.createObjectStore ('books', {keyPath: 'id'})
    }

    laisser oldVersion = event.oldVersion
    laisser newVersion = event.newVersion

    / * Les tables d'utilisateurs doivent être ajoutées pour la version 2. Si la version existante est 1, elle sera mise à niveau vers 2 et le magasin d'objets utilisateur sera créé. * /
    if (oldVersion === 1) {
        db.createObjectStore ('utilisateurs', {keyPath: 'id'})
    }
}

Nous avons mis en cache dbInstance dans le gestionnaire d’événements de réussite de la demande ouverte. Pour récupérer ou ajouter des données dans IndexedDB, nous allons utiliser dbInstance . Ajoutons quelques enregistrements de livre dans notre magasin d'objets de livres.

 let transaction = dbInstance.transaction ('books')
let objectstore = dbInstance.objectstore ('books')

laisser bookRecord = {
    id: '1',
    nom: "The Alchemist",
    auteur: 'Paulo Coelho'
}
let addBookRequest = objectstore.add (bookRecord)

addBookRequest.onsuccess = (event) => {
    console.log ('Enregistrement de livre ajouté avec succès')
}

addBookRequest.onerror = (event) => {
    console.log ("Une erreur s'est produite lors de l'ajout d'une fiche de livre")
}

Nous utilisons les transactions en particulier lors de la rédaction d'enregistrements dans des magasins d'objets. Une transaction est simplement un wrapper autour d’une opération pour assurer l’intégrité des données. Si l'une des actions d'une transaction échoue, aucune action n'est exécutée sur la base de données.

Modifions une fiche de livre avec la méthode put :

 let modifyBookRequest = objectstore.put (bookRecord) // la méthode put prend un objet en tant que paramètre
modifyBookRequest.onsuccess = (event) => {
    console.log ('Enregistrement du livre mis à jour avec succès')
}

Récupérons un enregistrement de livre avec la méthode get :

 let transaction = dbInstance.transaction ('books')
let objectstore = dbInstance.objectstore ('books')

/ * La méthode get prend l'identifiant de l'enregistrement * /
let getBookRequest = objectstore.get (1)

getBookRequest.onsuccess = (event) => {
    / * event.target.result contient l'enregistrement correspondant * /
    console.log ('Enregistrement du livre', event.target.result)
}

getBookRequest.onerror = (event) => {
    console.log ('Erreur lors de l'extraction de l'enregistrement du livre.')
}

Ajout d'une icône sur l'écran d'accueil

Maintenant qu'il n'y a presque plus de distinction entre un PWA et une application native, il est logique d'offrir une position privilégiée au PWA. Si votre site Web remplit les critères de base d'un PWA (hébergé sur HTTPS, s'intègre avec les opérateurs de service et possède un manifeste.json ) et, une fois l'utilisateur placé sur la page Web, le navigateur invoque un invite en bas, demandant à l'utilisateur d'ajouter l'application à son écran d'accueil, comme indiqué ci-dessous:


 Invite à ajouter le PWA du Financial Times sur l'écran d'accueil
Invite à ajouter le PWA du Financial Times à l'écran d'accueil. ( Grand aperçu )

Lorsqu'un utilisateur clique sur «Ajouter un FT à l'écran d'accueil», le PWA peut définir son pied sur l'écran d'accueil, ainsi que dans le tiroir de l'application. Lorsqu'un utilisateur recherche une application sur son téléphone, tous les PWA correspondant à la requête de recherche sont répertoriés. Ils seront également visibles dans les paramètres système, ce qui facilitera leur gestion par les utilisateurs. En ce sens, un PWA se comporte comme une application native.

Les PWA utilisent manifest.json pour fournir cette fonctionnalité. Examinons un simple fichier manifest.json .

 {
    "name": "Demo PWA",
     "short_name": "Demo",
     "start_url": "/? standalone",
     "background_color": "# 9F0C3F",
     "theme_color": "# fff1e0",
     "display": "autonome",
     "icônes": [{
          "src": "/lib/img/icons/xxhdpi.png?v2",
          "sizes": "192x192"
     }]
}

The short_name appears on the user’s home screen and in the system settings. The name appears in the chrome prompt and on the splash screen. The splash screen is what the user sees when the app is getting ready to launch. The start_url is the main screen of your app. It’s what users get when they tap an icon on the home screen. The background_color is used on the splash screen. The theme_color sets the color of the toolbar. The standalone value for display mode says that the app is to be operated in full-screen mode (hiding the browser’s toolbar). When a user installs a PWA, its size is merely in kilobytes, rather than the megabytes of native applications.

Service workers, web push notifications, IndexedDB, and the home screen position make up for offline support, reliability, and engagement. It should be noted that a service worker doesn’t come to life and start doing its work on the very first load. The first load will still be slow until all of the static assets and other resources have been cached. We can implement some strategies to optimize the first load.

Bundling Assets

All of the resources, including the HTML, style sheets, images and JavaScript, are to be fetched from the server. The more files, the more HTTPS requests needed to fetch them. We can use bundlers like WebPack to bundle our static assets, hence reducing the number of HTTP requests to the server. WebPack does a great job of further optimizing the bundle by using techniques such as code-splitting (i.e. bundling only those files that are required for the current page load, instead of bundling all of them together) and tree shaking (i.e. removing duplicate dependencies or dependencies that are imported but not used in the code).

Reducing Round Trips

One of the main reasons for slowness on the web is network latency. The time it takes for a byte to travel from A to B varies with the network connection. For example, a particular round trip over Wi-Fi takes 50 milliseconds and 500 milliseconds on a 3G connection, but 2500 milliseconds on a 2G connection. These requests are sent using the HTTP protocol, which means that while a particular connection is being used for a request, it cannot be used for any other requests until the response of the previous request is served. A website can make six asynchronous HTTP requests at a time because six connections are available to a website to make HTTP requests. An average website makes roughly 100 requests; so, with a maximum of six connections available, a user might end up spending around 833 milliseconds in a single round trip. (The calculation is 833 milliseconds – 1006 = 1666. We have to divide 1666 by 2 because we’re calculating the time spend on a round trip.) With HTTP2 in place, the turnaround time is drastically reduced. HTTP2 doesn’t block the connection head, so multiple requests can be sent simultaneously.

Most HTTP responses contain last-modified and etag headers. The last-modified header is the date when the file was last modified, and an etag is a unique value based on the contents of the file. It will only be changed when the contents of a file are changed. Both of these headers can be used to avoid downloading the file again if a cached version is already locally available. If the browser has a version of this file locally available, it can add any of these two headers in the request as such:


Add ETag and Last-Modified Headers to prevent downloading of valid cached assets
ETag and Last-Modified Headers. (Large preview)

The server can check whether the contents of the file have changed. If the contents of the file have not changed, then it responds with a status code of 304 (not modified).


If-None-Match Header to prevent downloading of valid cached assets
If-None-Match Header. (Large preview)

This indicates to the browser to use the locally available cached version of the file. By doing all of this, we’ve prevented the file from being downloaded.

Faster responses are in now place, but our job is not done yet. We still have to parse the HTML, load the style sheets and make the web page interactive. It makes sense to show some empty boxes with a loader to the user, instead of a blank screen. While the HTML document is getting parsed, when it comes across it will make a synchronous HTTP request to the server to fetch asset.jsand the whole parsing process will be paused until the response comes back. Imagine having a dozen of synchronous static asset references. These could very well be managed just by making use of the async keyword in script references, like . With the introduction of the async keyword here, the browser will make an asynchronous request to fetch asset.js without hindering the parsing of the HTML. If a script file is required at a later stage, we can defer the downloading of that file until the entire HTML has been parsed. A script file can be deferred by using the defer keyword, like .

Conclusion

We’ve learned a lot of many new things that make for a cool web application. Here’s a summary of all of the things we’ve explored in this article:

  1. Service workers make good use of the cache to speed up the loading of assets.
  2. Web push notifications work under the hood.
  3. We use IndexedDB to store a massive amount of data.
  4. Some of the optimizations for instant first load, like using HTTP2 and adding headers like Etaglast-modified and If-None-Matchprevent the downloading of valid cached assets.

That’s all, folks!

Smashing Editorial(rb, ra, al, yk, il)




Source link