Applications Web à chargement instantané avec une architecture de shell d'application

Addy Osmani
Addy Osmani

Un shell d'application correspond au code HTML, CSS et JavaScript minimal permettant le fonctionnement d'une interface utilisateur. Le shell de l'application doit:

  • chargement rapide
  • être mis en cache
  • afficher dynamiquement du contenu

Un shell d'application est le secret pour bénéficier de performances fiables. Considérez le shell de votre application comme le bundle de code que vous publieriez sur une plate-forme de téléchargement d'applications si vous développiez une application native. Il s'agit de la charge nécessaire pour démarrer, mais peut-être pas toute l'histoire. Elle permet de conserver votre interface utilisateur en local et d'extraire du contenu de manière dynamique via une API.

App Shell Séparation du shell HTML, JS et CSS, et du contenu HTML

Contexte

L'article d'Alex Russell sur les progressive web apps (applications Web progressives) décrit comment progressivement une application Web peut changer grâce à l'utilisation et au consentement de l'utilisateur, afin d'offrir une expérience plus semblable à celle d'une application native, avec une utilisation hors connexion, des notifications push et la possibilité d'être ajoutée à l'écran d'accueil. Tout dépend des avantages offerts par le service worker en termes de fonctionnalité et de performances, ainsi que de ses capacités de mise en cache. Vous pouvez ainsi vous concentrer sur la rapidité, avec le même chargement instantané et les mêmes mises à jour régulières que pour les applications natives.

Pour tirer pleinement parti de ces fonctionnalités, nous avons besoin d'une nouvelle façon de penser les sites Web: l'architecture du shell d'application.

Voyons comment structurer votre application à l'aide d'une architecture shell d'application augmentée de service worker. Nous allons examiner le rendu côté client et côté serveur, et partager un exemple de bout en bout que vous pouvez essayer aujourd'hui.

Pour souligner ce point, l'exemple ci-dessous montre le premier chargement d'une application utilisant cette architecture. Le message "L'application est prête pour une utilisation hors connexion" s'affiche en bas de l'écran. Si une mise à jour du shell devient disponible ultérieurement, nous pouvons demander à l'utilisateur d'actualiser la page pour la nouvelle version.

Image d'un service worker s'exécutant dans les outils de développement pour le shell d'application

À nouveau, que sont les service workers ?

Un service worker est un script qui s'exécute en arrière-plan, indépendamment de votre page Web. Il répond aux événements, y compris aux requêtes réseau effectuées à partir des pages qu'il diffuse et aux notifications push depuis votre serveur. La durée de vie d'un service worker est volontairement courte. Il s'active lorsqu'il reçoit un événement et ne s'exécute que pendant la durée nécessaire à son traitement.

Par ailleurs, les service workers disposent d'un ensemble limité d'API par rapport à JavaScript dans un contexte de navigation normal. Cette pratique est la norme pour les nœuds de calcul sur le Web. Un service worker ne peut pas accéder au DOM, mais peut accéder à des éléments tels que l'API Cache. Il peut également envoyer des requêtes réseau à l'aide de l'API Fetch. Vous pouvez également utiliser l'API IndexedDB et postMessage() pour la persistance des données et l'échange de messages entre le service worker et les pages qu'il contrôle. Les événements push envoyés depuis votre serveur peuvent appeler l'API Notification pour accroître l'engagement utilisateur.

Un service worker peut intercepter les requêtes réseau effectuées à partir d'une page (ce qui déclenche un événement d'extraction sur le service worker) et renvoyer une réponse récupérée sur le réseau, dans un cache local ou même créée de façon automatisée. En fait, il s'agit d'un proxy programmable dans le navigateur. L'avantage, c'est que, quelle que soit la provenance de la réponse, la page Web semble ne pas avoir été impliquée dans le service worker.

Pour en savoir plus sur les service workers, consultez la page Présentation des service workers.

Avantages en matière de performance

Les service workers sont puissants pour la mise en cache hors connexion, mais ils offrent également des gains de performances significatifs avec un chargement instantané pour les visites répétées sur votre site ou votre application Web. Vous pouvez mettre en cache le shell de votre application pour qu'elle fonctionne hors connexion et insérer son contenu à l'aide de JavaScript.

En cas de visites répétées, vous pouvez ainsi obtenir des pixels pertinents à l'écran sans le réseau, même si votre contenu provient finalement de ce réseau. Cela revient à afficher les barres d'outils et les fiches immédiatement, puis à charger le reste de votre contenu progressivement.

