nursit/offline

SPIP Plugin for adding offline mode feature to websites

v1.0.1 2023-07-18 09:01 UTC

This package is auto-updated.

Last update: 2024-04-06 13:49:21 UTC


README

Plugin pour SPIP 3.1+ permettant de proposer simplement la consultation offline d'un site SPIP existant

Principe

Le plugin Offline s'appuie sur les ServiceWorkers pour permettre la mise en cache des pages du site dans le navigateur et réutiliser ce cache quand la connexion internet est coupée.

Le ServiceWorker est un morceau de code autonome qu'on envoie dans le navigateur de l'utilisateur et qui va ensuite fonctionner indépendamment en interceptant les requêtes pour le site concerné et en gérant la mise dans un cache pour plus tard si on est connecté, ou en utilisant le fichier dans le cache si on est offline.

Mode d'emploi

Installer le plugin

Ajouter en debut de section du fichier .htaccess les RewriteRules suivantes

RewriteCond %{REQUEST_URI} offline\.api\.(.+)$
RewriteCond local/offline/%1 -f
RewriteRule ^offline\.api\.(.+)$ local/offline/$1 [QSA,L,skip=100]

Modifier dans le .htaccess la RewriteRule des api pour permettre d'appeler l'action API avec une URL de la forme /offline.api.sw.js

RewriteRule ^([\w]+)\.api([/.](.*))?$ spip.php?action=api_$1&arg=$3 [QSA,L]

De cette manière, l'appel à l'URL /offline.api.sw.js servira

  • le fichier statique local/offline/sw.js si il existe
  • l'url ?action=api_offline&arg=sw.js sinon, qui se chargera de le reconstruire

Configuration

La constante _OFFLINE_DEBUG permet de faire fonctionner le plugin avec des logs verbeux dans la console JS et sans caching du service-worker côté serveur. Commencez par l'ajouter à votre fichier mes_options.php pour tester le plugin (mais il faudra penser à l'enlever avant toute mise en production)

define('_OFFLINE_DEBUG', true);

Se rendre sur la page de configuration du plugin ecrire/?exec=configurer_offline pour configurer et activer le mode offline.

  • Version du cache : ce numéro de version est pris en compte dans le nom du cache utilisé côté navigateur. Il vous permet par exemple de forcer une mise à jour des pages mises en cache chez les utilisateurs lorsque vous venez de publier une nouvelle version du site.
  • Activation du mode offline : choix du mode d'activation (cf le principe de fonctionnement ci-dessous)
  • Stratégie pour les pages : choix de la stratégie de service des pages du site
    • vous pouvez privilégier le réseau pour être sur d'avoir la dernière version, mais en connexion mobile intermittente l'utilisateur devra attendre à chaque fois que le navigateur se rende compte que non, la connexion ne passe pas, avant d'avoir la page en cache
    • vous pouvez privilégier le contenu déjà en cache. Dans ce cas il est chaque fois remis à jour depuis le réseau si possible pour que la prochaine fois le contenu soit à jour
  • Stratégie pour les ressources : ici cela concerne la stratégie pour toutes les images, css, js… associées à une page.
    • si vous privilégiez le réseau, idem que pour les pages, on essaye d'abord avec la connexion
    • si vous privilégiez le cache vous aurez un fonctionnement plus rapide. Ici pour toutes les ressources statiques connues de SPIP et les ressources timestampées on s'épargne une mise à jour en background, ce qui économise en plus des requêtes réseau
  • Taille maxi (ko) des medias mises en cache : vous pouvez limiter la taille maximum des medias (image, audio, video) mis en cache pour éviter de remplir le cache de l'utilisateur. Si vous mettez 0 il n'y aura pas de limite (déconseillé) L'image de fallback plugins/offline/img/fallback.png utilisée à la place des images qui ne sont pas en cache quand l'utilisateur est hors-ligne est surchargeable dans votre dossier squelettes/
    Si vous utilisez une limite de taille vous pouvez toujours forcer la mise en cache offline d'un media en ajoutant un ?offline-cache dans son URL
  • URL 404 offline : c'est l'URL de la page qu'on présentera aux utilisateurs quand ils demandent une URL qui n'est pas en cache et qu'on n'a pas de réponse du réseau. Par défaut le plugin propose une page 404_offline construite à partir de la page 404 de votre site, mais vous pouvez la personaliser ou choisir une autre URL.
  • Ressources supplémentaires à mettre en cache : Par défaut la home page et l'URL 404 offline du site ainsi que leurs ressources seront mise en cache lors de l'activation.
    Si certaines ressources spécifiques manquaient ou si vous voulez mettre en cache d'autres pages par défaut, indiquez les URLs ici

Principe de fonctionnement

Le plugin Offline s'appuie sur les ServiceWorkers pour permettre la mise en cache des pages du site dans le navigateur et réutiliser ce cache quand la connexion internet est coupée.

