Une application HTML5 desktop en mode offline

Ludovic Chane Won In

Durant une de mes récentes missions, j'ai eu la chance de développer une application HTML5 en mode hors ligne (offline). Cette application a pour cœur de métier le calcul de structures dans le bâtiment. En gros, l'ingénieur bâtiment va sur un chantier avec son portable ou sa tablette, ouvre l'application et rentre des données de mesures physiques pour voir si à tout hasard le bâtiment ne risque pas de s'écrouler. Toute la logique métier est donc côté client et une connexion au serveur Web permet juste de télécharger l'application une première fois, puis de faire des mises à jour.

La grande question est : pourquoi avoir choisi des technologies Web pour faire une application de type bureau hors ligne ? Tout d'abord c'était un pari sur l'avenir et ensuite plusieurs dispositifs clients étaient ciblés, notamment des portables et des tablettes, et ça HTML5 sait bien faire. L'argument récurrent contre HTML5 est le manque de compatibilité navigateur. Pour une application interne à l'entreprise, on aurait pu cibler un seul navigateur, mais ça n'a pas été notre cas. Le client voulait de la compatibilité Firefox, Chrome et même IE8 ! Heureusement, ils ont bien voulu de Google Chrome Frame.

Dans cet article, je vous présenterai donc les différentes technologies et API que j'ai utilisées pour développer cette application, mais surtout je vous dévoilerai les 20 % d'écueils qui ont pris 80 % de mon temps !

Cet article a été publié avec l'aimable autorisation de Ippon technologies. L'article original (Une application HTML5 « desktop » en mode offline) peut être vu sur le blog d'Ippon.

Commentez ♪

- Titre : Une application HTML5 desktop en mode offline

- Auteur : Ludovic Chane Won In

- Parution : 12 juin 2013

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Ludovic Chane Won In. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Mode Offline

- I-A. Le fichier cache manifest

- I-B. Écueils

- I-B-1. Pas de Wildcards dans la section CACHE

- I-B-2. Type MIME du fichier cache manifest

- I-B-3. Mise à jour du cache

- I-B-4. C'est trop bien le cache HTML5, mais en mode développement ce n’est pas pratique

- I-B-5. Ne pas mettre en cache le fichier cache manifest lui-même

- I-B-6. Double Rechargement

- II. Api Localstorage

- II-A. Écueils

- II-A-1. String-String

- III. API File, Filesystem

- III-A. Écueils

- III-A-1. Support des navigateurs

- IV. Modélisation 2D

- V. Framework JavaScript, CSS

- VI. Conclusion et remerciements

- VI-A. Remerciements

I - Mode Offline

Mon ingénieur en bâtiment n'aura pas forcément de connexion Internet sur les chantiers. Il lui faut donc une application capable de fonctionner en mode hors ligne. HTML5 a introduit l'interface ApplicationCache qui va permettre de mettre en cache l'application Web et ainsi de se passer d'une connexion Internet les fois suivantes.

I-A - Le fichier cache manifest

Le fichier cache manifest est un simple fichier texte qui va référencer tous les fichiers de votre application qui doivent (ou ne doivent pas) être mis en cache. Voici un exemple de fichier manifest qui demande à mettre en cache les fichiers index.html et css/style.css :

CACHE MANIFEST
index.html
css/style.css