Pour tester cette architecture sur des appareils réels, nous avons exécuté notre exemple de shell d'application sur WebPageTest.org et avons affiché les résultats ci-dessous.

Test 1: Test avec le câble sur un Nexus 5 à l'aide de Chrome pour les développeurs

La première vue de l'application doit extraire toutes les ressources du réseau et n'obtient un rendu significatif qu'au bout de 1,2 seconde. Grâce à la mise en cache des service workers, notre visite répétée obtient un rendu significatif et se termine complètement en 0,5 seconde.

Schéma de peinture de test de la page Web pour la connexion par câble

Test 2: Test en 3G avec un Nexus 5 à l'aide de Chrome pour les développeurs

Nous pouvons également tester notre exemple avec une connexion 3G légèrement plus lente. Cette fois, cela prend 2,5 secondes lors de la première visite pour notre première peinture significative. Le chargement complet de la page prend 7,1 secondes. Grâce à la mise en cache des service workers, notre visite répétée obtient un rendu significatif et se termine complètement en 0,8 seconde.

Schéma "Pain de la page Web" pour une connexion 3G

Les autres vues racontent une histoire similaire. Comparez les trois secondes nécessaires pour obtenir la première peinture significative dans le shell de l'application:

Tracer la timeline pour la première vue à partir du test de la page Web

à la 0,9 seconde nécessaire lorsque la même page est chargée depuis le cache de notre service worker. Les utilisateurs finaux gagnent plus de deux secondes de temps.

Appliquer la timeline pour répéter la vue d'une page Web test

Grâce à l'architecture du shell d'application, vous pouvez obtenir des performances similaires et fiables pour vos propres applications.

Un service worker nous oblige-t-il à repenser la structure des applications ?

Les service workers entraînent quelques modifications subtiles dans l'architecture des applications. Plutôt que d'écraser toute votre application dans une chaîne HTML, il peut être préférable d'utiliser le style AJAX. C'est là que vous disposez d'une interface système (qui est toujours mise en cache et peut toujours démarrer sans le réseau) et d'un contenu actualisé régulièrement et géré séparément.

Les conséquences de cette division sont importantes. Lors de la première visite, vous pouvez afficher le contenu sur le serveur et installer le service worker sur le client. Lors des visites suivantes, vous n'aurez besoin que de demander des données.

Qu'en est-il de l'amélioration progressive ?

Bien que le service worker ne soit pas compatible avec tous les navigateurs pour le moment, l'architecture du shell du contenu de l'application utilise une amélioration progressive pour garantir que tout le monde peut accéder au contenu. Prenons notre exemple de projet.

Vous trouverez ci-dessous la version complète affichée dans Chrome, Firefox Nightly et Safari. Tout à gauche, vous pouvez voir la version de Safari dans laquelle le contenu est affiché sur le serveur sans service worker. À droite, les versions Nuit de Chrome et Firefox sont affichées avec la technologie d'un service worker.

Image du shell d'application chargé dans Safari, Chrome et Firefox

Quand est-il judicieux d'utiliser cette architecture ?

L'architecture du shell d'application est la plus logique pour les applications et les sites dynamiques. Si votre site est petit et statique, vous n'aurez probablement pas besoin d'un shell d'application. Vous pouvez simplement mettre en cache l'intégralité du site à l'aide d'une étape oninstall de service worker. Utilisez l'approche la plus logique pour votre projet. Un certain nombre de frameworks JavaScript encouragent déjà à séparer la logique de votre application du contenu, ce qui rend ce modèle plus simple à appliquer.

Existe-t-il déjà des applications de production qui utilisent ce modèle ?

L'architecture du shell d'application est possible en quelques modifications au niveau de l'interface utilisateur globale de votre application. Elle a parfaitement fonctionné pour les sites de grande envergure tels que la progressive web app Google I/O 2015 et la boîte de réception de Google.

Image du chargement de la boîte de réception Google. Illustre la boîte de réception à l'aide d'un service worker.

Les shells d'application hors connexion sont un atout majeur en termes de performances. Ils se manifestent également bien dans l'application Wikipédia hors connexion de Jake Archibld et la progressive web app de Flipkart Lite.

Captures d'écran de la démonstration Wikipédia de Jake Archibld

Expliquer l'architecture

Lors du premier chargement, votre objectif est d'afficher un contenu pertinent sur l'écran de l'utilisateur le plus rapidement possible.

Premiers chargements et autres pages

Schéma du premier chargement avec le shell d'application

