Utilisez Jake pour builder vos livrables web
01 Décembre 2010
Construire les livrables de ces webapps ou de son blog est une étape rébarbative avec des tâches que l'on est obligé de répéter... Sauf si l'on ne veut pas optimiser ces fichiers au niveau performances. Il permet d'automatiser des tâches pour produire un livrable final. Au travers de cette introduction à Jake, nous allons voir comment construire son propre mécanisme de build pour les fichiers statiques de son site...
Installation de Jake
Jake
est un outil de build qui utilise Node.js avec des fonctionnalités similaires à Rake, un outil de build pour le langage Ruby. Pour fonctionner, il faut donc que Node.js soit installé sur votre machine, profitez-en pour installer également NPM
l’excellent gestionnaire de paquets pour Node.js
, sans quoi l’installation de Jake sera impossible.</p>
Pour installer Jake
, c'est on ne peux plus simple avec NPM, il suffit de saisir la commande suivante :
``` shell
$> npm install jake
```
Si l'installation c'est bien déroulée, vous devriez pouvoir saisir dans votre terminal la commande jake
et obtenir en sortie le contenu :
``` shell
$> jake
Could not load Jakefile.
If no Jakefile specified with -f or --jakefile, jake looks for Jakefile or Jakefile.js in the current directory
```
Création de tâches
Maintenant que l'installation des différentes dépendances est terminées, nous allons pouvoir entrer dans le vif du sujet. Le principe de Jake
est de créer des tâches que l'on va executer en fonction du but que l'on veut atteindre. Dans cet article nous allons définir deux tâches qui vont permettre de minifier les fichiers CSS et les fichiers JS.
Pour définir une tâche avec Jake, il suffit de créer un fichier JavaScript avec le contenu suivant :
desc('This is a task with default name');
task('default', [], function (params) {
// do something
});
Avec ce snippet de code, il y a trois choses importantes à voir :
- 'default' est le nom de la tâche,
- '[]' est la liste des tâches pré-requises
- function(params){} est le code qui sera executé.
Ainsi pour définir pour l'objectif définit en début d'article, on va créer un fichier task.js
pour y créer 3 tâches default
minify-css
et minify-js
. Pour cela, il suffit d'insérer dans le fichier le code suivant :
desc('This is the default task that executes two next tasks');
task('default', ['minify-css','minify-js']);
desc('This the css task');
task('minify-css', [], function () {
console.log('Should do the css-minify task');
});
desc('This the js task');
task('minify-js', [], function () {
console.log('Should do the js-minify task');
});
Execution des tâches
Maintenant que les tâches viennent d'être crées dans le fichier Jake, nous allons voir comment les exécuter. Pour cela rien de plus simple, ouvrez un terminal, naviguez jusqu'à votre répertoire de test et saisissez la commande :
$> jake -f task.js
Should do the css-minify task
Should do the js-minify task
Mais si on ne veux pas executer la tâche pour minifier les fichiers javascripts comment faire ? Simplement en ajoutant le nom de la tâche que je veux exécuter à la ligne de commande précédente :
$> jake -f task.js minify-css
Should do the css-minify task
Codage des tâches
Les tâches sont crées, vous savez les executées, il ne reste plus qu'à coder les actions à réaliser par les tâches. Pour tout ce qui est en rapport avec la minification, nous allons tirer parti de la puissance de Node.js
et de Npm
. C'est à dire que nous allons installer des petits modules qui s'occuperont de minifier le JavaScript et les CSS.
npm install node-cssmin node-jsmin
Une fois, ces nouveaux modules installés on peut coder les tâches. Pour cette implémentation, j'ai choisi de minifier uniquement les fichiers JS et CSS qui ne se terminent pas pas le suffixe "-min". Voici le code qui réalise ces traitements. Ces snippets fonctionne mais libre à vous de les adapter, améliorer (rayez la mention inutile) selon vos besoins.
var fs = require('fs'), path = require('path');
desc('This is the default task');
task('default', ['minify-css', 'minify-js'], function (params) {
console.log('Default task');
});
desc('This the css task');
task('minify-css', [], function () {
console.log('Starting css-minify task');
var cssmin = require('cssmin').cssmin;
path.exists("minified", function (exists) {
if(! exists){
fs.mkdirSync("minified", mode=0744);
}
var files = filterFilesWithExtension('.', 'css');
files.forEach(function(filename){
content = fs.readFileSync(filename, encoding='utf8');
var minified = cssmin(content);
writeMinifiedFile("./minified/" + filename, minified);
});
});
});
desc('This the js task');
task('minify-js', [], function () {
console.log('Starting js-minify task');
var jsmin = require('jsmin').jsmin;
path.exists("minified", function (exists) {
if(! exists){
fs.mkdirSync("minified", mode=0744);
}
var files = filterFilesWithExtension('.', 'js');
files.forEach(function(filename){
content = fs.readFileSync(filename, encoding='utf8');
var minified = jsmin(content);
writeMinifiedFile("./minified/" + filename, minified);
});
});
});
/***************************************************************/
function filterFilesWithExtension(path, ext){
var matched = new Array();
var files = fs.readdirSync(path);
for(i in files){
if(files[i].match("\."+ext+"$") && ! files[i].match("-min\."+ext+"$"))
matched.push(files[i]);
}
console.log(matched.length + ' files where found with extension "' + ext + '" in folder ' + path);
return matched;
}
function writeMinifiedFile(filename, content){
minFilename = filename.replace(/(.*)\.([a-z]{3})/,'$1-min.$2');
fs.writeFileSync(minFilename, content, encoding='utf8');
}
Ce code déclare donc deux tâches qui permettent de minifier les fichiers JS et CSS de votre répertoire courant. Il pourrait être intéressant d'améliorer le code pour parcourir les répertoires de manière récursives, mais cet extrait de code vous permet de spécifier manuellement les répertoires à parcourir donc la fonctionnalité est minimale mais adaptable selon vos besoins. Idéalement, j'aimerai ne plus avoir de méthodes de traitement secondaire (par exemple la fonction qui filtre les fichiers par extension (filterFilesWithExtension), il faut juste voir si des modules Node.js existent pour effectuer ce traitement, ce qui allégera le fichier mais ajoutera des dépendances... C'est un choix.
Rendre le système pérènne
Tout d'abord, je vous conseille de créer un fichier de tâches par projet et de le sauvegarder dans un outil de gestion de version, cela permettra de vous éviter le stress d'une release pressé et de pas pouvoir générer vos fichiers.
Ensuite, pourquoi s’arrêter à deux tâches dans ce fichier alors que l'on pourrait créer d'innombrables autres tâches ? Dans le cas ou vous voudriez créer d'autres tâches il serai donc intéressant de pouvoir "compartimenter" le code des différentes tâches. Imaginons que l'on décide de créer des tâches capables de combiner ou changer le formatage du code ou encore effectuer un autre traitement sur nos fichiers statiques...
Pour ne pas encombrer notre code, nous pourrions l'organiser selon le type de fichier qui va subir la tache. Pour faire cette séparation, on peut utiliser les namespace pour permettre de gérer correctement les tâches. Ainsi le code suivant
desc('This is the default task that executes two next tasks');
task('default', ['minify:default', 'combine:default'], function(){});
namespace('minify', function(){
desc('This is the default task of the namespace');
task('default', ['minify:css','minify:js'], function(){});
desc('This the css task');
task('css', [], function () {
console.log('Should do the minify:css task');
});
desc('This the js task');
task('js', [], function () {
console.log('Should do the minify:js task');
});
});
namespace('combine', function(){
desc('This is the default task of the namespace');
task('default', ['combine:css','combine:js'], function(){});
desc('This the css task');
task('css', [], function () {
console.log('Should do the combine:css task');
});
desc('This the js task');
task('js', [], function () {
console.log('Should do the combine:js task');
});
});
Maintenant, il faudra saisir la commande suivante pour effectuer une minification des fichier CSS et JS en supposant que votre fichier de tâches s'appelle task.namespace.js
$> jake -f task.namespace.js combine:default
Conclusion
Jake
est un utilitaire d'une puissance incroyable, je commence tout juste (tout comme Node.js) à l'apprivoiser donc le code que je présente dans cet article est encore fortement améliorable. Mais il est extrêmement pratique d’écrire du JavaScript pour faire des traitements de fichiers avant de les mettre en prod...
Je l'utilise maintenant pour minifier mes fichiers et les combiner, j'exporte les fichiers traités dans un répertoire build que je suffixe avec un timestamp de la date... Avec ces extraits de code, pourquoi ne pas réaliser un petit serveur de développement qui minifierai et combinerai les fichiers à la volée pour valider en direct que les fichiers produits sont bien valides, ce genre de développement se réalise très rapidement avec Node.js
Node.js est vraiment un outil incroyable et extensible à souhait...
Informations complémentaires :