Javascript et Maven

Le moins que l’on puisse dire, c’est que le  javascript a le vent en poupe.
Le trinôme HTML5/CSS3/Javascript se taille la part du lion pour la réalisation des applications et sites front.
Les plus grands acteurs du web l’ont complètement adopté. Il suffit d’observer le nombre de framworks, les annonces d’Adobe sur l’abandon du flex au profit des standards du web, l’avènement du mobile… Bref tous les indicateurs sont au beau fixe pour faire du Javascript le langage pivot des applications d’aujourd’hui et de demain. Ce langage hier décrié, gagne donc ces lettres de noblesses et attire de plus en plus les « ingénieurs respectables » qui apportent leur savoir-faire.

La conséquence de tout cela est que les projets se complexifient et on y retrouve de plus en plus de concepts jusque là réservés au backoffice : pattern, MVC, injection de dépendance et bien sur industrialisation. Et quel outil mieux que maven pouvait occuper le créneau du build.

Maven et Javascript. Il y a 3-4 ans cela aurait fait sourire, mais maintenant nous l’utilisons au quotidien. Mais pour quoi faire ?

L’intérêt est le même que pour les projets backoffice. Automatiser un certain nombre de tâches tout au long du cycle de vie du projet.

Nous voulons maitriser le processus de build, avoir un système uniforme qui garantie la pérennité du code en :

  • validant le code javascript (jslint)
  • concaténant des fichiers javascritpts ou css
  • minifiant ces fichiers
  • exécutant des test unitaires
  • générant des packages de livraisons …

Nous pouvons également ajouter nos projets dans un outils d’intégration continue comme Jenkins afin d’automatiser les build et les déploiements.

Cela permettra aussi de faciliter les processus de livraison. On imagine facilement des livraisons différentes en fonction du contexte (debug/prod) à l’aide des profils activés lors du build.

On pourra également  générer plusieurs type de « livrables ». Par exemple on automatisera la génération des sources différentes selon un contexte mobile/desktop.

Bref le maître mot est laissons faire maven pour le build et concentrons nous sur le contenu.

Mais contrairement aux environnements Java on est encore loin du célèbre « convention over configuration » et la mise en place peut engendrer pas mal de bidouilles dans vos pom.

La « mavenisation » de nos projet peut se faire à plusieurs niveaux.

assembly plugin/dependancy plugin

Sans utiliser des plugin maven spécialisés nous pouvons jeter un coup d’œil sur les bons vieux plugins maven. Le binôme assembly plugin/dependancy plugin permet de résoudre un certain nombre de situations

En effet l’assembly plugin vous permet de vous constituer une archive facilement déployable dans d’autres projets.

Imaginez par exemple que vous ayez codé un composant complet dans un projet que vous aimeriez utiliser plus tard. Pour éviter les copier/coller, vous en faites une dépendance.

Cette configuration indique d’utiliser le fichier my-assembly.xml qui spécifie les fichiers à inclure dans notre dépendance. On utilise l’id de l’assembleur comme classifier (appendAssemblyId).

Et zou ! Vous voila avec une nouvelle dépendance identifiée par le groupid/artifactid du projet et le classifier choisi.

Du coup au prochain build vous aurez dans le répertoire target le fichier suivant:

groupid-artifactid-version-my-script.zip

Et cette dépendance pourra être utilisée dans d’autres projets grâce au maven-dependency-plugin

On indique juste les attributs de la dépendance ainsi que le répertoire dans laquelle on souhaite la dezipper (outputDirectory)

Ces solution permettent de packager et récupérer des ressources en utilisant maven. Mais il n’y a aucun ajout spécifique lié à nos langages quotidiens.

Pour retrouver des fonctionnalités dédiées au javascript et aux css il faut se tourner vers des plugins spécialisés.

Javascript Maven Plugin

Pour la petite histoire nous avons commencez à l’utiliser en 2011 dans une version 1.0-alpha-1-SNAPSHOT qui est au point mort mais qui apporte un grand nombre de fonctionnalités.

Mais depuis peu une version 2 est sortit et elle à l’air très complète. On en reparlera une autre fois
Alors comment ça se passe avec l’ancienne version ?

Installation

Pour utiliser ce plugin dans votre projet il faut déclarer le repo suivant dans la section pluginRepositories