Ce fichier (manifest.appcache par exemple, mais vous pouvez lui donner n'importe quel nom ou extension) doit être référencé par votre page dans la balise <html> :

<html manifest="manifest.appcache">
...
</html>

Voilà, rien de plus simple. Mais on peut faire un peu plus compliqué. Le fichier cache manifest peut se décomposer en trois sections :

- la section CACHE : c'est la section par défaut, celle qui va référencer les fichiers à mettre en cache ;

- la section NETWORK : c'est l'opposé de la section CACHE, ici on va référencer les fichiers qui nécessiteront toujours un accès réseau ;

- la section FALLBACK : dans cette section on peut proposer des pages de « secours » si des ressources ne sont pas accessibles.

En exemple :

CACHE MANIFEST
# on peut omettre cette ligne, car CACHE: est la section par défaut
CACHE:
index.html
css/style.css
# Ressources qui nécessitent que l'utilisateur soit en ligne
NETWORK:
img/logo.png
# offline.html sera utilisé si online.html n'est pas accessible
# offline.html sera utilisé à la place des autres fichiers html s'ils ne sont pas accessibles
FALLBACK:
online.html offline.html
*.html offline.html

I-B - Écueils

I-B-1 - Pas de Wildcards dans la section CACHE

On peut utiliser des wildcards (caractères génériques) dans les sections NETWORK et FALLBACK,mais pas dans la section CACHE ! Si vous avez beaucoup de fichiers dans votre application, ça peut être ennuyeux. Je vous conseille d'utiliser un petit script pour générer une première fois votre fichier, sous Unix par exemple :

echo "CACHE MANIFEST" > manifest.appcache; find . -type f | sed "s#^\./##" >> manifest.appcache

I-B-2 - Type MIME du fichier cache manifest

Une erreur fréquente est d'oublier de vérifier que votre serveur Web sert le fichier cache manifest avec le bon type MIME. Un fichier cache manifest doit avoir le type MIME text/cache-manifest. Par exemple dans Apache, vous pouvez rajouter cette ligne dans le fichier de configuration :

AddType text/cache-manifest .appcache

I-B-3 - Mise à jour du cache

Tiens bizarre, vous avez modifié votre fichier index.html,mais lorsque vous rechargez la page, vous ne voyez pas vos modifications ! C'est normal si vous avez mis en cache votre fichier index.html ! C'est bien là le principe du cache ! Du coup, si vous voulez que votre navigateur mette à jour son cache avec votre nouveau fichier, vous devez modifier le fichier cache manifest.
Je vous conseille d'utiliser une ligne de commentaire avec un numéro de version que vous incrémenterez lorsque vous déploierez une nouvelle version de votre application.

CACHE MANIFEST
# Version 2
index.html
...

Le cache peut également être mis à jour programmatiquement via l'API ou lorsque vous effacez le cache du navigateur.

I-B-4 - C'est trop bien le cache HTML5, mais en mode développement ce n’est pas pratique

Effectivement, ce n'est pas pratique, car pour voir vos modifications, vous devez en permanence modifier votre fichier cache manifest ! Là plusieurs solutions s'offrent à vous :

- configurez votre serveur Web pour que vos fichiers expirent tout de suite ;

- ne pas utiliser de cache en développement.

I-B-5 - Ne pas mettre en cache le fichier cache manifest lui-même

Car si vous le faites, votre fichier cache manifest sera en cache et votre navigateur ne verra pas les modifications apportées à ce fichier. Il ne mettra donc pas à jour son cache ! Il faudra attendre le temps d'expiration du fichier.

I-B-6 - Double Rechargement

Lors du téléchargement d'une nouvelle version de votre application, il faut charger deux fois la page : une fois pour mettre à jour son cache, une seconde fois pour vraiment charger la nouvelle version en cache. Pour éviter ce double chargement à votre utilisateur, vous pouvez utiliser un peu de code JavaScript qui va faire ce travail pour vous :

// On vérifie si un nouveau cache est disponible au chargement de la page
window.addEventListener('load', function(e) {

window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Là, le navigateur a téléchargé un nouveau cache
// On change le cache et on recharge la page
window.applicationCache.swapCache();
window.location.reload();
} else {
// Le cache Manifest n'a pas changé, on ne fait rien
}
}, false);

}, false);

II - Api Localstorage

Robert, mon ingénieur en bâtiment, avait l'habitude avec son logiciel habituel, de sauvegarder son travail, de revenir sur son chantier le lendemain et de reprendre là où il s'était arrêté. Si on était sur une application Web classique, on sauvegarderait notre travail dans une base de données. Mais en mode hors ligne, il faut composer avec ce que le navigateur nous propose. Il y a les cookies, mais bon la taille maximale d'un cookie est de 4 ko, ça ne fait pas beaucoup. Ce qu'il nous faut, c'est donc un espace de stockage :

- de grande capacité ;

- sur le poste client ;

- qui persiste au-delà d'un rechargement de page ;

- qui n'est pas transmis au serveur.

Dans un premier temps, on va utiliser le Web Storage et plus particulièrement le LocalStorage qui se comporte simplement comme une mappe clé-valeur. Celui-ci répond à tous nos besoins. Par exemple, sa limite de stockage est de 5 Mo par nom de domaine. Cette limite est évoquée dans la spécification, mais elle peut différer selon les implémentations.

II-A - Écueils

II-A-1 - String-String

Le principal problème est que le LocalStorage ne propose qu'un mapping clé-valeur où clé et valeur ne peuvent être que des chaînes de caractères ! Si vous sauvegardez un entier ou un objet, celui-ci sera transformé en chaîne de caractères. Lorsque vous voudrez le charger, il faudra coder une petite étape intermédiaire pour retrouver votre objet initial (parseInt() ou JSON.parse() par exemple). Mais si vous ne voulez pas vous embêter, vous pouvez utiliser le petit framework Locache qui possède une API simple de cache et qui permet notamment de sauvegarder vos objets dans le LocalStorage et de les récupérer en tant que tel.

