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

Addy Osmani
Addy Osmani
Matt Gaunt

Un shell d'application correspond au code HTML, CSS et JavaScript minimal utilisé pour alimenter une interface utilisateur. Le shell de l'application doit:

  • chargement rapide
  • être mis en cache
  • afficher le contenu de manière dynamique

Un shell d'application est le secret pour obtenir de bonnes performances et de manière fiable. 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 créiez une application native. Il s'agit de la charge nécessaire pour démarrer, mais pas de toute l'histoire. Elle conserve votre interface utilisateur en local et extrait le contenu de manière dynamique via une API.

Séparation du shell d'application entre le shell HTML, JS et CSS et le contenu HTML

Contexte

L'article d'Alex Russell sur les progressive web apps explique comment une application Web peut changer progressivement en fonction de l'utilisation et du consentement de l'utilisateur. Elle offre ainsi une expérience semblable à celle d'une application native, avec une compatibilité hors connexion, des notifications push et la possibilité d'être ajoutée à l'écran d'accueil. Cela dépend essentiellement des fonctionnalités et des avantages en termes de performances du service worker, ainsi que de leurs capacités de mise en cache. Vous pouvez ainsi vous concentrer sur la vitesse, ce qui offre à vos applications Web le même chargement instantané et des mises à jour régulières que dans les applications natives.

Pour tirer pleinement parti de ces fonctionnalités, nous avons besoin d'une nouvelle approche des sites Web: l'architecture du shell d'application.

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

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

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

Qu'est-ce qu'un service worker ?

Un service worker est un script qui s'exécute en arrière-plan, séparément 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 envoyées depuis votre serveur. La durée de vie d'un service worker est intentionnellement courte. Elle s'active lorsqu'elle 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. Il s'agit de 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. L'API IndexedDB et postMessage() peuvent également être utilisées pour la persistance des données et la messagerie 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 de récupération sur le service worker) et renvoyer une réponse extraite du réseau, extraite d'un cache local ou même construite par programmation. En fait, il s'agit d'un proxy programmable dans le navigateur. Ce qui est intéressant, c'est que, quelle que soit l'origine de la réponse, la page Web renvoie vers la page Web comme si aucun service worker n'était impliqué.

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

Avantages en matière de performance

Les service workers sont très utiles pour la mise en cache hors connexion, mais ils offrent également d'importantes performances sous la forme d'un chargement instantané en cas de visites répétées sur votre site ou application Web. Vous pouvez mettre en cache le shell de votre application afin qu'il fonctionne hors connexion et qu'il insère son contenu à l'aide de JavaScript.

Lors des visites répétées, cela vous permet d'afficher des pixels pertinents à l'écran sans réseau, même si c'est le cas par la suite. Cela revient à afficher immédiatement des barres d'outils et des fiches, 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 présenté les résultats ci-dessous.

Test 1:Test du câble sur un appareil Nexus 5 avec Chrome pour les développeurs

La première vue de l'application doit récupérer toutes les ressources du réseau et n'obtient un rendu pertinent 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 pertinent et le chargement se termine en 0,5 seconde.

Diagramme de peinture de test de page Web pour la connexion du câble

Test 2:Test de la 3G avec un Nexus 5 avec l'application Chrome pour les développeurs

Nous pouvons également tester notre exemple avec une connexion 3G légèrement plus lente. Cette fois, il faut 2,5 secondes à la première visite pour que notre première peinture soit 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 aboutit à une peinture significative et un chargement complet se termine en 0,8 seconde.

Schéma illustrant un test de page Web pour une connexion 3G

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

Application de la chronologie pour la première vue à partir du test de page Web

au temps de 0,9 seconde nécessaire pour charger une même page depuis le cache du service worker. Les utilisateurs finaux bénéficient ainsi d'un gain de temps de plus de deux secondes.

Application de la chronologie pour une vue répétée à partir du test de page Web

L'architecture du shell d'application permet d'améliorer des performances similaires et fiables pour vos propres applications.

Les service workers nous obligent-ils à repenser la structure des applications ?

