V1V2
Blog & Projects

Une application web offline HTML5 avec le cache manifest

Dossier avec une planète

Parmi l'ensemble des nouveautés apportées par l'HTML5, on entend surtout parler des plus "multimédia" d'entre elles, c'est à dire des balises audio, video, canvas... Pourtant, ces fonctionnalités ne sont pas vraiment nouvelles pour l'utilisateur final, qui était déjà habitué à de tels contenus avec Flash. L'une des réelles révolutions apportées par l'HTML5, et une des moins médiatisée, est la possibilité de créer des applications web offline.

Introduction

Le terme "application offline" doit être un peu nuancé et compris plutôt comme : "Des applications web qui continuent à fonctionner même quand la connexion internet se coupe". De plus, le terme "application" a été banalisé par l'avènement des smartphones, mais dans ce cadre, on doit plutôt comprendre des "sites web". Cela dit la différence entre les deux va s'amoindrir de plus en plus dans les années à venir...

Ces applications sont donc principalement destinées aux téléphones portables, qui subissent régulièrement des déconnexions. Prenons un exemple :

Vous rédigez un mail sur votre iPhone ou votre Android, connecté à internet avec la 3G, sur l'application Gmail. Puisque vous êtes bien entendu quelqu'un de mobile et dynamique (c'est évident !) vous vous déplacez avec votre smartphone. Puis vous prenez un ascenseur, ou votre métro passe dans un tunnel, ce qui coupe votre connexion 3G. Que se passe-t-il ?

Tout à fait ! L'application Gmail ne se ferme pas subitement en criant "Alerte ! Je ne capte plus le réseau 3G, fermeture de l'application !". Elle continue de fonctionner en "offline". Vous pouvez même "envoyer" un mail sans avoir d'accès à internet, et celui-ci sera mis en attente localement, puis réellement envoyé dès que vous récupérerez votre connexion.

On est donc face à des applications "hybrides" qui fonctionnent à la fois online et offline.

Bien au contraire, c'est très simple, et vous saurez le faire après avoir lu ce tutoriel !

La compatibilité

Comme toute fonctionnalité HTML5, une question importante se pose : la compatibilité. Qu'en est-il avec les applications offline ? Elles fonctionnent déjà sur tous les navigateurs sauf Internet Explorer. Et Internet Explorer 9 ne supportera pas cette fonctionnalité non plus.

Haha du calme... Croyez-vous que votre ordinateur fixe subisse fréquemment des déconnexions du réseau 3G...? Non bien entendu. Les applications offline sont principalement destinées aux smartphones et autres tablettes tactiles, et les navigateurs Android et iPhone, supportent déjà cette fonctionnalité. Aucun problème du côté de la compatibilité donc !

Effectivement les navigateurs proposent déjà depuis plusieurs années des consultations de site "hors ligne", et de la mise en cache de certains fichiers. Mais soyons clairs : ces techniques sont peu fiables et très instables. Si vous actualisez une page en ayant perdu la connexion à internet, la plupart du temps vous aurez droit à une belle erreur vous demandant de vérifier votre connexion...

Allez, trêve de bavardage, on se plonge au coeur du sujet !

Le cache d'application (AppCache)

Les applications offline utilisent "l'application cache" du navigateur, qui permet :

  • D'utiliser une application web sans connexion internet,
  • D'améliorer la vitesse de chargement des pages en préchargeant des fichiers,
  • De réduire la charge du serveur, puisqu'en effet, il n'enverra que les fichiers qui auront changé depuis le dernier passage de l'utilisateur.

Notez au passage une petite remarque non négligeable : le cache d'une application est limité à 5Mo.

La page HTML

L'application cache repose principalement sur l'utilisation d'un fichier "manifest" à ajouter aux pages HTML de votre site. Tout d'abord, créons donc une structure basique de page web :

<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      #main {
        width: 900px;
        margin: auto;
      }
    </style>
  </head>
  <body>
    <div id="main">
      <h1>Ceci est mon application offline</h1>
      <img src="earth-folder.png" />
    </div>
  </body>
</html>

Afin d'utiliser le cache d'application, il va falloir déclarer un fichier manifest. Ce fichier se déclare dans la balise html comme ceci :

