Le temps de chargement d'une application informatique est un point essentiel en terme d'usabilité. Il a un impact important sur l'expérience utilisateur, tellement important qu'il peut être le facteur décisif d'adhésion ou de rejet de l'application par les utilisateurs qui se font un avis en 2-3 secondes. On a tous des exemples douloureux en tête... ou pas d'ailleurs... et c'est bien ça le drame : ces applications passent aux oubliettes! Les site web sont évidemment très concernés par cette problématique, la concurrence est rude sur la toile… et les plus performants marquent des points face à leurs concurrents, des points qui valent très cher! Les grands du web (Google, Yahoo, etc.) l’ont bien compris et en ont fait leur priorité n°1.
Le problème : le temps de chargement d’une page web dépend de la richesse du contenu et des fonctionnalités qu’elle offre… autant de choses qui sont de plus en plus attendues par les utilisateurs.
Pour résumer, quand on construit une application internet riche (RIA), on est de plus en plus confronté à la question suivante :
Comment concilier performance et richesse fonctionnelle ?
Le temps de chargement d'une RIA est la somme de deux phases :
Si on compare ces deux phases entre une application web classique (j’utilise ce terme pour désigner les applications web dont le contenu HTML est généré côté serveur) et une application RIA (utilisant les nouveaux paradigmes consistant à générer les vues côté client), on obtient (grosso-modo... c'est l'ordre de grandeur qui nous intéresse ici) les graphiques suivants :
J'ai volontairement séparé le chargement de la vue initiale et les vues suivantes pour faire apparaître les spécificités des RIA en terme de chargement. En effet, le chargement initial d'une RIA est souvent douloureux comparé aux application web classique, par contre le chargement des vues suivantes est globalement plus réactifs pour une RIA. Cet article est donc découpé en deux, correspondant à ces deux grandes phases de chargement, avec comme objectif de :
Les "bibles" des bonnes pratiques de développement web de Google et Yahoo sont disponibles en ligne :
Les règles les plus classiques liées à l'optimisation du téléchargement des composants web sont essentiellement :
Une fois que tout ça est respecté, reste à optimiser l'application elle-même...
La taille d'une application GWT est dépendante de la quantité de code "Java client" produit et du nombre de composants utilisés (DatePicker, Panels, etc.). Même si GWT optimise le code Javascript généré (suppression du code mort, génération d'un fichier Javascript par type et version de navigateur et par langue pour éviter un gros fichier Javacript prenant en compte toutes les spécificités de chaque navigateur et langue, etc.), un premier niveau d'optimisation sera de :
A ce titre, GWT 2.0 viendra avec son lot de nouveautés et notamment la possibilité de générer des rapports Story Of Your Compile (SOYC) permettant de mieux cibler les portions de code à optimiser. Ca fera l'objet d'un autre article en préparation...
Pour palier à la contrainte HTTP1.1 des 2 connexions HTTP simultanées sur le même domaine, il est indispensable de diminuer au maximum le nombre de composants qui seront téléchargés :
public interface Icons extends ImageBundle {
public static final Icons INSTANCE = GWT.create(Icons.class);
@Resource("com/octo/sample/public/images/logo.gif")
public AbstractImagePrototype getLogo();
@Resource("com/octo/sample/public/images/info.png")
public AbstractImagePrototype getInfo();
}
Dans l'exemple ci-dessus, les deux icônes logo et info sont automatiquement fusionnés à la compilation GWT dans le même fichier image. Il suffit d'ajouter ces images dans l'interface graphique de la manière suivante par exemple :
panel.add(Icons.INSTANCE.getInfo())
GWT va générer le code permettant de positionner l'image en CSS de sorte à faire apparaître uniquement l'icône info.
En GWT 2.0, le concept de ImageBundle va être étendu aux autres ressources envoyées côté client (CSS, XML, properties, etc.) via le ClientBundle. Les fichiers ressources seront donc fusionnés au moment de la compilation, voire intégrées au fichier Javascript et mis en cache côté client (il y a d'autres intérêts mais qui concernent moins le sujet de cet article).
Une autre grosse nouveauté GWT 2.0, appelée CodeSplitting, permettra de découper l'application en plusieurs modules, c'est à dire plusieurs fichiers Javascript. Cela permettra d'éviter un seul gros fichier Javascript, long à télécharger au démarrage de l'application sur le client.
L'objectif de cette fonctionnalité est donc clairement de diminuer le temps de téléchargement au chargement de l'application sur le client et tendre vers les proportions suivantes :
L'utilisation de cette fonctionnalité est très simple, il suffit d'utiliser l'API GWT.runAsync comme suit :
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable reason) {
}
public void onSuccess() {
SearchView searchView = new SearchView ();
}
});
Dans l'exemple ci-dessus, la déclaration et l'instanciation d'un composant (ici une vue de type SearchView) sont encapsulées dans la méthode onSuccess d'une classe anonyme de type RunAsyncCallback, en paramètre de la méthode static runAsync de la classe GWT. Dans cet exemple, le code Javascript correspondant à la classe SearchView, et tous les composants qu'il utilise de manière exclusive (à l'exception des composants issus de librairies tierces type Wrapper JS) sera stocké dans un fichier Javascript séparé du fichier Javascript général. Ce fichier sera automatiquement téléchargé sur le client à l'exécution, l'idéal étant de déclarer ce code sur traitement d'un évènement non déclenché à l'initialisation de la RIA (lors du traitement de l'évènement clic sur le menu "Search" pour reprendre l'exemple).
Pour être plus précis, GWT détecte à la compilation que le composant SearchView est candidat à un SplitPoint puisqu'il n'est utilisé qu'à travers un seul runAsync. Par contre, les composants utilisés dans plusieurs SplitPoints sont stockés dans un autre fichier Javascript partagé mais différent du fichier Javascript du chargement initial, en gros ça donne le découpage suivant :
Vous l'aurez compris, ce mécanisme n'est pas forcément simple à utiliser et j'imagine que vous vous posez déjà quelques questions comme :
On a donc vu dans cet article comment optimiser les temps de téléchargement d'une application GWT et des composants web sur le client, l'article suivant concernera l'optimisation du temps d'initialisation d'une application GWT sur le browser... stay tuned! ;)