I. Qu'est-ce qu'un bundle de données ?

Une application web est accompagnée d'un nombre plus ou moins important de ressources statiques : notamment les images et feuilles de style CSS. Chaque référence à l'une de ces ressources va provoquer son chargement via une requête HTTP dédiée. On peut facilement arriver à plus de cinquante images (icônes ou éléments de style graphique) pour une seule page. Dans les styles modernes il n'est pas rare de manipuler des 9-box : un conteneur graphique utilisant neuf images (les quatre coins, les quatre bords et le centre). Chaque type de 9-box nécessitera neuf requêtes HTTP.

L'optimisation d'un site web réside en partie dans sa vitesse d'affichage et la minimisation des requêtes HTTP est un axe de travail. Si on avait une seule grosse image concaténant toutes les petites images utilisées par notre thème graphique, il ne faudrait déjà plus qu'une seule requête HTTP pour récupérer tous les éléments de style. Et on peut faire le même raisonnement pour minimiser les inclusions de feuilles de style en les concaténant. C'est cette facilité de regroupement qui est amenée par les bundles de données GWT.

Voici une comparaison du nombre de requêtes HTTP du temps de chargement et pour une page utilisant un bundle de données et la même page ne l'utilisant pas. Bien sûr le temps de chargement dépend de la bande passante, de la disponibilité du serveur, de la vitesse du poste client... mais les graphiques suivants montrent l'ordre de grandeur.

Nombre de requêtes de chargement
Temps de chargement

Les temps de chargement sont légèrement diminués dans le cas d'un bundle et le nombre de requêtes HTTP d'images dans cet exemple est passé de 19 à 0 ! En effet sur mon navigateur de test, GWT a compilé les images en base64 et elles sont directement incluses dans le code JavaScript généré. Notons au passage qu'il aurait quand même fallu une requête pour charger le bundle sur les navigateurs ne supportant pas la définition des images en base64.

II. Images

Le regroupement d'images est le plus intuitif. GWT va agencer dans une nouvelle image PNG toutes les images qui lui sont données à gérer. Il y a cependant une spécificité par rapport aux fichiers JPG puisque ceux-ci seront mis dans des fichiers séparés pour minimiser la taille de l'image générée. Dans votre projet GWT il vous suffit de déclarer toutes les images à utiliser dans une interface étendant ClientBundle. Créez un nouveau package contenant cette interface et toutes vos images.

Package

Inscrivez chaque image à gérer dans l'interface

 
Sélectionnez
ImageResource icon();

Ajoutez le constructeur GWT du bundle :

 
Sélectionnez
public static ImageBundle R = GWT.create(ImageBundle.class);
 
public interface Resources extends ClientBundle{
    public static Resources R = GWT.create(Resources.class);
 
    ImageResource add();
    ImageResource edit();
    ImageResource email();
    ImageResource facebook();
    ImageResource login();    
    ImageResource logout();
    ImageResource refresh();
    ImageResource remove();
    ImageResource rss();
    ImageResource twitter();
    ImageResource user_group();
    ...
}

La création d'une signature de fonction "icon" correspondra à une image icon.bmp, icon.png, icon.jpg ou icon.gif située dans le même package. Le plugin GWT de votre environnement vous avertira si vous référencez une image inexistante dans le package. Si vous souhaitez que la fonction "icon" soit en réalité associée à une autre image, il va falloir le préciser avec une annotation @Source("monImage.png"). Voilà le résultat de la compilation des images :

Bundle résultat

Les images contenues dans le bundle s'utiliseront en indiquant la fonction de l'interface qui leur est associée. Voilà par exemple comment instancier des objets GWT image liés au bundle. Et bien sûr au final, notre page se charge très rapidement côté client, nous avons au plus une seule requête HTTP pour charger toutes les images.

 
Sélectionnez
RootPanel.get().add(new Image(Resources.R.add()));

III. Feuilles de style

GWT amène une gestion des feuilles de style très puissante : il amène des notions de variables CSS, de factorisation de règles CSS, d'inclusions conditionnelles et bien sûr il permet d'utiliser les ressources images définies en bundle.

De la même manière que pour les bundles d'images, il vous suffit d'inclure votre fichier CSS dans un package de votre projet et de déclarer une méthode du même nom dans l'interface du bundle. Une méthode "style" sera associée au fichier "style.css". Exemple

 
Sélectionnez
public interface Resources extends ClientBundle{
    public static Resources R = GWT.create(Resources.class);
 