Les service workers impliquent des modifications subtiles dans l'architecture des applications. Plutôt que d'écraser toute votre application dans une chaîne HTML, il peut être utile d'utiliser le style AJAX. Il s'agit d'un shell (qui est toujours mis 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 implications de cette division sont considérables. 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'avez besoin que de demander des données.

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

Même si les service worker ne sont actuellement pas compatibles avec tous les navigateurs, l'architecture du shell de contenu de l'application utilise une amélioration progressive pour que tout le monde puisse accéder au contenu. Prenons par exemple notre exemple de projet.

Vous pouvez voir 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, nous voyons les versions nocturnes de Chrome et de Firefox, qui sont fournies par un service worker.

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

Quand est-il pertinent d'utiliser cette architecture ?

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

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

L'architecture du shell d'application est possible en modifiant simplement l'interface utilisateur globale de votre application. De plus, elle est parfaitement adaptée aux sites de grande envergure tels que la progressive web app I/O 2015 de Google et la boîte de réception de Google.

Image de la boîte de réception Google en cours de chargement. Illustration d'Boîte de réception à l'aide d'un service worker.

Les shells d'applications hors connexion constituent une amélioration majeure des performances. Ils se retrouvent également bien dans l'application Wikipédia hors connexion de Jake Archibald et dans la progressive web app de Flipkart Lite.

Captures d'écran de la démo Wikipédia de Jake Archibald.

Expliquer l'architecture

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

Chargez d'abord d'autres pages

