Le système de build de Dojo

Morvan Mikaël

Cet article décrit le système de build de Dojo permettant d'optimiser les performances d'une application Dojo. Il explique les raisons du build et en donne un exemple en utilisant Ant.

Merci d'avance pour vos commentaires: Commentez ♪

- Titre : Le système de build de Dojo

- Auteur : Morvan Mikaël

- Parution : 30 juin 2009

- 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 ® 2009 Morvan Mikaël. 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. Introduction

- II. Focus sur les performances des applications WEB 2.0

- III. Description du build de Dojo

- IV. Description du fonctionnement du système de build

- V. Mise en œuvre avec Ant

- V-A. Création du projet Eclipse

- V-B. Création du fichier de profile

- V-C. Création du fichier Ant

- V-D. Lancement du projet optimisé

- VI. Conclusion

- VII. Le code source

- VIII. Remerciements

I - Introduction

Dojo est un framework JavaScript open source très complet. J'ai rédigé un précédent article 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.

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 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 :

Avant le build

Après le build

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) :

Exemple de fichier profile

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é)

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 tutoriel 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 article, 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.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.

Répertoire projet que vous devez obtenir

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

fichier index.html

<!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 :

Avant le build

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 :

Le fichier profile.json dans le répertoire build

/**
* @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

<?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 :

Le fichier build.xml

<!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>

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.