    CssResource style();
}

La déclaration d'une méthode style() est associée au fichier style.css situé dans le package courant. L'inclusion de cette feuille de style se fait par

 
Sélectionnez
Resources.R.style().ensureInjected();

Les feuilles de style sont elles aussi optimisées, les classes sont regroupées, les commentaires sont supprimés. Les éléments de style peuvent même être directement inscrits dans un attribut style des éléments du DOM s'ils sont peu utilisés. L'utilisation classique peut s'illustrer avec cet exemple :

 
Sélectionnez
@external .test;
.test{
	font-size: 9pt;
	color: #985;
}
 
Sélectionnez
monWidget.addStyleName("test");

Pour utiliser directement les classes CSS dans votre code HTML, il faut déclarer ces classes CSS avec l'annotation @external. De cette manière les sélecteurs ne seront pas optimisés et ils resteront cohérents avec votre code HTML.

III-1. Utilisation des bundles d'images dans un bundle CSS

GWT permet de réutiliser dans une feuille de style une image incluse dans un bundle d'images. C'est là tout l'intérêt des sprites GWT. Rien ne vous empêche de créer un même bundle pour stocker à la fois des images et des feuilles de style. La déclaration d'une règle CSS utilisant un élément d'un bundle d'images se fait de la manière suivante :

 
Sélectionnez
@external .info;
@sprite
.info{
    gwt-image : "info";
}}
 
Sélectionnez

public interface Resources extends ClientBundle{
    public static Resources R = GWT.create(Resources.class);
 
    CssResource style();
    ImageResource info();
}}}

IV. Utilisation des bundles de données texte

Bien qu'ils soient moins utiles, les bundles de données peuvent vous permettre d'optimiser encore un peu les transferts de données statiques de votre application. Si vous avez besoin de manipuler un fichier texte statique (fichier txt, xml, json...), les bundles de données texte sont pour vous.

La définition d'un bundle de données est similaire à celle des bundles CSS et images : vous ajouter une méthode dans l'interface de votre bundle et GWT associera un fichier du package courant avec le nom de la méthode. Une méthode "methode" cherchera un fichier "methode.txt", à moins que vous n'ajoutiez l'annotation @source, comme pour les bundles CSS et images.

Il y a cependant une nuance pour les bundles de texte : ceux-ci peuvent être accédés de manière synchrone ou asynchrone. La méthode synchrone chargera le contenu du fichier texte dès le chargement de la page alors que la ressource asynchrone ne sera accédée et transférée que lors de son utilisation.

 
Sélectionnez
public interface Resources extends ClientBundle {
    public static Resources R = GWT.create(Resources.class);
 
    ImageResource dvp();    
    CssResource style();
 
    TextResource countries();	// une ressource synchrone -> countries.txt    
    ExternalTextResource countriesAsync(); // une ressource asynchrone -> countriesAsync.txt
}
 
Sélectionnez
// synchrone 
String countries = Resources.R.countries().getText();
 
// asynchrone
Resources.R.countriesAsync().getText(new ResourceCallback<TextResource>(){
	@Override
	public void onError(ResourceException e) {
		// gestion de l'erreur de récupération				
	}
 
	@Override
	public void onSuccess(TextResource resource) {
		String countries = resource.getText();
		// traitement des données, parsing ...				
	}				
});

La récupération du contenu d'un bundle texte renvoie un objet String, à vous ensuite de le parser s'il s'agit d'un fichier XML, JSON ou autre.

V. Risques de dérive

La notion de bundle peut paraître très tentante, mais il faut bien faire attention à ne pas en abuser, vous pourriez perdre en performance. Le bundle idéal est celui dont toutes les ressources sont utilisées sur une page web. Il est donc déconseillé de stocker dans un même bundle des éléments graphiques utilisés sur des pages différentes ou visibles par des utilisateurs différents.

Une solution pertinente sera de créer un bundle centralisant toutes les ressources communes à tous les modules de votre application web et ensuite autant de bundles que de modules, pour y stocker les éléments spécifiques à chaque module. De cette manière vous maximisez vos chances d'utiliser tous les éléments d'un bundle quand il aura été téléchargé par le navigateur.

Merci à toute l'équipe de rédaction de Developpez.com et en particulier à benwit pour ses remarques techniques et ClaudeLELOUP pour sa relecture orthographique.