Automatisez votre workflow avec gulp.js

Depuis que j'ai découvert SASS/SCSS en 2011, je l'utilise dans tous mes projets. Y compris sur d'anciens projets en PHP “maison” (pas de framework) que je maintiens encore activement. Pour ces projets, je me servais jusque-là de l'outil en ligne de commande pour la compilation : au début d'une session de développement, je lançais donc la commande sass --watch style/sass:style/ --style compressed dans un terminal, et toutes les modifications faites sur mon fichier source étaient immédiatement répercutées sur mon CSS. On a connu plus pratique : je devais ajuster les répertoires en fonction de l'emplacement où j'étais, et il m'arrivait parfois d'oublier l'option compressed. Il était temps d'automatiser tout ça !

Vous allez me dire “pourquoi pas un simple script bash qui lance la bonne commande, et basta”. Et vous n'aurez pas tort. Mais voilà un moment que je voulais m'essayer à des outils comme Grunt ou Brunch.io : c'était l'occasion où jamais !

Grunt, Brunch, Gulp… qu'est-ce que c'est ?

Ces outils, désormais plébiscités pour le développement frontend, permettent d'automatiser toutes les tâches répétitives que l'on est amenés à faire : compilation des SCSS ou des Coffee Scripts, minification, compression des fichiers image, etc.

Différents outils existent, Grunt semble être un des plus répandus, mais souvent décrié à cause d'une configuration très verbeuse. Comme c'est généralement quelque chose que je n'apprécie pas, je ne me suis pas attardé longtemps dessus. J'ai testé rapidement brunch.io, mais j'ai eu quelques difficultés à le faire fonctionner, et je trouvais tous ces outils assez difficiles à appréhender, beaucoup plus que ce à quoi je m'attendais en tout cas. Et au moment où je commençais à envisager de finalement passer par un simple script shell, j'ai découvert l'excellent gulp.js. Twingo !

Gulp.js

La particularité de gulp.js est d'être configuré via un fichier javascript, aka gulpfile.js, et par conséquent facile à appréhender pour quelqu'un qui connaît un minimum ce langage. La configuration de gulpjs consiste à créer des flux dans lesquels vont passer les fichiers à traiter : on y configure un enchaînement de modules spécialisés chacun dans une tâche particulière, dans l'ordre qu'on souhaite, et on renvoie le résultat dans un ou des fichier(s).

Mettons-nous en situation

Dans mon cas, c'est assez simple : j'utilise des fichiers SCSS que je compile en CSS minifié, et pas de Coffee Script. Je me suis également dit que c'était une bonne occasion pour minifier mes fichiers Javascript perso (les fichiers javascript de librairie étant tous d'ores et déjà minifiés) par la même occasion. J'avais donc besoin de modules gérant :

  1. La compilation de SCSS → CSS : gulp-sass,
  2. La minification de CSS : gulp-minify,
  3. La minification de Javascript : gulp-uglify,
  4. Le renommage de fichier : je vais enregistrer mes fichiers javascripts source en *.src.js, et je sauvegarderai les fichiers minifiés en *.min.js. Pour ça, j'utiliserai gulp-rename.

Installation de gulp et de ses modules

Gulp.js se base sur node.js : il vous faut donc une installation de node.js fonctionnelle avec node et npm. Une fois que c'est le cas, vous n'avez plus qu'à lancer la commande suivante pour faire une install globale de gulp :

1
2
3
4
$ sudo npm install -g gulp
$ gulp -v
» [gulp] CLI version 3.6.2
» [gulp] Local version undefined

OK, gulp est bien installé ! Ensuite, direction la racine de notre projet, on va devoir indiquer à gulp quels modules sont nécessaires. Pour cela, créons d'abord un fichier package.json au moyen de la commande npm init. Répondez aux différentes questions posées (dans le doute, laisser la valeur par défaut), et voilà, votre fichier package.json est créé. C'est lui qui va conserver les dépendances de votre projet.

Désormais, nous devons installer les modules gulp identifiés précédemment. Nous utiliserons la commande npm install --save-dev <module> pour que les modules soient enregistrés dans le package.json en tant que dépendances de développement. Pas besoin de sudo cette fois-ci, puisque nous installons les modules uniquement pour notre projet. C'est parti, donc :

1
2
3
4
$ npm install --save-dev gulp-sass
$ npm install --save-dev gulp-minify
$ npm install --save-dev gulp-uglify
$ npm install --save-dev gulp-rename

gulpfile.js

Tout est prêt, entrons dans le vif du sujet, notre fichier gulpfile.js. Tout d'abord, nous avons besoin d'y importer les modules que l'on vient d'installer. Rien de compliqué :

1
2
3
4
5
var gulp      = require('gulp'),
    rename    = require('gulp-rename'),     // Renommage des fichiers
    sass      = require('gulp-sass'),       // Conversion des SCSS en CSS
    minifyCss = require('gulp-minify-css'), // Minification des CSS
    uglify    = require('gulp-uglify');     // Minification/Obfuscation des JS

Nous allons maintenant définir les flux de traitement à appliquer à nos fichiers : dans le monde gulp, ces flux s'appelle des tâches. Comme évoqué en début d'article, chaque tâche est un enchaînement d'actions qui prend en entrée des fichiers, et envoie le résultat de la tâche dans d'autres fichiers. Définissons notre première tâche, celle qui consistera simplement à compiler les fichiers SCSS :

