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 sont décrits 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 suffiraient pas), mais va mettre en lumière une 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'); ». À 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.
La masse de fichiers à charger induit des temps de chargement prohibitifs d'une simple application Dojo : de l'ordre de une 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 :
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. Étant 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 JavaScript 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) :
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 :
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 doit 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.
On va coder un petit fichier HTML permettant l'affichage de deux widgets Dijit.
<!
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écessaires à 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 :
Pas terrible : 79 requêtes, 1,3 seconde 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 :
/**
*
@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.
<?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 veut pouvoir utiliser firebug light-->
<copy
todir
=
"${final.dir}dojo/_firebug/"
>
<fileset
dir
=
"${release.dir}dojo/_firebug/"
/>
</copy>
<!-- Les images et les css doivent être gardé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 -->
<!-- À 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 pallier 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 JavaScript 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 exclut 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 :
<!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écessaires à 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>
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.