En général, l'architecture du shell d'application:

  • Privilégiez le chargement initial, mais laissez le service worker mettre en cache le shell d'application pour que les visites répétées n'exigent pas que le shell soit de nouveau récupéré sur le réseau.

  • Le chargement différé ou en arrière-plan charge tout le reste. Une bonne option consiste à utiliser la mise en cache après lecture complète pour le contenu dynamique.

  • Utilisez les outils de service worker tels que sw-precache pour mettre en cache et mettre à jour de manière fiable le service worker qui gère votre contenu statique. (Nous aborderons le sw-precache plus loin.)

Pour ce faire, procédez comme suit:

  • Le serveur envoie du contenu HTML que le client peut afficher et utilise des en-têtes d'expiration du cache HTTP très lointains pour tenir compte des navigateurs non compatibles avec les service workers. Il diffusera les noms de fichiers à l'aide de hachages afin de permettre à la fois la "gestion des versions" et les mises à jour faciles pour plus tard dans le cycle de vie de l'application.

  • Page(s) incluront des styles CSS intégrés dans une balise <style> au sein du <head> du document pour fournir une première analyse rapide du shell de l'application. Chaque page charge de manière asynchrone le code JavaScript nécessaire à la vue actuelle. Étant donné que le CSS ne peut pas être chargé de manière asynchrone, nous pouvons demander des styles à l'aide de JavaScript, car il s'agit d'un outil asynchrone plutôt que synchrone et par analyseur. Nous pouvons également utiliser requestAnimationFrame() pour éviter les cas où nous pourrions recevoir un succès de cache rapide et que des styles pourraient s'intégrer accidentellement dans le chemin critique du rendu. requestAnimationFrame() force le premier cadre à être peint avant le chargement des styles. Une autre option consiste à utiliser des projets tels que loadCSS de Filament Group pour demander du code CSS de manière asynchrone à l'aide de JavaScript.

  • Le service worker stocke une entrée en cache du shell de l'application de sorte que, lors des visites répétées, l'interface système puisse être chargée entièrement à partir du cache du service worker, sauf si une mise à jour est disponible sur le réseau.

App Shell pour le contenu

Une implémentation pratique

Nous avons écrit un exemple entièrement fonctionnel à l'aide de l'architecture du shell d'application, du code JavaScript vanilla ES2015 pour le client et du fichier Express.js pour le serveur. Bien entendu, rien ne vous empêche d'utiliser votre propre pile pour les parties client ou serveur (ex. : PHP, Ruby, Python).

Cycle de vie d'un service worker

Pour notre projet de shell d'application, nous utilisons sw-precache, qui offre le cycle de vie de service worker suivant:

Événement Action
Installer Mettez en cache le shell de l'application et les autres ressources de l'application monopage.
Activation Videz les anciens caches.
Fetch Diffuser une application Web d'une seule page pour les URL et utiliser le cache pour les assets et les partiels prédéfinis. Utilisez le réseau pour les autres requêtes.

Bits de serveur

Dans cette architecture, un composant côté serveur (ici, écrit en Express) doit pouvoir traiter le contenu et la présentation séparément. Le contenu peut être ajouté à une mise en page HTML qui entraîne un affichage statique de la page, ou il peut être diffusé séparément et chargé de manière dynamique.

Il est tout à fait compréhensible que votre configuration côté serveur puisse être très différente de celle que nous utilisons pour notre application de démonstration. Ce modèle d'applications Web est réalisable avec la plupart des configurations de serveur, bien qu'elle nécessite quelques modifications d'architecture. Nous avons constaté que le modèle suivant fonctionne très bien:

Schéma de l&#39;architecture du shell d&#39;application
  • Les points de terminaison sont définis pour trois parties de votre application: les URL visibles par l'utilisateur (index/caractère générique), le shell de l'application (service worker) et vos parties HTML.

  • Chaque point de terminaison est doté d'un contrôleur qui utilise une disposition en guidons qui, à son tour, peut récupérer des vues partielles et des vues. En termes simples, les partiels sont des vues qui sont des blocs de code HTML copiés dans la page finale. Remarque: Les frameworks JavaScript qui synchronisent les données de manière plus avancée sont souvent plus faciles à transférer vers une architecture de shell d'application. Ils ont tendance à utiliser la liaison de données et la synchronisation plutôt que des partiels.

  • L'internaute voit d'abord une page statique avec du contenu. Cette page enregistre un service worker, s'il est compatible, qui met en cache le shell de l'application et tout ce dont il dépend (CSS, JS, etc.).

  • Le shell d'application agit alors comme une application Web monopage, en utilisant JavaScript vers XHR dans le contenu d'une URL spécifique. Les appels XHR sont effectués vers un point de terminaison /partials* qui renvoie le petit bloc de code HTML, CSS et JS nécessaire à l'affichage de ce contenu. Remarque: Il existe de nombreuses méthodes, et XHR n'est qu'une d'entre elles. Certaines applications intègrent leurs données (peut-être en utilisant JSON) pour le rendu initial et ne sont donc pas "statiques" au sens HTML aplati.

  • Les navigateurs non compatibles avec les service workers devraient toujours bénéficier d'une expérience de remplacement. Dans notre démonstration, nous revenons à l'affichage statique de base côté serveur, mais ce n'est qu'une des nombreuses options disponibles. L'aspect du service worker vous offre de nouvelles opportunités d'améliorer les performances de votre application de type application monopage à l'aide du shell d'application mis en cache.

Gestion des versions de fichiers

Une question revient à savoir comment gérer les versions et la mise à jour des fichiers. Cette option est spécifique à l'application. Les options sont les suivantes:

  • Réseau d'abord, et sinon, utiliser la version mise en cache.

  • Réseau uniquement et échouer en mode hors connexion.

  • Mettez en cache l'ancienne version et mettez-la à jour ultérieurement.

Pour le shell d'application lui-même, vous devez adopter une approche de priorité à la mise en cache pour la configuration de votre service worker. Si vous ne mettez pas en cache le shell d'application, vous n'avez pas correctement adopté l'architecture.

Outils

Nous disposons d'un certain nombre de bibliothèques d'aide de service worker qui facilitent la configuration de la mise en cache préalable du shell de votre application ou de la gestion des schémas de mise en cache courants.

Capture d&#39;écran du site de la bibliothèque Service Workers sur Web Fundamentals

Utiliser sw-precache pour le shell d'application

L'utilisation de sw-precache pour mettre en cache le shell d'application doit gérer les problèmes liés aux révisions de fichiers, aux questions d'installation/d'activation et au scénario de récupération du shell d'application. Déposez sw-precache dans le processus de compilation de votre application et utilisez des caractères génériques configurables pour récupérer vos ressources statiques. Plutôt que d'élaborer manuellement votre script service worker, laissez sw-precache en générer un qui gère votre cache de manière sécurisée et efficace, à l'aide d'un gestionnaire d'extraction mis en cache.

Les premières visites sur votre application déclenchent la mise en cache préalable de l'ensemble complet de ressources nécessaires. Cette procédure est semblable à celle qui consiste à installer une application native à partir d'une plate-forme de téléchargement d'applications. Lorsque les utilisateurs reviennent dans votre application, seules les ressources mises à jour sont téléchargées. Dans notre démonstration, nous informons les utilisateurs lorsqu'une nouvelle interface système est disponible avec le message "Mises à jour de l'application. Actualisez la page pour la nouvelle version." Ce modèle permet d'indiquer aux utilisateurs qu'ils peuvent actualiser la page pour obtenir la dernière version.

Utiliser Sw-Toolbox pour la mise en cache de l'environnement d'exécution

Utilisez sw-toolbox pour la mise en cache de l'environnement d'exécution avec différentes stratégies en fonction de la ressource:

  • cacheFirst pour les images, ainsi qu'un cache nommé dédié doté d'une règle d'expiration personnalisée de N maxEntrys.

  • networkFirst ou le plus rapide pour les requêtes API, en fonction de l'actualisation du contenu souhaitée. Le plus rapide peut suffire, mais si un flux API spécifique est mis à jour fréquemment, utilisez networkFirst.

Conclusion

Les architectures de shell d'application présentent plusieurs avantages, mais n'ont de sens que pour certaines classes d'applications. Le modèle étant encore jeune, il peut être utile d'évaluer les bénéfices en termes d'efforts et de performances globales de cette architecture.

Lors de nos tests, nous avons tiré parti du partage de modèles entre le client et le serveur afin de minimiser le travail de création de deux couches d'application. L'amélioration progressive reste ainsi une fonctionnalité essentielle.

Si vous envisagez déjà d'utiliser des service workers dans votre application, examinez l'architecture et déterminez si elle est pertinente pour vos propres projets.

Nous remercions nos évaluateurs: Jeff Posnick, Paul Lewis, Alex Russell, Seth Thompson, Rob Dodson, Taylor Savage et Joe Medley.