<html manifest="site.manifest">

Le manifest

Une fois déclaré, il faut bien sûr créer le fichier manifest en question. Appelons le nôtre site.manifest. Dans ce fichier, qui commence obligatoirement par la ligne CACHE MANIFEST, nous allons déclarer les fichiers qui doivent être mis en cache :

CACHE MANIFEST

# Version 0.1

index.html
earth-folder.png

On peut également y ajouter des commentaires. Chaque ligne de commentaire doit commencer par #. Il est important de versionner son fichier manifest via un commentaire, afin que le navigateur détecte par la suite les changements dans le fichier. Nous y reviendrons plus tard.

Notons qu'il est possible d'utiliser des URL absolues.

Il est possible d'ajouter différentes sections à notre fichier manifest :

  • CACHE, il s'agit de la section par défaut qui liste les fichiers à mettre en cache.
  • NETWORK, qui liste les fichiers qui nécessitent obligatoirement une connexion internet.
  • FALLBACK, qui liste les fichiers qui, au cas où ils ne soient pas accessibles en ligne, doivent renvoyer vers d'autres fichiers.

L'exemple suivant représente un site web classique dont la page d'accueil, le CSS et les images sont mises en cache. Si l'utilisateur se connecte à la page d'accueil en étant hors ligne, la page offline.html lui est présentée au lieu d'index.html. Il est donc informé qu'il est déconnecté. Par conséquent il ne pourra pas accéder aux autres pages du site, puisque la section NETWORK spécifie via une étoile *, que tous les autres fichiers requièrent une connexion internet.


CACHE MANIFEST

# v0.1

CACHE:
index.html
css/style.css
img/logo.png

FALLBACK:
/ /offline.html

NETWORK:
*

Le .htaccess

Nous arrivons maintenant au passage (un peu) délicat. Il va falloir déclarer le MIME-type du fichier manifest. Ceci se fait par l'intermédiaire du fichier de configuration de serveur. Dans la grande majorité des cas vous utiliserez un fichier .htaccess pour les projets PHP. Créez donc un fichier .htaccess dans le répertoire de votre application et ajoutez-y simplement la ligne :

AddType text/cache-manifest manifest

Dans cette ligne, on déclare que tous les fichiers se terminant par manifest ont pour MIME-type text/cache-manifest.

Test de l'application hors ligne

Afin de pouvoir tester localement notre application, nous allons devoir passer par Apache (et oui, faites chauffer vos WAMP / MAMP / LAMP !).

Plaçons notre application dans le dossier de votre serveur (www pour WAMP) et rendez-vous sur l'adresse http://localhost/. Si tout se passe bien, votre page s'affiche. Maintenant stoppez les services de WAMP, puis rafraîchissez la page.

Alors qu'une page classique aurait naturellement fait afficher une belle erreur 404... Votre page est toujours là ! Ouvrez maintenant la console de votre navigateur si celui-ci en possède une (pour Chrome, elle se trouve dans Outils, Outils de développement, onglet Console). On peut y voir ceci :

Creating Application Cache with manifest http://localhost/le-chemin-vers-votre-manifest
Application Cache Checking event
Application Cache Downloading event
Application Cache Progress event (0 of 3)
... (1 of 3)
... (2 of 3)
... (3 of 3)
Application Cache Cached event

On y voit en effet tous les événements qui ont lieu lorsque votre navigateur met à jour son AppCache. Les plus coriaces d'entre vous souhaiterons pouvoir intercepter ces événements pour pouvoir effectuer des traitements au moment des événements. Ceux-ci seront détaillés dans la partie "aller plus loin" de ce tutoriel.

Mise à jour du manifest

Attention soyez bien attentif à présent ! La phrase suivante a de quoi perturber : une fois votre application dans l'AppCache grâce au fichier manifest, c'est cette version offline qui a la priorité sur la version en ligne ! Pourquoi ? Tout simplement parce que vous lui avez justement dit de le stocker dans le cache ! C'est exactement comme lorsque votre navigateur garde en mémoire une image ou un CSS sur un site, afin de ne pas avoir à le télécharger à nouveau. Seulement, c'est un peu plus perturbant lorsqu'il est question d'un fichier HTML, je le reconnais... Et donc, comment fait-on pour mettre à jour le cache avec la version en ligne ?