Il faut comprendre que le ServiceWorker est un morceau de code autonome qu'on envoie dans le navigateur de l'utilisateur et qui va ensuite fonctionner indépendamment. Si on modifie le code ou la version du cache, l'utilisateur ne verra l'effet de ces modifications que lorsqu'il sera venu sur une page du site qui envoie une nouvelle mise à jour, et qu'il aura fermé puis réouvert son navigateur sur le site.

Ainsi si on arrête simplement de proposer la fonctionnalité sur le site, tous les utilisateurs vont continuer à fonctionner avec la dernière version du ServiceWorker qu'ils auront téléchargé.

Si donc on propose le mode offline aux utilisateurs connectés, ils auront l'installation de la fonction en étant connecté, puis les mises à jour de la fonction chaque fois qu'ils seront connectés. Mais quand ils seront déconnectés ils continueront à bénéficier de la fonctionnalité, simplement sans mise à jour du code ou sans prise en compte des nouvelles versions du cache.

De même, pour désactiver la fonctionnalité hors-ligne chez les visiteurs, il ne faut surtout pas désactiver le plugin ! Au lieu de cela il faut choisir le mode "Désinstaller le mode offline chez les visiteurs " et le laisser fonctionner ainsi — alternativement il faut récuperer le code de désinstallation, le mettre en dur dans le site et il est ensuite possible de désactiver le plugin (cf https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#updates)

URLs des services

Les scripts sont servis via les URLs suivantes :

  • /offline.api.install.js : script d'installation du service, qui est automatiquement intégré dans le <head> des pages publiques si vous choisissez l'activation automatique
  • /offline.api.sw.js : le service-worker en lui même, avec la configuration et ses fonctions utiles
  • /offline.api.uninstall.js : le script de desinstallation du service, , qui est automatiquement intégré dans le <head> des pages publiques si vous choisissez la désactivation

Dans le navigateur l'espace d'execution du service worker est indépendant de celui de la page. Ainsi une fonction JS disponible dans la page (et dans la console) ne sera pas disponible pour le service worker. De même les fonctions du service worker sont non accessibles depuis les pages web du site

En mode debug, ces URLs sont servies par action/api_offline.php de manière dynamique. En production le plugin produit des fichiers statiques minifiés dans local/offline/ qui sont servis de manière statique par Apache via la modification dans votre .htaccess Si vous voulez repasser en mode debug après avoir testé le mode production, il faut supprimer les fichiers statiques pour permettre au script PHP de reprendre la main.

Build

Le service worker est uploadé avec une liste d'URL à télécharger pour rendre disponibles certaines pages hors-ligne - a minima la home du site et la page d'erreur 404 offline. Pour rendre ces pages hors ligne, il faut construire la liste des URLs utilisées par ces pages. Ce mécanisme nécessite de télécharger chaque page, puis de les parcourir pour détecter les ressources utiles, et de vérifier leur validité (si une URL invalide ou en 404 est contenue dans la demande de mise en cache, aucune URL du lot n'est mise en cache à l'installation)

Ce build peut nécessiter du temps et n'est donc jamais fait de manière synchrone immédiate. Il est déclenché en tache de fond lors de l'enregistrement d'une nouvelle configuration

Si votre site n'est pas fréquenté ou que vous voulez en disposer immédiatement, vous pouvez lancer la commande via spip-cli :

spip offline:build:services

Téléchargement d'une partie du site "pour lire plus tard"

Le plugin propose une balise #BOUTON_TELECHARGER_OFFLINE qui peut s'utiliser ainsi :

<BOUCLE_titre(RUBRQUES){id_rubrique}>
<h1>#TITRE</h1>
[(#BOUTON_TELECHARGER_OFFLINE{rubrique,#ID_RUBRIQUE})]
</BOUCLE_titre>

Vous pouvez également passer 2 arguments optionnels supplémentaires correspondant au libellé du bouton de téléchargement actif et inactif

#BOUTON_TELECHARGER_OFFLINE{rubrique,#ID_RUBRIQUE,'Télécharger cette documentation','Téléchargement non disponible'}

Le bouton télécharger repose sur 2 squelettes :

  • le squelette offline/urls-{objet}.html (offline/urls-rubrique.html pour une rubrique) qui liste les URLs des pages à télécharger pour l'objet passé en #ENV du squelette. La liste est au format texte, une URL par ligne.
  • le squelette offline/bouton-telecharger.html qui gère l'affichage du bouton

La balise #BOUTON_TELECHARGER_OFFLINE se charge de vérifier l'existence d'un squelette offline/urls-{objet}.html. Si le squelette n'existe pas elle n'affichera rien.

La balise se charge également de vérifier qu'une liste d'URL a été construite pour cet objet, à partir de la liste des pages fournies par le squelette offline/urls-{objet}.html. Si la liste des URLs n'a pas été construite, elle lance une tache cron pour construire cette liste et affiche le bouton dans un état unactive. Pour des raisons de performance, le bouton n'est pas dynamique : il ne vérifie pas l'état de la liste à chaque affichage mais seulement à la mise à jour du cache. Il faudra donc attendre la prochaine mise à jour du cache ou forcer avec un ?var_mode=calcul pour qu'il sorte de cet état.

Quand la liste des URLs pour l'objet demandé est bien disponible, le bouton est affiché dans le HTML avec un état unactive activable. C'est une fonction javascript qui va se charger, côté client, de vérifier que le service worker est bien chargé, et de passer alors le bouton dans l'état active.

Au même moment il vérifie aussi si la liste des URLs a déja été chargée pour cet objet, et dans ce cas la classe offline-ok est ajoutée. Attention, cela ne prend pas en compte un éventuel changement de la liste des URLs suite à la mise à jour du contenu !

Enfin, quand l'utilisateur clique sur le bouton pour (re)télécharger le contenu, le bouton reçoit la classe offline-download

La construction de la liste des URLs de téléchargement d'un objet peut se faire automatiquement en cron, mais aussi manuellement, via spip-cli :

spip offline:build:urls --objet=rubrique --id_objet=1

Le build construit un fichier texte config/offline/objets/urls-rubrique-1.txt qui sera alors chargé au format json par le navigateur via l'URL offline.api.urlsdownload.json?objet=rubrique&id_objet=1

Utilisation sur un site multidomaines

La prise en compte de multidomaines n'est pas encore vraiment opérationnelle. Les URLs à télécharger doivent être relatives au domaine consulté, et les éventuelles URLs croisées seront bloquées en l'absence de directives CORS.

Mise à jour des listes d'URLs

Pour assurer une mise à jour de toutes les listes d'URLs existantes, il est possible de lancer depuis un cron système la commande

spip offline:rebuild

qui va reconstruire le service worker en mettant à jour la liste d'URLs à charger à l'installation, mais également repasser sur chaque fichier config/offline/objets/urls-{objet}-{id_objet}.txt existant et recalculer la liste des URLs à charger pour cet objet.

Considérations de performance

Pour optimiser le temps de chargement et revalidation des ressources (CSS, JS), il est préconisé de les minifier et concaténer en un seul fichier au sein de chaque page. Le plugin Compresseur livré avec SPIP permet cela automatiquement.

Toutesfois dans le cadre de l'utilisation du plugin Offline et de la mise en cache utilisateur des ressources pour navigation hors-ligne cette pratique doit-être reconsidérée.

Fonctionnel hors ligne

D'un point de vue fonctionnel, la concaténation en un seul fichier présente un inconvénient majeur : le nommage du fichier concaténé dépend de la liste des ressources qui y sont intégrées.

Ainsi, si une page particulière embarque une ressource supplémentaire, le fichier concaténé référencé ne sera pas le même que sur les autres pages, et la navigation hors ligne sur cette page sera brisée, ou a minima nécessite de mettre plus de ressources en cache.

Pire encore : pour garantir que le fichier concaténé se mets à jour quand une ressource intégrée change, celles-ci sont en général référencées avec un timestamp. Résultat : si une ressource est éditée et (même légèrement) modifiée, le fichier concaténé référencé sur la page change complètement de nom.

A contrario, si on garde des ressources non concaténées dans les pages, la gestion du cache hors-ligne est simplifiée, car chaque ressource peut-être servie de façon atomique. Qui plus est, le plugin gère les ressources timestampées intelligemment : la mise en cache se fait avec le timestamp, mais aussi sans le timestamp en tant que fallback generique. Ainsi, si pour une page en cache venait à manquer une ressource timestampée, le plugin sera capable de servir une version presque équivalent à titre de fallback (sauf en cas de grosses modificiations des ressources côté serveur, mais dans ce cas il est préconisé d'incrémenter la version de cache éditoriale)

Performance : Offline + HTTP/2

L'utilisation du plugin Offline nécessite https pour des raisons de sécurité (les service-workers permettent de modifier profondément le fonctionnement ET le contenu du site, il est donc impératif qu'ils soient délivrés à l'utilisateur sans risque de corruption par une attaque de type MITM) Du coup, si l'on combine https avec HTTP/2, alors il peut devenir globalement optimal de désactiver la concaténation des feuilles de style et des JS en un seul fichier, et de privilégier un service atomique des ressources.

Sur un hit à froid, hors utilisation du mode offline, et sans aucun cache navigateur, cette structure de page n'est plus que très peu pénalisante, car HTTP/2 compense en servant les ressources dans un pipeline, les unes à la suite des autres. Et une fois que le mode offline est actif, on bénéficie alors des mises en cache atomiques des ressources, qui sont toujours servies depuis le cache dès lors qu'elles ont été téléchargées une fois (le timestamp des URLs garantissant un nouveau téléchargement si la ressource change). Une page contenant juste une ressource de plus chargera beaucoup plus vite.

A partir du moment où la typologie des visiteur du site est majoritaire constituée de visiteurs réguliers, et avec HTTP/2, cette structure de page est donc à privilégier (on veillera toutefois à conserver une minification individuelle des ressources).

Biblio

References

Retex

Libs/Frameworks

Cours