Vous définissez ensuite votre projet en packaging javascript.

C’est pour cela que vous devez systématiquement rajouter la propriété extension dans la configuration du plugin.

Gestion des ressources

Ce plugin ne gère que les fichiers javascript.

Mais dans le package finale on désire souvent ajouter d’autres fichiers (css,images,html…). La gestion des autre ressources passe par le maven-resources-plugin. C’est à dire que par défaut tous les fichiers présents dans le répertoire src/main/resources seront incluent dans le package.

Pour modifier ce comportement par défaut et inclure d’autres ressources il faut les déclarer manuellement.

Goal compile

Ce goal va permettre de copier l’ensemble des scripts de sourceDirectory (src/main/javascript par défaut) dans le répertoire identifié par outputDirectory (${project.build.outputDirectory} par défaut).

On peut également contrôler les fichiers à inclure ou exclure (à noter que l’exclusion prime sur l’inclusion).

Avec un descripteur (descriptor) les fichiers mentionnés  seront concaténés en un seul fichier. Les autres seront juste copiés.

Dans cet exemple on décide de générer le fichier site.js dans le répertoire build à partir des fichiers file1.js et file2.js

Attention les règles d’inclusion/d’exclusion du pom priment sur les inclusions du fichiers d’assembler.

Le plugin exécute par défaut un goal compile ayant pour id ‘default-compile’. Si vous désirez votre propre configuration sans que le goal soit exécuté 2 fois il suffit de reprendre cet id.

Goal package

Ce goal va permettre de packager le répertoire de build sous forme de jar. Les réglages par défaut sont en général suffisant et il n’est pas nécessaire de rajouter une conf spécifique mais on peut vouloir spécifier un classifier ou indiquer le répertoire à packager

La propriété scriptsDirectory indique le répertoire root qui sera packagé. Donc si vous le faites pointer sur le répertoire contenant vos scripts, vous n’inclurez pas dans l’archive les autres ressources.
De même que pour le goal compile, il existe une exécution par défaut appelée default-package.

Goal compress

Ce goal sera exécuté après le phase de compilation sur les scripts produits. Tous les path sont configurables.

La propriété webappDirectory permet de spécifier le répertoire root et la propriété scripts indique le nom du répertoire dans lequel se trouvent les fichiers javascript à compresser.
Par défaut le compressor utilisé est jsmin mais il est préférable d’utiliser celui de yahooUI car il permet une validation des scripts avant la compression qui fait échouer le build en cas d’erreurs.

Goal inplace

Ce goal est utilisé pour inclure des dépendances javascript dans votre projet. Les dépendances doivent être buildées avec ce plugin pour pouvoir être incluses.

Si par exemple vous avez un framework js buildé de façon autonome vous pourrez alors inclure ces sources dans votre projet. Pour cela il vous suffit de le déclarer en dépendance avec le type javascript

Il faut ensuite configurer le goal pour lui indiquer ou dezipper les sources (warSourceDirectory).
Je mes un / dans libsDirectory et scriptsDirectory sinon le plugin par défaut utilise une arborescence script/libs/lib.

Goal jsunit

Ce goal permet de lancer les test unitaires. Nous ne l’utilisons pas car :

  • il n’est pas facilement transposable (path vers un exécutable de browser)
  • la gestion des ressources à inclure n’est pas aisée (l’inclusion se fait dans les fichiers de test)
  • il fait appel a jsunit qui est obsolète maintenant (on notera la référence à jasmine sur la home du projet 🙂 )

Du coup nous configurons le plugin en skip test pour pouvoir utiliser jasmine.

Jsdoc

Codehauss fournit également un plugin permettant de générer la jsdoc.

La définition du plugin est :

Et on l’éxécute de la façon suivante :

On indique uniquement la localisation des sources à parser (sourceDirectory).

On retrouve alors dans le target/site/jsdoc l’arborescence du site correspondant à notre documentation.

YUI Compressor Maven Mojo

Comme son nom l’indique ce plugin se concentre sur les problématiques de compression et de validation.

Il ne nécessite pas de packaging particulier et s’insère dans un processus « classique » de build. A vous de voir si vous voulez un packaging war ou jar.

Installation

Pour utiliser ce plugin dans votre projet il faut déclarer le repo suivant dans la section pluginRepositories.