III - API File, Filesystem

Robert, qui dépose tous les matins ses enfants à l'école, est content de pouvoir sauvegarder son travail dans son navigateur. Mais avant, il le faisait dans des fichiers qu'il pouvait voir en vrai sur son disque dur. En plus, ça lui arrivait d'envoyer son fichier à son collègue Miguel pour qu'il y jette un œil. OK… pas de problème…
Dans une application Web classique, c'est le serveur qui envoie un binaire à votre navigateur et ce dernier vous propose de le sauvegarder quelque part sur le système de fichiers. Mais là, je vous rappelle, on travaille en mode hors ligne.

HTML5 introduit plusieurs API permettant de gérer des fichiers :

- l'API File permet de lire des fichiers provenant de votre système de fichiers au niveau OS ;

- les API FileWriter et FileSystem permettent de gérer un système de fichier à l'intérieur de votre navigateur. Ce système de fichier est cantonné à un nom de domaine et évidemment vous ne pouvez pas y accéder en dehors de votre navigateur courant. Mais le W3C a tout prévu et l'interface FileWriter possède une méthode saveAs(data, filename) permettant de sauvegarder le fichier sur votre système de fichiers au niveau OS. Malheureusement cette méthode n'est supportée que dans Chrome. Heureusement, il existe un polyfill pour les autres navigateurs : FileSaver.js.

III-A - Écueils

III-A-1 - Support des navigateurs

Ici, le principal écueil est le support disparate des navigateurs des API de fichiers. D'une part, les API fichiers de HTML5 sont compliquées, car scindées en différentes API et de l'autre côté, certains navigateurs supportent une API, mais pas l'autre. C'est facile de s'y perdre. Je vous conseille d'utiliser le site caniuse pour se tenir au courant des différentes compatibilités navigateurs.

IV - Modélisation 2D

Autre truc sympa que l'application doit faire, c'est modéliser les différentes structures (poteaux, dalles…) des bâtiments. Oui, Robert comprend mieux avec des schémas et des dessins. Pas vous ?

Bon, ici on était confronté à deux choix : canvas ou SVG. On a essayé les deux :

- canvas avec Kinetic.js ;

- SVG avec Raphael.js.

À vrai dire, on était plutôt satisfait des deux. En ce qui nous concerne, ce qui a fait la différence, c'est que SVG c'est du vectoriel. D'une part, l'application précédente utilisait du vectoriel et d'autre part, ces modélisations devaient être imprimées pour être incorporées dans un rapport.

V - Framework JavaScript, CSS

Bien sûr, au-delà de HTML5, nous avons utilisé pléthore d'outils et framework JavaScript et CSS. Je ne vais pas les détailler ici, surtout que mes collègues s'y sont déjà attelés :

- pour la partie MVC : Backbone.js : Présentation de Backbone.js ;

- pour la gestion des ressources : Require.js : AMD loader pour un code JavaScript organisé et performant ;

- l'inévitable Twitter Bootstrap. Le thème classique était suffisant et le responsive CSS de Bootstrap était juste nécessaire.

VI - Conclusion et remerciements

J'ai oublié de préciser que ce développement n'était pas un développement de zéro, mais une migration d'application bureau écrite en C++ vers une application Web hors ligne écrite en HTML et JavaScript ! On commence à voir des entreprises de moins en moins frileuses par rapport à ces nouvelles technologies Web et tant mieux ! Le Web existe maintenant depuis plus de 20 ans et est utilisé partout, par tout le monde et tout le temps.

Je pense que HTML5 a de beaux jours devant lui, notamment dans le développement d'applications d'entreprise dites de bureau. Les API sont là, les implémentations ne suivent pas toujours, mais la communauté HTML / JavaScript / CSS est tellement bouillonnante que de nombreuses librairies émergent pour combler les trous. La convergence applications Web, applications bureau et applications mobiles sera sans doute HTML5 (ou HTML6). Seul l'avenir nous le dira.

VI-A - Remerciements

Cet article a été publié avec l'aimable autorisation de Ippon technologies. L'article original (Une application HTML5 « desktop » en mode offline) peut être vu sur le blog d'Ippon.

Nous tenons à remercier Didier Mouronval pour sa relecture attentive de cet article.