Comment faire fonctionner vos applications Web hors ligne

La puissance de JavaScript et de l'API du navigateur

Le monde devient de plus en plus interconnecté - le nombre de personnes ayant accès à Internet est passé à 4,5 milliards .

image

Mais ces données ne reflètent pas le nombre de personnes qui ont une connexion Internet lente ou interrompue. Même aux États-Unis, 4,9 millions de foyers ne peuvent pas accéder à Internet câblé à des vitesses supérieures à 3 mégabits par seconde.

Le reste du monde - ceux qui ont un accès fiable à Internet - est toujours enclin à perdre la connectivité. Certains facteurs peuvent affecter la qualité de votre connexion réseau:

  • Mauvaise couverture du fournisseur.
  • Conditions météorologiques extrêmes.
  • Coupures de courant.
  • Les utilisateurs qui tombent dans des zones mortes, comme les bâtiments qui bloquent leurs connexions réseau.
  • Voyage en train et voyage en tunnel.
  • Connexions contrôlées par un tiers et limitées dans le temps.
  • Pratiques culturelles qui nécessitent un accès Internet limité ou inexistant à des heures ou des jours spécifiques.

Compte tenu de cela, il est clair que nous devons considérer l'expérience autonome lors du développement et de la création d'applications.

Logiciel EDISON - développement web
Cet article a été traduit avec le soutien d'EDISON Software, une société qui effectue d'excellentes commandes depuis le sud de la Chine , et développe également des applications et des sites Web .
J'ai récemment eu l'opportunité d'ajouter de l'autonomie à une application existante en utilisant des travailleurs de service, le stockage en cache et IndexedDB. Le travail technique nécessaire pour que l'application fonctionne hors ligne a été réduit à quatre tâches distinctes, dont je parlerai dans cet article.

Travailleurs des services


Les applications créées pour une utilisation hors ligne ne doivent pas dépendre fortement du réseau. Conceptuellement, cela n'est possible que si, en cas de panne, des options de sauvegarde existent.

Si l'application Web ne parvient pas à se charger, nous devons prendre les ressources du navigateur quelque part (HTML / CSS / JavaScript). D'où viennent ces ressources, sinon d'une demande de réseau? Que diriez-vous d'un cache. La plupart des gens conviendront qu'il est préférable de fournir une interface utilisateur potentiellement obsolète qu'une page vierge.

Le navigateur interroge constamment les données. Le service de mise en cache des données en tant que solution de rechange nous oblige toujours à intercepter les demandes du navigateur et à écrire des règles de mise en cache. C'est là que les travailleurs des services entrent en jeu - considérez-les comme un intermédiaire.

image

Le service worker est juste un fichier JavaScript dans lequel nous pouvons nous abonner aux événements et écrire nos propres règles pour la mise en cache et la gestion des pannes de réseau.
Commençons.

Veuillez noter: notre application de démonstration

Tout au long de cet article, nous ajouterons des fonctions autonomes à l'application de démonstration. L'application de démonstration est une page simple pour prendre / louer des livres dans la bibliothèque. Les progrès seront présentés sous la forme d'une série de GIF et l'utilisation de simulations Chrome DevTools hors ligne.

Voici l'état initial:

image

Tâche 1 - Mise en cache des ressources statiques


Les ressources statiques sont des ressources qui ne changent pas souvent. HTML, CSS, JavaScript et les images peuvent entrer dans cette catégorie. Le navigateur essaie de charger des ressources statiques à l'aide de requêtes pouvant être interceptées par le technicien de service.

Commençons par inscrire notre technicien.

if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js'); }); } 

Les employés de service sont des employés Web sous le capot et doivent donc être importés à partir d'un fichier JavaScript distinct. L'inscription a lieu en utilisant la méthode d' register après le chargement du site.
Maintenant que nous avons chargé un technicien de service, mettons en cache nos ressources statiques.

 var CACHE_NAME = 'my-offline-cache'; var urlsToCache = [ '/', '/static/css/main.c9699bb9.css', '/static/js/main.99348925.js' ]; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll(urlsToCache); }) ); }); 

Étant donné que nous contrôlons les URL des ressources statiques, nous pouvons les mettre en cache immédiatement après l'initialisation du technicien de service à l'aide du Cache Storage .

image