Et il suffit ensuite de le déclarer dans la section plugins de votre pom.

Gestion des ressources

Ce plugin ne manipule que les fichiers js et css. Il se constitue un pool de ressources à partir :

  • des fichiers contenus dans le répertoire définit par la propriété sourceDirectory.
  • des fichiers contenus dans les répertoires de ressources (par defaut src/main/resources)
  • des fichiers contenus dans le répertoire définit par la propriété warSourceDirectory (par défaut src/main/webapp)

La propriété nosuffix permet de ne pas rajouter de suffixe aux fichiers manipulés et de remplacer l’original. Sinon par défaut le fichier compressé est suffixé de -min.

Goal jslin et compress

Le goal jslint permet de lancer une validation jslint sur les fichiers js et le goal compress permet la compresion et la concaténation.

Le résultat de la compression dépend de l’origine des fichiers:

  • Les fichiers provenant de sourceDirectory et des ressources se retrouveront dans le répertoire définit par la propriété outputDirectory
  • les fichiers provenant de warSourceDirectory se retrouveront dans le répertoire définit par la propriété webappDirectory (par defaut
    ${project.build.directory}/${project.build.finalName})

La compression est toujours précédée d’une phase de validation.

Agrégation

Le plugin permet également de concaténer les fichiers js et css en un seul. Ici, pas d’assembleur, les règles d’inclusions et d’exclusions se font directement dans le pom à l’intérieur d’une section aggrégation.

On fournit au plugin une liste de fichiers à inclure ou exclure (l’exclusion étant prioritaire sur l’inclusion).
Pour déterminer le path du fichier traité, le plugin utilise comme répertoire de base le répertoire parent du fichier déclaré dans output.

On peut modifier ce répertoire grâce à la propriété inputDir.

Attention pour un packaging de type war par défaut le goal compress s’exécute pendant la phase process-resources. Du coup des fichiers peuvent être écrasés lors de la phase de Processing du maven-war-plugin. Pour y pallier vous pouvez :

  • déclarer que vos goal s’éxécutent durant la phase package
  • configurer le maven-war-plugin pour exclure des fichiers à copier

Jasmine

Ce plugin permet d’executer les test jasmine.

Il suffit de lui indiquer :

  • le root des sources (jsSrcDir)
  • le root des script de test (jsTestSrcDir)
  • les différents fichiers à inclure dans le contexte d’éxécution (sourceIncludes)
  • les différentes suites de test à exécuter (specIncludes)

Attention, pensez à utilisez au moins la version 1.2.0.0 et à mettre la propriété extension à true. Sinon vous constaterez que vos différentes phases de build sont exécutées 2 fois.

La section sourceIncludes permet de spécifier les fichiers à ajouter au contexte d’éxécution. C’est typiquement ici que l’on ajoute :

  • les librairies et framework (jquery, mootools)
  • les classes à tester

Si vous désirez rajouter des librairies dans votre contexte de test (jasmine-jquery par exemple) il faut les référencer dans la section specIncludes.

Vous verrez alors dans la console le résultat d’éxécutions de vos différents « describe » et « it »

Conclusion

Le plus souvent, dans des projets web nous utilisons une combinaison des différentes solutions décrites ci-dessus. Le pom ci-dessous est représentatif d’un vrai projet web.

  • Avec le maven-resources-plugin on définit les ressources à inclure (images,skin pour les css)
  • On rajoute des dépendances static (css) avec le maven-dependency-plugin
  • On agrège les css en un fichier avec le yuicompressor-maven-plugin
  • On récupère des dépendances javascript avec le goal inplace du javascript-maven-plugin
  • On compile,agrège et minifie l’ensemble des fichiers javascripts avec le javascript-maven-plugin
  • On crée des dépendances spécifiques avec le maven-assembly-plugin
  • On test les scripts à l’aide du jasmine-maven-plugin

L’utilisation de maven est donc possible pour builder vos applications web. La difficulté principale réside dans la gestion des ressources et vous risquez de passer du temps à déterminer les différents path pour contrôler finement le devenir de vos fichiers.
Mais bref, on le constate bien, les outils sont là. Il reste un degré de maturation à franchir, mais gageons que l’engouement suscité fera que, de plus en plus de développeurs Java proposeront des solutions d’intégrations continus.