1
2
3
4
5
6
7
8
// SCSS TASK
gulp.task('css', function()
{
  return gulp.src('./src/style/sass/*.scss')    // Prend en entrée les fichiers *.scss
    .pipe(sass())                      // Compile les fichiers
    .pipe(minifyCss())                 // Minifie le CSS qui a été généré
    .pipe(gulp.dest('./src/style/'));  // Sauvegarde le tout dans /src/style
});

Plutôt simple, n'est-ce pas ? Pour tester cette tâche, direction le terminal, et lançons la commande gulp css :

1
2
3
4
$ gulp css
[gulp] Using gulpfile ~/gulp-test/gulpfile.js
[gulp] Starting 'css'...
[gulp] Finished 'css' after 68 ms

Et voilà, si vous vous rendez dans votre répertoire /style, vous y trouverez votre fichier CSS minifié. Elle est pas belle la vie ? Maintenant que nous sommes sur notre lancée, occupons-nous de la tâche qui renomme et minifie les fichiers JS.

1
2
3
4
5
6
7
8
9
10
11
12
// JAVASCRIPT TASK
gulp.task('js-uglify', function()
{
  return gulp.src('./src/js/*.src.js')    // Prend en entrée les fichiers *.src.js
    .pipe(rename(function(path){
      // Il y a différentes méthodes pour renommer les fichiers
      // Voir ici pour plus d'infos : https://www.npmjs.org/package/gulp-rename
      path.basename = path.basename.replace(".src", ".min");
    }))
    .pipe(uglify())
    .pipe(gulp.dest('./src/js/'));
});

Ça n'était pas beaucoup plus compliqué, n'est-ce pas ? Idem, on va vérifie que la tâche s'exécute bien :

1
2
3
4
$ gulp js-uglify
[gulp] Using gulpfile ~/gulp-test/gulpfile.js
[gulp] Starting 'js-uglify'...
[gulp] Finished 'js-uglify' after 57 ms

gulp watch et tâche par défaut

On a déjà parcouru un sacré chemin depuis le début ! Mais vous l'avouerez, lancer les tâches manuellement à chaque fois qu'un fichier a été modifié, ce n'est pas pratique, et c'est loin de ce qu'on espérait faire intialement. Essayons donc de reproduire le comportement de sass --watch :

1
2
3
4
5
6
// WATCH TASK
gulp.task('watch', function()
{
  gulp.watch('./src/style/sass/*.scss', ['css']);
  gulp.watch('./src/js/*.src.js', ['js-uglify']);
});

Maintenant, il suffira de lancer la commande gulp watch pour que Gulp.js lance les tâches css et js-uglify dès qu'il détectera une modification sur les fichiers sources.

Vous trouvez que gulp watch, c'est encore trop long à écrire ? Alors nous pouvons définir la tâche watch par défaut : elle sera alors lancée quand vous utiliserez la commande gulp :

1
gulp.task('default', ['watch']);

Complément : gulp-plumber

À l'usage, je me suis rendu compte d'un problème avec cette approche : lorsque les fichiers modifiés comportent des erreurs, la tâche watch est arrêtée. Il faut alors relancer gulp pour recommencer à surveiller les fichiers source. Pas très pratique, n'est-ce pas ? Pour remédier à ce problème, quelques rapides recherches m'ont orienté vers le module gulp-plumber, dont la description parle d'elle-même : gulp-plumber prevents pipe breaking caused by errors from gulp plugins. Exactement ce qu'il nous faut !

Nous allons donc installer et configurer ce dernier module, vous devriez vous en sortir seuls désormais.

1
$ npm install --save-dev gulp-plumber
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// --- gulpfile.js : le fichier complet ---

var gulp      = require('gulp'),
    rename    = require('gulp-rename'),     // Renommage des fichiers
    sass      = require('gulp-sass'),       // Conversion des SCSS en CSS
    minifyCss = require('gulp-minify-css'), // Minification des CSS
    uglify    = require('gulp-uglify'),     // Minification/Obfuscation des JS
    plumber   = require('gulp-plumber');    // Ne pas s'arrêter en cas d'erreurs


// SCSS TASK
gulp.task('css', function()
{
  return gulp.src('./src/style/sass/*.scss')    // Prend en entrée les fichiers *.scss
    **.pipe(plumber())**
    .pipe(sass())                      // Compile les fichiers
    .pipe(minifyCss())                 // Minifie le CSS qui a été généré
    .pipe(gulp.dest('./src/style/'));  // Sauvegarde le tout dans /src/style
});


// JAVASCRIPT TASK
gulp.task('js-uglify', function()
{
  return gulp.src('./src/js/*.src.js')    // Prend en entrée les fichiers *.src.js
    .pipe(plumber())
    .pipe(rename(function(path){
      // Il y a différentes méthodes pour renommer les fichiers
      // Voir ici pour plus d'infos : https://www.npmjs.org/package/gulp-rename
      path.basename = path.basename.replace(".src", ".min");
    }))
    .pipe(uglify())
    .pipe(gulp.dest('./src/js/'));
});


// WATCH TASK
gulp.task('watch', function()
{
  gulp.watch('./src/style/sass/*.scss', ['css']);
  gulp.watch('./src/js/*.src.js', ['js-uglify']);
});


gulp.task('default', ['watch']);

Et voilà ! Désormais, lorsqu'on lance gulp et que les fichiers SCSS comportent des erreurs (par exemple), plumber entre en action et fait en sorte que gulp continue à tourner malgré tout :) Je vous laisse fouiller parmi les modules gulp, il existe une foule d'actions possibles. J'ai intégré ce matin auto-prefixer à mon workflow par exemple, c'est encore une fois d'une simplicité enfantine !


Ressources