L'Application Cache sera mis à jour si :

  • L'utilisateur vide son cache manuellement,
  • Le fichier manifest change,
  • Le cache est mis à jour avec du code JavaScript.

Voilà pourquoi il était important d'insérer un numéro de version dans un commentaire ! Il suffit de changer ce numéro de version pour obliger l'AppCache du client à se mettre à jour. Si vous effectuez peu de mises à jour (dans le cas d'un site vitrine par exemple) c'est la meilleure solution. Par contre, si vous avez besoin que le client soit en permanence à jour, on préfèrera la version JavaScript. Et encore une fois, c'est dans la section "Aller plus loin" que ça se passe !

Deux remarques avant de passer à la suite :

  • Si le téléchargement de l'un des fichiers échoue, alors l'ensemble du téléchargement échoue. On peut considérer que le manifest liste le contenu d'un "pack" non-scindable.
  • Lorsque l'on rafraichit la page après avoir modifié le fichier manifest, c'est la page initiale (donc non à jour) qui s'affiche en premier pendant le téléchargement du nouveau manifest. La nouvelle version ne s'affichera qu'au prochain rafraichissement de page (ou si on le fait via JavaScript).

WebApps iPhone et Android

Alors attention, c'est le moment grandiose du tutoriel ! Depuis le début on parle d'applications, mais qui sont en fait des sites, mais qui sont quand même surtout destinés aux smartphones qui utilisent des applications... Bref c'est plus très clair tout ça ! Alors voilà qui devrait réconcilier tout le monde : la finalité de tout ça est entre autres de transformer un site web en véritable application pour mobile. C'est à dire que notre site-application pourra :

  • Avoir une icone,
  • Avoir un écran de chargement,
  • S'afficher en plein écran,
  • Etre installée sans passer par l'App Store Apple ni l'Android Market.

Pas mal non ? Il s'agit en réalité d'un raccourci vers l'URL de votre site web qui sera à placer sur le bureau du smartphone de l'utilisateur. Un simple favoris devient donc une application à part entière.

Bien vu. C'est effectivement la grosse faiblesse d'une Web App par rapport à une application native. Il n'est pas intuitif pour un utilisateur lambda de placer un raccourci web parmi ses icônes d'applications habituelles.

Heureusement ça n'est pas fichu, grâce à plusieurs projets très prometteurs qui permettent de transformer une web app en application native, et donc permettre de les trouver via un App Store et de les installer comme n'importe quelle autre application native. Les plus curieux pourront faire quelques recherches sur Phonegap et Appcelerator Titanium, qui feront l'objet de tutoriels dédiés sur ce site.

Revenons-en à nos moutons !

La déclaration des icones, des images de chargement, etc. est contenue dans des balises link et meta, qui sont à placer dans le head du document.

Apple ayant été pionnier dans les Web Apps, il ont choisi de nommer ces paramètres avec des noms propriétaires. Heureusement pour nous, Google a décidé d'utiliser les balises Apple afin de ne pas compliquer encore plus la vie des développeurs. Par conséquent, les extraits de code suivants fonctionnent aussi sur Android.

Utiliser une icone qui aura un effet de lumière sur iPhone :

<link rel="apple-touch-icon" href="apple-touch-icon.png" />

Désactiver l'effet de lumière :

<link rel="apple-touch-icon-precomposed" href="apple-touch-icon.png" />

Sur iPhone, les icones seront mises au format iPhone, c'est à dire carrées aux coins arrondis. Sur Android, il est possible d'utiliser des images transparentes.

Afficher son application en plein écran :

<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-touch-fullscreen" content="yes" />

Choisir le style de la barre de statut sur iPhone :

<meta name="apple-mobile-web-app-status-bar-style" content="black" />

Utiliser une image de chargement :

<link rel="apple-touch-startup-image" href="startup.png" />

Ne possédant pas d'iPhone, je ne peux vraiment tester les options spécifiques aux iPhones mais je vous invite à me dire si tout fonctionne bien dans les commentaires. Sur un Android HTC Desire, l'icone (qui est le plus important) s'affiche correctement. Je vous invite d'ailleurs à nous faire part de vos expérimentations sur iPhone via les commentaires sur cette page.

Aller plus loin : le JavaScript

L'API JavaScript HTML5 possède un objet window.applicationCache. Cet objet permet de :

  • Connaître les états du cache,
  • Attacher des traitements aux événements,
  • Faire une mise à jour du cache,
  • Changer le cache actuel.

Voici la classe ApplicationCache telle qu'elle est donnée par le WHATWG :

// update status
const unsigned short UNCACHED = 0;
const unsigned short IDLE = 1;
const unsigned short CHECKING = 2;
const unsigned short DOWNLOADING = 3;
const unsigned short UPDATEREADY = 4;
const unsigned short OBSOLETE = 5;
readonly attribute unsigned short status;

// updates
void update();
void swapCache();

// events
attribute Function onchecking;
attribute Function onerror;
attribute Function onnoupdate;
attribute Function ondownloading;
attribute Function onprogress;
attribute Function onupdateready;
attribute Function oncached;
attribute Function onobsolete;

Ainsi on peut tester le statut actuel :

if (webappCache.status == window.applicationCache.UPDATEREADY)

Ou avec un switch :

switch (appCache.status) {
  case appCache.UNCACHED: // UNCACHED == 0
    return 'UNCACHED'
    break
  case appCache.IDLE: // IDLE == 1
    return 'IDLE'
    break
  // ...
}

Mais il est préférable de passer par la gestion d'événements pour détecter lorsque l'Application Cache change de statut :

var webappCache = window.applicationCache

webappCache.addEventListener('updateready', updateCache, false)
webappCache.update()

function updateCache() {
  webappCache.swapCache()
  alert('Une nouvelle version est disponible.
Veuillez rafraîchir la page pour mettre à jour.')
}
  • La méthode update() force le lancement du processus de mise à jour du cache,
  • addEventListener("updateready", updateCache, false) lancera la fonction updateCache dès que le statut de l'AppCache passera en updateready,
  • swapCache() permet d'échanger l'ancien cache avec le nouveau cache, ce qui finalise l'opération.

Si vous souhaitez trouver de plus amples informations sur le sujet, je vous invite à vous rendre sur la page Application Cache du WHATWG, où sont listés tous les status que peut prendre applicationcache.status, et tous les événements associés.

Dernière petite remarque, il est possible de tester si l'utilisateur est connecté à internet ou non avec la propriété :

navigator.onLine

Et voilà ! Vous avez tous les outils en main pour réaliser de superbes applications web ! Il y a fort à parier que ces applications deviendront le standard sur les smartphones d'ici quelques années, puisqu'elles fonctionnent aussi bien sur iPhone qu'Android. Couplé avec une balise canvas, on pourra par exemple jouer à des jeux vidéos en 3D dans le navigateur, qui seront compatibles avec tous les mobiles ! Plus besoin de s'embêter à développer plusieurs versions pour chaque OS mobile !

Code complet de démonstration

Cette démonstration illustre les capacité des smartphones (iPhone et Android notamment) à considérer un lien vers une page web comme étant une application web. En ajoutant cette page en favoris et sur le bureau de votre smartphone, une icone spécifique apparaîtra et vous pourrez accéder à la page sans connexion internet si vous l'avez visité une fois auparavant.

<!DOCTYPE html>
<html manifest="site.manifest">
  <head>
    <meta
      name="viewport"
      content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"
    />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    <link rel="apple-touch-icon" href="earth-folder.png" />
    <link rel="apple-touch-icon-precomposed" href="earth-folder.png" />
    <link rel="apple-touch-startup-image" href="earth-folder.png" />

    <style type="text/css">
      #main {
        width: 900px;
        margin: auto;
      }
    </style>
  </head>
  <body>
    <div id="main">
      <h1>Cette page est accessible hors ligne</h1>
      <p>Elle possède une icone d'application pour les smartphones.</p>
      <img src="earth-folder.png" />
    </div>
  </body>
</html>

Le fichier site.manifest :

CACHE MANIFEST

# Version 0.4

exemple-application-cache-manifest-html5.html
earth-folder.png

Le .htaccess :

AddType text/cache-manifest manifest

Merci d'avoir lu jusqu'ici et à très bientôt pour de nouveaux tutoriels !