Schéma du premier chargement avec le shell de l'application

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

  • Priorisez le chargement initial, mais laissez le service worker mettre en cache le shell de l'application afin que les visites répétées n'exigent pas que le shell soit de nouveau récupéré depuis le réseau.

  • Chargez tout le reste en arrière-plan ou de manière différée. Une bonne solution consiste à utiliser la mise en cache en lecture seule pour le contenu dynamique.

  • Utilisez des outils de service worker tels que sw-precache, par exemple pour mettre en cache et mettre à jour de manière fiable le service worker qui gère votre contenu statique. (Plus d'informations sur sw-precache plus loin dans cet article.)

Pour y parvenir:

  • Le serveur envoie le contenu HTML que le client peut afficher et utilise des en-têtes d'expiration du cache HTTP à venir pour les navigateurs qui ne sont pas compatibles avec les service workers. Elle diffusera les noms de fichiers au moyen de hachages pour permettre à la fois la gestion des versions et les mises à jour faciles pour plus tard dans le cycle de vie de l'application.

  • Les pages incluront des styles CSS intégrés dans une balise <style> dans le document <head> pour fournir un premier aperçu rapide du shell de l'application. Chaque page chargera de manière asynchrone le code JavaScript nécessaire à la vue actuelle. Étant donné que CSS ne peut pas être chargé de manière asynchrone, nous pouvons demander des styles à l'aide de JavaScript, car il EST asynchrone plutôt que piloté par l'analyseur et synchrone. Nous pouvons également utiliser requestAnimationFrame() pour éviter les cas où un succès de cache rapide risquerait d'être brisé et où des styles pourraient accidentellement être intégrés au chemin critique du rendu. requestAnimationFrame() force l'affichage du premier frame 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.

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

Shell d&#39;application 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 d'Express.js pour le serveur. Bien entendu, rien ne vous empêche d'utiliser votre propre pile pour les parties client ou serveur (par exemple, PHP, Ruby, Python).

Cycle de vie des service workers

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

Événement Action
Installer Mettre en cache le shell de l'application et les autres ressources d'application monopage
Activer Videz les anciens caches.
Récupération Diffusez une application Web sur une seule page pour les URL, et utilisez le cache pour les assets et les partiels prédéfinis. Utilisez le réseau pour d'autres requêtes.

Bits de serveur

Dans cette architecture, un composant côté serveur (dans notre cas, écrit en Express) doit pouvoir traiter séparément le contenu et la présentation. Le contenu peut être ajouté à une mise en page HTML qui génère un rendu statique de la page, ou être diffusé séparément et chargé dynamiquement.

Votre configuration côté serveur peut considérablement différer 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, mais il peut nécessiter quelques modifications de l'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: l'URL visible par l'utilisateur (index/caractère générique), le shell de l'application (service worker) et vos partiels HTML.

  • Chaque point de terminaison dispose d'un contrôleur doté d'une disposition en guidons, qui peut extraire des vues et des portions de guidon. En termes simples, les partials sont des vues qui sont des morceaux de code HTML copiés dans la page finale. Remarque: Les frameworks JavaScript qui offrent une synchronisation plus avancée des données sont souvent plus faciles à transférer vers une architecture de shell d'application. Elles ont tendance à utiliser la liaison de données et la synchronisation plutôt que des partiels.

  • L'internaute voit initialement une page statique avec du contenu. Cette page enregistre un service worker, le cas échéant, qui met en cache le shell de l'application et tous les éléments dont il dépend (CSS, JS, etc.).

  • Le shell de l'application agit ensuite comme une application Web monopage, en utilisant JavaScript pour XHR dans le contenu d'une URL spécifique. Les appels XHR sont adressés à un point de terminaison /partials* qui renvoie la petite portion de code HTML, CSS et JS nécessaire pour afficher ce contenu. Remarque: Il existe de nombreuses façons d'aborder cela, et XHR n'en est que l'une d'entre elles. Certaines applications incorporent leurs données (peut-être au format JSON) pour le rendu initial et ne sont donc pas "statiques" au sens HTML aplati.

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

Gestion des versions de fichiers

Une question qui se pose est de savoir comment gérer la gestion des versions et la mise à jour des fichiers. Ceci est spécifique à l'application et les options sont les suivantes:

  • d'abord sur le réseau. Sinon, utilisez la version mise en cache.

  • Réseau uniquement et échec en cas d'état hors connexion.

  • Mettez l'ancienne version en cache et effectuez la mise à jour plus tard.

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

Outils

Nous gérons un certain nombre de bibliothèques d'aide de service worker qui facilitent la mise en cache préalable du shell de votre application ou la gestion des modèles de mise en cache courants.

Capture d&#39;écran du site de la bibliothèque Service Worker dans les principes de base du Web

Utiliser sw-precache pour le shell d'application

L'utilisation de sw-precache pour mettre en cache le shell de l'application permet de résoudre les problèmes liés aux révisions de fichiers, aux questions d'installation/activation et au scénario de récupération du shell de l'application. Ajoutez 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 de service worker, laissez sw-precache en générer un qui gère votre cache de manière efficace et sécurisée, à l'aide d'un gestionnaire de récupération axé sur le cache.

Les premières visites dans votre application déclenchent la mise en cache préalable de l'ensemble des ressources nécessaires. Cette procédure est semblable à l'installation d'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'un nouveau shell est disponible avec le message "Mises à jour de l'application. Actualisez pour voir la nouvelle version." Ce modèle est un moyen simple d'informer les utilisateurs qu'ils peuvent s'actualiser pour obtenir la dernière version.

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

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

  • cacheFirst pour les images, avec un cache nommé dédié avec une règle d'expiration personnalisée de N maxEntries.

  • networkFirst ou la plus rapide pour les requêtes API, selon l'actualisation du contenu souhaitée. Le plus rapide peut sembler acceptable, mais si un flux d'API spécifique est fréquemment mis à jour, utilisez networkFirst.

Conclusion

Les architectures de shell d'application présentent plusieurs avantages, mais ne sont pertinentes que pour certaines classes d'applications. Le modèle est encore jeune, et il serait utile d'évaluer les efforts et les performances globales de cette architecture.

Dans nos expériences, nous avons tiré parti du partage de modèles entre le client et le serveur afin de réduire le travail de création de deux couches d'application. Cela garantit que l'amélioration progressive reste 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 contributeurs: Jeff Posnick, Paul Lewis, Alex Russell, Seth Thompson, Rob Dodson, Taylor Savage et Joe Medley.