I. Introduction

Dojo est un framework javascript Open Source très complet. J'ai rédigé un précédent articleIntroduction à Dojo décrivant ce framework.

Dans sa dernière release (1.3), Dojo contient de très très nombreux fichiers :

Type de fichier  Quantité 
Javascript  1513 
Style css  200 
Gif / Jpg  210 
HTML  730 
Autre  581 
Total  3234 

Cette multiplicité de fichiers est due au fait que chaque classe et chaque widget dans Dojo est décrit dans un fichier javascript. En plus du fichier javascript, un widget peut être accompagné d'un template (fichier HTML), d'une feuille de style (fichier css), de fichiers javascript pour l'internationalisation et éventuellement d'images.

II. Focus sur les performances des applications WEB 2.0

Ce paragraphe n'a pas l'ambition de faire le tour de la problématique de performance pour les applications Web 2.0 (plusieurs articles n'y suffirait pas) mais va mettre en lumière un problématique importante des applications Web 2.0 : le nombre très important de fichiers à charger sur le navigateur de l'utilisateur.

Tout d'abord, il faut bien comprendre de quelle manière est architecturée une application Web 2.0 réalisée en JavaScript : Le fichier HTML qui contient normalement le contenu de la page n'est utilisé que pour charger un noyau d'application en JavaScript. Ce noyau va ensuite charger différents fichiers JavaScript qui vont générer l'interface utilisateur.

Au niveau de Dojo, le chargement des différents widgets est assuré par une instruction "dojo.require('nom de la classe');" A chaque appel, cette instruction va effectuer une requête Http au serveur pour obtenir les fichiers JavaScript qui ne sont pas dans le cache du navigateur. Bien entendu, pour chaque fichier demandé (css, image,...) une nouvelle requête sera effectuée.

L'image suivant illustre la situation avec un simple chargement de Dojo. Firebug est utilisé pour tracer les éléments chargés.

Image non disponible
Trace réseau du chargement de Dojo

La masse de fichiers à charger induit des temps de chargement prohibitifs d'une simple application Dojo : de l'ordre de 1 seconde sur un poste en local mais plusieurs secondes sur un vrai serveur distant !

Cette problématique a bien évidemment été prise en compte par les équipes de développement de Dojo et leur solution a été la création du système de build de Dojo.

III. Description du build de Dojo

Le build de dojo a plusieurs objectifs :

  • Compresser les fichiers JavaScript
  • Grouper plusieurs fichiers JavaScript en un seul
  • Internaliser les fichiers non JavaScript ( template HTML, fichiers d'internationalisation)
  • Regrouper les fichiers CSS contenant la clause @import
  • Créer une release de votre application

Les deux images suivantes illustrent les différences entre une application Web 2.0 non buildée et une application Web 2.0 buildée :

Avant le build Après le build
Image non disponible Image non disponible

Comme vous pouvez le constater, la différence est flagrante ! On passe du chargement d'une centaine de fichiers au chargement de trois fichiers. Bien entendu, certaines ressources comme les images ne pourront pas être buildées.

IV. Description du fonctionnement du système de build

Le système de build inclus une version customisée de Rhino, l'interpréteur JavaScript de Mozilla. Cette version est modifiée pour permettre la compression de code JavaScript. Etant donné que cet interpréteur est codé en Java, il est nécessaire d'avoir un JRE sur son poste (> 1.4.2).

En plus de Rhino, le système de build comprend un programme JavaScript nommé "build". Ce programme a pour but la concaténation des JavaScripts en un seul fichier.

Ces deux outils se situent dans le répertoire /util de dojo : util/shrinksafe/shrinksafe.jar et util/buildscript/build.js

En sortie de build, plusieurs actions auront été réalisées :

  • Création d'un répertoire pour la version
  • Copie sélective de fichiers dans le répertoire de la version
  • Concaténation des fichiers en un seul fichier
  • Compression du fichier JavaScript

Pour fonctionner, le build se base sur un fichier de profile au format Json contenant :

  • Le nom du fichier JavaScript en sortie (name),
  • Les widgets à prendre en compte (dependencies)
  • Les répertoires à prendre en compte (prefixes)
Exemple de fichier profile
Sélectionnez

dependencies = {
	layers: [
		{
			name: "../dojox/storage/storage-browser.js",
			layerDependencies: [
			],
			dependencies: [
				"dojox.storage",
				"dojox.storage.GearsStorageProvider",
				"dojox.storage.WhatWGStorageProvider",
				"dojox.storage.FlashStorageProvider",
				"dojox.flash"
			]
		}
	],
 
	prefixes: [
		[ "dijit", "../dijit" ],
		[ "dojox", "../dojox" ]
	]
}

Pour lancer le build, la ligne de commande suivante doit être utilisée:

Ligne de commande pour le build (avec des retours chariots pour la lisibilité)
Sélectionnez

java -classpath ../shrinksafe/js.jar;../shrinksafe/shrinksafe.jar org.mozilla.javascript.tools.shell.Main build.js 
	action=clean,release, 
	dojodir=C:\Prog\Dojo\dojo-release-1.3.0-src, 
	profileFile=C:\Prog\Dojo\dojo-release-1.3.0-src\util\buildscripts\profiles\storage.profile.js, 
	releaseDir=C:\Prog\Dojo\dojo-release-1.3.0-src/release, 
	version=0.0.0.dev, 
	optimize=none

Cette mise en œuvre manuelle étant fastidieuse, je vais maintenant vous proposer une mise en œuvre avec Ant.

V. Mise en œuvre avec Ant

Ant est un outil de construction d'application écrit en Java. Je ne vais pas le décrire plus avant car il y a un excellent tutorielPrise en main d'Ant qui vous expliquera ça dans le détail.

Afin d'illustrer le build, on va réaliser un projet Dojo à l'aide d'Eclipse et on réalisera le script Ant permettant d'automatiser le build.

V-A. Création du projet Eclipse

Si vous avez suivi mon précédent articleIntroduction à Dojo, la création d'un projet Dojo ne dois plus avoir de secret pour vous.

Ici par contre, on va devoir modifier légèrement la procédure pour ne pas prendre la librairie proposée par défaut par Apatana. En effet, celle-ci est une librairie partielle ne comprenant pas tous les outils de build de Dojo. Il faut utiliser la version source de Dojo.

On peut la trouver à l'adresse suivante : Téléchargement de Dojo source 1.3.1Téléchargement de Dojo source version 1.3.1

Pour créer le projet d'exemple, créez un simple projet Web à l'aide d'Aptana (Default Web Project) sans préciser de bibliothèque Ajax. Ensuite décompressez la version source de Dojo dans un répertoire lib de votre projet. Enfin créez un répertoire build.

Image non disponible
Répertoire projet que vous devez obtenir

On va coder un petit fichier HTML permettant l'affichage de 2 widgets Dijit.

fichier index.html
Sélectionnez

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Projet de test de build</title>
	<!-- Import du source de Dojo -->
	<script  type="text/javascript"
		src="lib/dojo/dojo.js" djConfig="parseOnLoad:  true">		
	</script>
	<!-- Import des css-->
	<style type="text/css">
        @import "lib/dijit/themes/tundra/tundra.css";
        @import "lib/dojo/resources/dojo.css";
    </style>
	<!-- Import des bibliothèques nécéssaires à l'affichage des widgets -->
	<script  type="text/javascript">
		dojo.require("dijit.form.Button");
		dojo.require("dijit.form.CheckBox");
	</script>
</head>
<body  class="tundra">
	<div  dojoType="dijit.form.Button">Click Button</div>
	<div>Une checkBox</div>
	<div  dojoType="dijit.form.CheckBox"></div>
</body>
</html>
  

Un rapide coup d'œil sur Firebug après l'exécution de la page :

Image non disponible
Avant le build

Pas terrible : 79 requêtes, 1,3 secondes et 588 Kb sur mon poste.

V-B. Création du fichier de profile

Notre application étant très simple, le fichier de profil le sera également :

Le fichier profile.json dans le répertoire build
Sélectionnez

/**
 * @author Mikael Morvan
 * 20 juin 2009
 */
dependencies = {
	layers: [
		{
			name: "dojo.js",
			layerDependencies: [
			],
			dependencies: [
				"dijit.form.Button",
				"dijit.form.CheckBox"
 
			]
		}
	],
 
	prefixes: [
		[ "dijit", "../dijit" ],
		[ "dojox", "../dojox"]
	]
}						

Comme vous pouvez le constater, les deux widgets que j'utilise sont ajoutés à la section dependencies. Le nom de mon build "dojo.js" sera mixé avec le "dojo.js" de la bibliothèque de base et je n'aurai donc qu'un seul fichier JavaScript à charger ! La section "prefixes" est utilisée dans le cas où vous avez une bibliothèque personnelle.

V-C. Création du fichier Ant

Avec Eclipse, il faut commencer par ouvrir la vue Ant (Menu Window/Show View/Ant). Créez un nouveau fichier xml nommé "build.xml" et faites un "drag and drop" de la vue fichier vers la vue Ant. (Normalement votre vue Ant indiquera une erreur puisque votre fichier "build.xml" est mal construit).

J'ai séparé mon "projet Ant" en trois parties : la première partie permet de lancer le système de build de Dojo, la deuxième effectue une copie sélective des fichiers "buildés" vers un répertoire final et enfin la dernière partie effectue un peu de nettoyage.

Le fichier build.xml
Sélectionnez

<?xml version="1.0" encoding="UTF-8"?>
<!-- ====================================================================== 
     20 juin 2009                                                        
 
     testBuild    
     Build du projet testBuild
 
     Mikaël Morvan                                                                
     ====================================================================== -->
<project name="testBuild" default="nettoyage" basedir=".." >
    <description>
    	Build du projet d'exemple
    </description>
 
	<property name="build.name" value="buildDojo"/>	
	<property name="buildscripts.dir" value="${basedir}/lib/util/buildscripts/"/>	
	<property name="release.dir" value="${basedir}/build/release/${build.name}/"/>
	<property name="final.dir" value="${basedir}/build/lib/"/>
 
 
    <!-- ================================= 
          target: packaging              
         ================================= -->
 
    <target name="packaging" depends="" description="Build du projet">        
        <java
         classname="org.mozilla.javascript.tools.shell.Main"
         dir="${buildscripts.dir}"
         fork="true"
         failonerror="true"
         maxmemory="128m">
            <arg line="build.js"/>
            <arg line="action=clean,release"/>
            <arg line="dojodir=${basedir}/lib/"/>
            <arg line="profileFile=${basedir}/build/profile.json"/>
            <arg line="releaseName=${build.name}"/>
            <arg line="releaseDir=${basedir}/build/release/"/>
            <arg line="version=1.0.0.dev"/>
            <arg line="cssOptimize=comments"/>
            <arg line="layerOptimize=shrinksafe"/>
            <arg line="copyTests=false"/>
            <classpath>
                <pathelement location="${buildscripts.dir}\..\shrinksafe\shrinksafe.jar"/>
                <pathelement location="${buildscripts.dir}\..\shrinksafe\js.jar"/>
                <pathelement path="${java.class.path}"/>
            </classpath>
 
        </java>
    </target>
 
	<!-- - - - - - - - - - - - - - - - - - 
          target: optimisation                      
         - - - - - - - - - - - - - - - - - -->
    <target name="optimisation"  depends="packaging" description="optimisation de la release">
 
   		<delete dir="${final.dir}"/>
    	<mkdir dir="${final.dir}"/>
 
    	<!-- Répertoire dojo -->
    	<mkdir dir="${final.dir}dojo"/>
    	<copy file="${release.dir}dojo/dojo.js" todir="${final.dir}dojo/"/>
		<!-- Uniquement si on veux pouvoir utiliser firebug light-->
    	<copy todir="${final.dir}dojo/_firebug/">
    		<fileset dir="${release.dir}dojo/_firebug/"/>
    	</copy>
		<!-- Les images et les css doivent être gargées-->
		<copy todir="${final.dir}dojo/resources/">
    		<fileset dir="${release.dir}dojo/resources/">
    			<include name="**/*.css"/>
				<include name="**/*.png"/>	
				<include name="**/*.gif"/>	
				<!-- Les fichiers css non optimisés ont été gardés dans l'arborescence -->
				<exclude name="**/*.commented.css"/>
			</fileset>
    	</copy>
 
 
    	<!-- Répertoire dijit -->
    	<mkdir dir="${final.dir}dijit"/>
    	<copy todir="${final.dir}dijit/themes/">
    		<!-- On ne copie que les themes et rien d'autre (tout a été internalisé dans dojo.js)-->
    		<fileset dir="${release.dir}dijit/themes/">
    			<exclude name="**/*.commented.css"/>
    			<exclude name="**/*.psd"/>
    		</fileset>
    	</copy>
 
    	<!-- Répertoire dojox -->
		<!-- A ne garder que si c'est nécessaire-->
		<!--
    	<mkdir dir="${final.dir}dojox"/>
    	-->
    	<!-- Copie des css et des images uniquement -->
		<!--
    	<copy todir="${final.dir}dojox/">
    		<fileset dir="${release.dir}dojox/">
    			<include name="**/*.css"/>
    			<include name="**/*.png"/>
    			<include name="**/*.gif"/>
    			<include name="**/*.jpg"/>
    			<exclude name="**/*.commented.css"/>
    		</fileset>
    	</copy>
		-->
    	<!-- Nettoyage des fichiers utilisés pour les tests -->
		<!--
    	<delete dir="${final.dir}dojox/data/"/>
    	<delete dir="${final.dir}dojox/fx/"/>
    	<delete dir="${final.dir}dojox/gfx/"/>
    	<delete dir="${final.dir}dojox/grid/tests/"/>
    	<delete dir="${final.dir}dojox/image/tests/"/>
    	<delete dir="${final.dir}dojox/layout/tests/"/>
    	<delete dir="${final.dir}dojox/widget/tests/"/>
    	<delete dir="${final.dir}dojox/wire/"/>
    	-->
    </target>
 
	<!-- - - - - - - - - - - - - - - - - - 
          target: nettoyage                      
    - - - - - - - - - - - - - - - - - -->
	<target name="nettoyage" depends="optimisation" description="nettoyage de la distribution">
		<delete dir="${release.dir}"/>	        
	</target>
 
 
</project>
 

Un petit focus sur la partie "Optimisation" : il faut tout d'abord comprendre que le build de Dojo copie l'ensemble des fichiers de la source dans le répertoire de sortie. Donc, si jamais on a oublié de builder un fichier, il est présent malgré tout dans la version buildée. Pour ma part, je pense que la version buildée doit être optimisée et que la phase de test est là pour palier à l'oubli d'inclusion d'un widget dans le fichier de profil. C'est pourquoi j'ai réalisé cette partie optimisation.

En premier lieu, le fichier "dojo.js" est copié vers la destination : c'est ce seul fichier JavaSript qui comprend tout notre code source. Même les fichiers de template HTML ont été internalisés.

Ensuite, un recopie sélective des ressources css, png et gif est réalisée pour le répertoire "dojo". On exclu les fichiers terminant par "commented.css" car ce sont les fichiers css non optimisés qui sont gardés par le build.

Pour le répertoire "Dijit", c'est très simple : tous les fichiers ont été internalisés dans "dojo.js" dont il ne reste que les ressources css, png et gif situés dans le répertoire "themes". Et c'est tout !

Pour finir, les ressources du répertoire "Dojox" peuvent être copiées si nécessaire.

V-D. Lancement du projet optimisé

Une fois le fichier buildé, un nouveau répertoire /build/lib contient notre bibliothèque Dojo optimisée. On va donc modifier notre fichier HTML afin de le prendre en compte :

Le fichier build.xml
Sélectionnez

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Projet de test de build (version optimisée)</title>
	<!-- Import du source optimisé de Dojo -->
	<script  type="text/javascript"
		src="build/lib/dojo/dojo.js" djConfig="parseOnLoad:  true">		
	</script>
	<!-- Import des css-->
	<style type="text/css">
        @import "build/lib/dijit/themes/tundra/tundra.css";
        @import "build/lib/dojo/resources/dojo.css";
    </style>
	<!-- Import des bibliothèques nécéssaires à l'affichage des widgets -->
	<!-- Dans cette version optimisée, il n'y aura pas de chargement de JavaScript -->
	<script  type="text/javascript">
		dojo.require("dijit.form.Button");
		dojo.require("dijit.form.CheckBox");
	</script>
</head>
<body  class="tundra">
	<div  dojoType="dijit.form.Button">Click Button</div>
	<div>Une checkBox</div>
	<div  dojoType="dijit.form.CheckBox"></div>
</body>
</html>
 
Image non disponible
Après le build

Et voilà ! 7 requêtes, 334 ms et 212 Kb sur mon poste. L'optimisation est bien réelle et le gain de performance en production sera vraiment énorme.

VI. Conclusion

Comme vous avez pu le constater, le build de Dojo est vraiment nécessaire si on veut obtenir une application performante. La mise en œuvre à l'aide de Ant est simple et permet, dans le cadre d'un projet réel, d'automatiser le build (l'intégration continue peut même être envisagée).

Le système de build trouve un intérêt supplémentaire dans le cas où on crée des widgets personnalisés. En effet, dans ce cas là, les widgets pourront également être buildés de la même manière que les widgets de Dijit ou Dojox. On pourra dès lors obtenir une application Dojo personnalisée et performante.

Mais pour personnaliser cette application, il faudra apprendre à réaliser des widgets personnalisés. Ce sera le sujet d'un futur article ...

VII. Le code source

Voici le code source pour refaire les exemples du système de build: Le code source

VIII. Remerciements

Je voudrais remercier dourouc05 pour la correction de mon article.