Optimiser les performances de vos applications web sur mobile

le 28/05/2010 par Jessy Bernal
Tags: Software Engineering

La part du web consulté par des terminaux mobiles augmente très vite, et proposer à ces utilisateurs une version mobile devient un véritable enjeu stratégique. Malheureusement, le web mobile n'a pas grand chose à voir avec celui qu'on connaît sur desktop : du fait des faibles performances des appareils mobiles par rapport au desktop, des contraintes qui n'existaient plus vraiment sur nos ordinateurs sont de nouveaux d'actualité :

  • peu de CPU
  • peu de mémoire
  • peu d'autonomie
  • peu d'espace disque... voir pas du tout
  • une connexion intermittente... au mieux
  • une débit de connexion très variable

Yahoo! YSlow est certainement la référence pour trouver des optimisations sur son site web,  mais ces optimisations sont orientées pour le web sur Desktop et YSlow se base sur les prédicats suivants :

  • connexion internet sans coupure intermittente, toujours disponible et illimitée
  • une mémoire et un espace de stockage illimités

Si on prend nos contraintes sur mobile, on se rend compte que ça change pas mal la donne. Nous allons détailler ce qu'implique ces nouvelles contraintes sur votre application web en terme d'expérience utilisateur et vous donner quelques pistes d'optimisation qui émergent.

Les contraintes de mémoire et de stockage

Les mobiles disposant de peu de mémoire vive, cela impacte directement le cache du navigateur web mobile : Il est très restreint, et du coup, son comportement est différent que sur Desktop. Sur le Safari mobile de l'iPhone par exemple, les ressources (images, css, javascript, ...) supérieures à 25Ko ne sont tout simplement pas mises en cache. De plus seulement 19 des ressources du site seront mises en cache, tout juste 475ko au total (pour la version 2.2 de l'OS).

Les headers Expires et Cache-Control auront beau être là, le navigateur ne saura pas nécessairement en tirer parti. Conséquence pour votre utilisateur ? lorsqu'il clique sur le bouton précédent de son navigateur mobile,  parfois, sans raison apparente, celui-ci retélécharge la page alors qu'il venait de la  visiter quelques secondes auparavant. Frustrant.

Quelle solution peut-on préconiser ?

Sprite d'images, concaténation des fichiers JS et CSS ne sont pas systématiquement de bonnes optimisations. Ils peuvent facilement excéder cette taille et donc obliger vos utilisateurs à retélécharger ces ressources inutilement. Il faut donc parfois éviter de générer des ressources statiques trop volumineuses (moins de 25Ko) quitte à faire plusieurs requêtes.

Les contraintes de connexion au réseau

Le contexte d'utilisation des mobiles implique des utilisations dégradées variables : une connexion constamment interrompue (dans le métro, par exemple), un débit très variable, et parfois pas de connexion du tout. Cela provoque des temps de latence visible et désagréable, il faut donc faire notre maximum pour minimiser, voir supprimer, cette latence.

Une connexion fréquemment interrompue

Il faut considérer que le chargement de la page peut s'interrompre à tout moment et la reprise sur erreur des navigateurs n'est pas vraiment optimale. Si votre ressource n'avait pas été complètement téléchargée, il est propable que le navigateur relance le téléchargement intégral de cette ressource: si votre utilisateur doit télécharger votre fichier JS de 500ko et le CSS de 500ko aussi, il pourra s'y prendre à plusieurs reprises pour rendre la page. Pas idéal. L'idée là encore est de trouver un bon compromis entre la taille des fichiers et le nombre de fichiers à télécharger.