Maintenant que notre cache est plein des ressources statiques les plus récemment demandées, chargeons ces ressources à partir du cache en cas d'échec d'une demande.

 self.addEventListener('fetch', function(event) { event.respondWith( fetch(event.request).catch(function() { caches.match(event.request).then(function(response) { return response; } ); ); }); 

L'événement fetch est déclenché chaque fois que le navigateur fait une demande. Notre nouveau gestionnaire d'événements d' fetch dispose désormais d'une logique supplémentaire pour renvoyer les réponses mises en cache en cas de pannes de réseau.

Démo numéro 1


image

Notre application de démonstration peut désormais servir des ressources statiques hors ligne! Mais où sont nos données?

Tâche 2 - Mise en cache des ressources dynamiques


Les applications monopages (SPA) demandent généralement des données progressivement après le chargement initial de la page, et notre application de démonstration ne fait pas exception - la liste des livres n'est pas chargée immédiatement. Ces données proviennent généralement de demandes XHR qui renvoient des réponses qui changent fréquemment pour fournir un nouvel état à l'application - elles sont donc dynamiques.

La mise en cache des ressources dynamiques est en fait très similaire à la mise en cache des ressources statiques - la principale différence est que nous devons mettre à jour le cache plus souvent. Il est également assez difficile de générer une liste complète de toutes les demandes XHR dynamiques possibles, nous allons donc les mettre en cache à leur arrivée, et nous n'aurons pas de liste prédéfinie, comme nous l'avons fait pour les ressources statiques.

Jetez un œil à notre gestionnaire d' fetch :

 self.addEventListener('fetch', function(event) { event.respondWith( fetch(event.request).catch(function() { caches.match(event.request).then(function(response) { return response; } ); ); }); 

Nous pouvons personnaliser cette implémentation en ajoutant du code qui met en cache les demandes et réponses réussies. Cela garantit que nous ajoutons constamment de nouvelles demandes à notre cache et mettons constamment à jour les données mises en cache.

 self.addEventListener('fetch', function(event) { event.respondWith( fetch(event.request) .then(function(response) { caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, response); }); }) .catch(function() { caches.match(event.request).then(function(response) { return response; } ); ); }); 

Notre Cache Storage actuellement plusieurs entrées.

image

Démo numéro 2


image

Notre démo est maintenant la même au démarrage, quel que soit l'état de notre réseau!

Super. Essayons maintenant d'utiliser notre application.

image

Malheureusement, les messages d'erreur sont partout. Il semble que toutes nos interactions avec l'interface ne fonctionnent pas. Je ne peux pas choisir ou remettre le livre! Que faut-il corriger?

Tâche 3 - Créer une interface utilisateur optimiste


À l'heure actuelle, le problème de notre application est que notre logique de collecte de données dépend toujours fortement des réponses du réseau. L'action d'archivage ou d'extraction envoie une demande au serveur et attend une réponse réussie. C'est idéal pour la cohérence des données, mais mauvais pour notre expérience autonome.

Pour que ces interactions fonctionnent hors ligne, nous devons rendre notre application plus optimiste . Les interactions optimistes ne nécessitent pas de réponse du serveur et affichent volontiers une vue mise à jour des données. L'opération optimiste habituelle dans la plupart des applications Web est la delete - pourquoi ne pas donner à l'utilisateur une rétroaction instantanée si nous avons déjà toutes les informations nécessaires?

Déconnecter notre application du réseau en utilisant une approche optimiste est relativement facile à mettre en œuvre.

 case CHECK_OUT_SUCCESS: case CHECK_OUT_FAILURE: list = [...state.list]; list.push(action.payload); return { ...state, list, }; case CHECK_IN_SUCCESS: case CHECK_IN_FAILURE; list = [...state.list]; for (let i = 0; i < list.length; i++) { if (list[i].id === action.payload.id) { list.splice(i, 1, action.payload); } } return { ...state, list, }; 

La clé est de gérer les actions des utilisateurs de la même manière, que la requête réseau aboutisse ou non. L'extrait de code ci-dessus est tiré du réducteur redux de notre application, SUCCESS et FAILURE lancés en fonction de la disponibilité du réseau. Quelle que soit la manière dont la demande de réseau est traitée, nous allons mettre à jour notre liste de livres.

Démo numéro 3


image

L'interaction des utilisateurs se produit désormais en ligne (pas littéralement). Les boutons «check-in» et «check-out» mettent à jour l'interface en conséquence, bien que les messages rouges de la console indiquent que les requêtes réseau ne sont pas exécutées.

Bon! Il n'y a qu'un petit problème avec le rendu hors ligne optimiste ...

Ne perdons-nous pas notre monnaie!?

image

Tâche 4 - Mettre en file d'attente les actions des utilisateurs pour la synchronisation


Nous devons suivre les actions effectuées par l'utilisateur lorsqu'il était hors ligne, afin de pouvoir les synchroniser avec notre serveur lorsque l'utilisateur revient sur le réseau. Il existe plusieurs mécanismes de stockage dans le navigateur qui peuvent agir comme une file d'attente d'actions, et nous allons utiliser IndexedDB. IndexedDB fournit quelques éléments que vous n'obtiendrez pas de LocalStorage:

  • Opérations non bloquantes asynchrones
  • Limites de stockage considérablement plus élevées
  • Gestion des transactions

Regardez notre ancien code de réduction:

 case CHECK_OUT_SUCCESS: case CHECK_OUT_FAILURE: list = [...state.list]; list.push(action.payload); return { ...state, list, }; case CHECK_IN_SUCCESS: case CHECK_IN_FAILURE; list = [...state.list]; for (let i = 0; i < list.length; i++) { if (list[i].id === action.payload.id) { list.splice(i, 1, action.payload); } } return { ...state, list, }; 

Modifions-le pour stocker les événements d'archivage et d'extraction dans IndexedDB pendant l'événement FAILURE .

 case CHECK_OUT_FAILURE: list = [...state.list]; list.push(action.payload); addToDB(action); // QUEUE IT UP return { ...state, list, }; case CHECK_IN_FAILURE; list = [...state.list]; for (let i = 0; i < list.length; i++) { if (list[i].id === action.payload.id) { list.splice(i, 1, action.payload); addToDB(action); // QUEUE IT UP } } return { ...state, list, }; 

Voici l'implémentation de la création d'IndexedDB avec l' addToDB addToDB.

 let db = indexedDB.open('actions', 1); db.onupgradeneeded = function(event) { let db = event.target.result; db.createObjectStore('requests', { autoIncrement: true }); }; const addToDB = action => { var db = indexedDB.open('actions', 1); db.onsuccess = function(event) { var db = event.target.result; var objStore = db .transaction(['requests'], 'readwrite') .objectStore('requests'); objStore.add(action); }; }; 

Maintenant que toutes nos actions utilisateur hors ligne sont stockées dans la mémoire du navigateur, nous pouvons utiliser l'écouteur d'événements du navigateur en online pour synchroniser les données lorsque la connexion est rétablie.

 window.addEventListener('online', () => { const db = indexedDB.open('actions', 1); db.onsuccess = function(event) { let db = event.target.result; let objStore = db .transaction(['requests'], 'readwrite') .objectStore('requests'); objStore.getAll().onsuccess = function(event) { let requests = event.target.result; for (let request of requests) { send(request); // sync with the server } }; }; }); 

À ce stade, nous pouvons effacer la file d'attente de toutes les demandes que nous avons envoyées avec succès au serveur.

Démo numéro 4


La démo finale semble un peu plus compliquée. À droite, dans la fenêtre sombre du terminal, toute l'activité de l'API est enregistrée. La démo implique de se déconnecter, de sélectionner plusieurs livres et de revenir en ligne.

image

Il est clair que les demandes effectuées hors ligne ont été mises en file d'attente et envoyées immédiatement lorsque l'utilisateur revient en ligne.

Cette approche de «jeu» est un peu naïve - par exemple, nous n’aurons probablement pas besoin de faire deux demandes si nous prenons et retournons le même livre. Cela ne fonctionnera pas non plus si plusieurs personnes utilisent la même application.

C'est tout


Sortez et mettez vos applications Web hors ligne! Cet article montre certaines des nombreuses choses que vous pouvez faire pour ajouter des fonctionnalités autonomes à vos applications et n'est certainement pas exhaustif.
Pour en savoir plus, consultez les principes de base de Google Web . Pour voir une autre implémentation hors ligne, consultez cette présentation .


Lisez aussi le blog
Société EDISON:


20 bibliothèques pour
application iOS spectaculaire

Source: https://habr.com/ru/post/fr474374/


All Articles