Mais on reste dépendant du cache en mémoire du navigateur, et on l'a dit, on ne peut pas vraiment compter sur lui. Il vaut mieux donc appliquer autant que possible le pattern suivant : toute ressource statique ne devrait être téléchargée par le client qu'une seule fois (tant qu'elle ne change pas). N'oubliez pas aussi que certains utilisateurs payent (et cher) à la donnée transférée, et ce n'est pas très honnête de leur refaire télécharger du contenu inutilement. Pour être sûr de ne pas retélécharger une ressource statique, HTML 5 apporte la réponse avec le application cache. Sa mise en place consiste à lister toutes les resources statiques. Une fois téléchargées, le navigateur ne fera plus aucune requête. Si la ressource change, ou si vous devez rajouter une ressource, il faudra faire comprendre au navigateur qu'il doit invalider le cache. Cette gestion du cache manifest coté serveur n'est pas très complexe mais fastidieuse s'il y a de nombreuses ressources à gérer. Vos frameworks web ne tarderont pas à proposer des solutions pour vous faire le travail (c'est déjà le cas pour Rails avec le middleware rack-offline par exemple).

Aucune connexion

L'application cache nous a réglé le problème des ressources statiques, donc votre utilisateur peut accèder à votre site. Mais votre site est très probablement dynamique, et on a encore un problème : si votre utilisateur accède en déconnecté sur votre site (allez, au hasard, votre site d'actualité) c'est quand même dommage de ne pas lui laisser relire les articles qu'il avait déjà chargé. Ce contenu étant chargé dynamiquement, il faut le stocker quelque part. HTML 5 offre deux solutions aux développeurs. Une vraie base de données locale (encore complexe à mettre en place et à maintenir faute de frameworks mures) et le localStorage, très intuitif, qui fonctionne avec un mécanisme simple de clé/valeur. On stocke ainsi facilement des objets JavaScript sérialisé en JSON.

Un débit très variable

Une connexion lente est toujours frustante pour votre utilisateur, maintenant habitué au haut débit. Spécialement lorsqu'il navigue sur du contenu qu'il a déjà vu. Prenons un exemple d'utilisation : j'utilise cette application d'annonces immoblières où, suite à une recherche, je rajoute des annonces à ma liste de favoris. Lorsque je reviens 2 jours plus tard sur l'application et que je repasse en revue ces annonces, j'attend systématiquement quelques secondes pour revoir les annonces. L'application est bien obligée de vérifier si l'annonce n'a pas changé et si elle est toujours disponible, mais pendant ce temps, j'attends toujours.

L'idée pour améliorer va être de présenter tout de suite à l'utilsateur un contenu qui a été mis en cache s'il existe. Pendant ce temps, le navigateur récupère tant bien que mal la mise à jour. Une fois qu'il l'a, il la remet dans le cache. Si les deux versions sont différentes, il faut avertir l'utilisateur qu'une version plus récente de son contenu à été récupérée et qu'il peut rafraîchir le contenu. On ne va surtout pas rafraîchir la page sans l'avertir, car s'il est déjà en train de lire/intéragir avec le contenu, cela pourrait prêter à confusion).

Le plugin jquery-offline implémente cette stratégie et permet de la mettre en place en quelques lignes. Il utilise le localStorage pour stocker les informations prevenant d'une requête AJAX au format JSON en utilisant l'url comme clé pour le cache. Voici un exemple d'appel :

var updateArticles = function(callback) {
$.retrieveJSON("/article_list.json", function(json, status) {
  var content = $("#articleTemplate").render( json );
  $("#articles").empty().append(content);
  if (status == 'success') {
    // dans le cas où on ne provient pas du cache mais de la requête,
    // on peut gérer un comportement différent
  }
});};

Pour conclure, on voit que tous les outils sont à notre disposition pour minimiser l'impact de ces nouvelles contraintes. Malheureusement, les frameworks Javascript dédiés aux applications mobiles se concentrent encore essentiellement sur la partie présentation et navigation pour rattraper leur retard sur les API de GUI natives. Résultat, c'est à chaque développeur de se poser les bonnes questions dès le départ pour faire une application mobile agréable à utiliser. De nombreux bon patterns sont encore à découvrir et les frameworks Javascript ont encore de la marge pour être plus productifs. Espérons que la frustration actuelle lorsqu'on navigue sur le web avec son mobile sera bientôt du passé.