Gulp pour les débutants

Avec Grunt, Gulp est l'un des principaux task runners utilisés aujourd'hui. Le formidable Zell Liew présente de manière claire et complète cet outil de développement qui vous facilitera la vie.

Par

Note de Chris Coyier : Zell a un style de tutoriel bien à lui (voir son tuto Découvrir Susy) qui se prête bien à des sujets de cette ampleur. Je me suis attaqué à Grunt dans mon article Grunt pour ceux qui pensent que Grunt est compliqué et j’ai l’impression que les lecteurs l’ont trouvé bien utile, alors on recommence avec Gulp qui est tout aussi populaire que Grunt !

Gulp est un task runner c’est à dire qu’il effectue des tâches pour vous. Dans le développement web, vous l’utiliserez pour des tâches front-end telles que :

  • créer un serveur web local
  • rafraîchir le navigateur automatiquement à chaque fois qu’un fichier est modifié
  • utiliser des préprocesseurs comme Sass ou LESS
  • optimiser des ressources comme CSS, JavaScript et les images.

La liste est longue de ce que Gulp peut faire pour vous (les dingos comme moi l’utilisent pour construire des générateurs de sites statiques). Cet article est un guide d’introduction à Gulp qui vous aidera à construire vos propres process de construction. Si j’ai bien fait mon boulot, vous maîtriserez les bases de Gulp et vous aurez les outils pour aller plus loin.

Avant de commencer, voyons pourquoi vous pourriez utiliser Gulp plutôt que d’autre outils.

Pourquoi utiliser Gulp ?

Les outils comme Gulp sont souvent appelés des outils de développement ou de construction (build tools) car ils effectuent automatiquement un certain nombre de tâches nécessaires à la construction d’un site web. Les deux outils les plus populaires sont Gulp et Grunt, mais il y en a d’autres, comme Broccoli. Ce dernier se concentre sur la compilation de ressources, une des tâches les plus courantes.

Il existe déjà de nombreux articles expliquant les différences entre Grunt et Gulp, notamment celui-ci, celui-là ou encore cet autre. Brunch est un autre outil qui se concentre également sur les ressources et contient quelques-unes des tâches plus courantes telles que le serveur local et l’observation des modifications de fichiers, cette page présente les arguments en faveur de Brunch.

La principale différence entre eux réside dans la façon de configurer un workflow. Les configurations de Gulp sont généralement plus simples que celles de Grunt. Gulp est également plus rapide.

Et maintenant, commençons !

Ce que nous allons réaliser

À la fin de ce tutoriel, vous aurez un workflow comprenant les tâches détaillées au début de cet article :

  • créer un serveur web local
  • compiler Sass en CSS
  • rafraîchir le navigateur automatiquement à chaque fois qu’un fichier est modifié
  • optimiser toutes les ressources (CSS, JS, fonts, images).

Vous apprendrez également comment enchaîner différentes tâches dans des commandes simples, faciles à comprendre et à exécuter.

Mais d’abord, installons Gulp sur notre ordinateur.

Installer Gulp

Vous aurez besoin de Node.js (Node) sur votre ordi. Si vous ne l’avez pas déjà, il suffit d’aller sur le site de Node et de télécharger le package installer.

Une fois Node installé, vous pouvez installer Gulp en utilisant la commande suivante dans votre interface en ligne de commande ( NdT : si vous n’êtes pas familier de la chose, vous pouvez consulter l’article La console, une introduction).

$ npm install gulp -g

NB : les utilisateurs de Mac auront besoin du mot-clé "sudo" avant (il faudra donc taper sudo npm install gulp -g), pour vous en passer vous pouvez consulter cet article. Et juste un rappel : le "$" symbolise l’invite de commande, il ne fait pas partie de la commande que vous tapez.

Le flag -g de cette commande indique à npm que Gulp doit être installé globalement, ce qui permet d’utiliser la commande gulp depuis n’importe où sur votre système.

Créons maintenant un projet qui utilise Gulp.

Créer un projet Gulp

Pour commencer, nous allons créer un dossier que nous appellerons (par exemple) project qui sera la racine de notre projet pour ce tutoriel. Lancez la commande run init depuis ce dossier :

...placez-vous sur le dossier project en tapant cd suivi d’un espace suivi du chemin d’accès au dossier
$ npm init

NdT : si vous rencontrez un problème, reportez-vous à l’article sur Grunt, partie “installation”, voir le gif intitulé “pour les ploucs de la console.

La commande npm init crée un fichier package.json pour project, qui enregistre les infos relatives à votre projet, telles que les dépendances utilisées (Gulp est un exemple de dépendance).

npm init vous renverra le message suivant :

détails du projet et demande de confirmation oui/non

Une fois le fichier package.json créé, nous pouvons installer Gulp dans le projet avec la commande suivante :

npm install gulp --save-dev

Cette fois-ci, nous installons Gulp dans le dossier project et non pas globalement, c’est pourquoi la commande est différente.

Comme vous le constatez, le mot-clé sudo n’est pas nécessaire (pour les utilisateurs de Mac) du fait que l’installation n’est pas globale. On ajoute --save-dev pour dire d’ajouter gulp comme dépendance de développement (devDependencies) dans package.json.

la dépendance gulp est ajoutée à package.json
Un premier élément dans notre liste de “devDependencies”

Si vous jetez un coup d’oeil maintenant à votre dossier project vous verrez que Gulp a créé un dossier node_modules ainsi qu’un fichier gulp à l’intérieur de node_modules.

dossier gulp dans le dossier node_modules

Nous sommes presque prêts, il nous reste un point : déterminer la structure du projet.

La structuration de nos dossiers

Gulp est très flexible et nous donne la possibilité de structurer le projet comme bon nous semble. Pour ce faire, il nous faut juste comprendre la logique avant de peaufiner notre projet.

Pour cet article, j’utiliserai la structure d’une webapp générique :

|- app/
    |- css/
    |- fonts/
    |- images/
    |- index.html
    |- js/
    |- scss/
|- dist/
|- gulpfile.js
|- node_modules/
|- package.json

Dans cette structure, nous utilisons le dossier app pour le développement, le dossier dist (pour “distribution”) contenant lui les fichiers optimisés pour le site en production.

Puisque app est utilisé pour le développement, c’est là que se trouvera notre code.

Nous devrons garder cette structure à l’esprit lorsque nous travaillerons sur les configurations Gulp. Et maintenant, nous allons créer notre première tâche dans gulpfile.js qui contient nos configurations.



Notre première tâche Gulp

Lorsqu’on utilise Gulp, la première étape consiste à le requérir (require) dans le gulpfile.

//JS
var gulp = require('gulp');

La déclaration require indique à Node d’aller chercher dans node_modules un package appelé gulp. Une fois trouvé, nous attribuons son contenu à la variable gulp.

Écrivons maintenant une tâche gulp avec cette variable gulp. Voici la syntaxe de base d’une tâche gulp :

//
gulp.task('nom-de-la-tache', function() {
  // quelque chose
});

Le nom-de-la-tache sera toujours être utilisé lorsque vous voulez lancer une tâche. Par exemple, vous pourrez lancer cette tâche dans la console en écrivant :

$ gulp nom-de-la-tache

Pour faire un test, créons une tâche intitulée hello qui affichera le message “Hello Zell” :

//JS
gulp.task('hello', function() {
  console.log('Hello Zell');
});

Nous pouvons lancer cette tâche via la console :

$ gulp hello

et la console nous retourne un log qui dit Hello Zell :

message affiché par la console : hello zell

Bon, les tâches Gulp sont en général un peu plus compliquées que cela, elles contiennent deux méthodes gulp ainsi qu’un tas de plugins. Voici à quoi ça ressemble :

//JS
gulp.task('task-name', function () {
  return gulp.src('source-files') // Get source files with gulp.src
    .pipe(aGulpPlugin()) // Sends it through a gulp plugin
    .pipe(gulp.dest('destination')) // Outputs the file in the destination folder
})

Comme vous le voyez, une tâche réelle prend deux méthodes gulp : gulp.src et gulp.dest.

gulp.src indique à la tâche gulp quels fichiers utiliser pour la tâche, tandis que gulp.dest indique où mettre les fichiers qui résultent de l’exécution de la tâche.

Nous allons réaliser une vraie tâche, de compilation de fichiers Sass en CSS.

Preprocessing avec Gulp

Nous pouvons compiler Sass en CSS avec Gulp, grâce à un plugin nommé gulp-sass. Vous pouvez installer gulp-sass dans votre projet via la commande npm install tout comme nous l’avons fait pour gulp, sans oublier le flag --save-dev qui ajoutera le plugin aux dépendances de développement dans package.json.

$ npm install gulp-sass --save-dev

Pour pouvoir l’utiliser, nous devons ensuite requérir (require) gulp-sass depuis le dossier node_modules où il a été installé, comme nous l’avons fait avec gulp.

//JS
var gulp = require('gulp');
// Requires the gulp-sass plugin
var sass = require('gulp-sass');

Nous pouvons utiliser gulp-sass en remplaçant aGulpPlugin() par sass(). Et puisque la tâche est censée compiler Sass en CSS, appelons-la sass.

//JS
gulp.task('sass', function(){
  return gulp.src('source-files')
    .pipe(sass())    // ici on utilise gulp-sass
    .pipe(gulp.dest('destination'))
});

Nous devons fournir des fichiers source ainsi qu’une destination, donc créons un fichier styles.scss dans le dossier app/scss. Ce fichier sera ajouté à la tâche sass dans gulp.src.

Nous voulons que le résultat éventuel styles.css se retrouve dans le dossier app/css qui sera donc la “destination” de gulp.dest.

//JS
gulp.task('sass', function(){
  return gulp.src('app/scss/styles.scss')
    .pipe(sass()) // Converts Sass to CSS with gulp-sass
    .pipe(gulp.dest('app/css'))
});

Pour tester notre tâche, nous allons ajouter un contenu Sass à notre fichier styles.scss.

//SCSS
// styles.scss
.testing {
  width: percentage(5/7);
}

Si nous lançons gulp sass dans la console, nous pouvons constater qu’un fichier styles.css a été créé dans app/css. De plus, son contenu est une valeur 71.4287% correspondant à l’évaluation de percentage(5/7).

//CSS
/* styles.css */
.testing {
  width: 71.42857%; 
}

Pour info : gulp-sass utilise LibSass pour convertir Sass en CSS. C’est beaucoup plus rapide que les méthodes basées sur Ruby, mais si vous le souhaitez vous pouvez utiliser ces dernières avec gulp-ruby-sass ou gulp-compass.

Parfois, nous avons besoin de compiler plus d’un fichier .scss en CSS. Nous pouvons le faire, en une seule fois, via les globs de Node.

Globbing avec Node

Les globs sont des patterns de correspondance qui fonctionnent un peu comme les regex mais spécifiquement pour les chemins d’accès.

La plupart des workflows de gulp feront appel à 4 globs seulement :

  1. *.scss : le pattern * est une sorte de “joker” qui correspond à n’importe quel pattern dans le dossier courant. Dans cet exemple, tous les fichiers ayant une extension .scss dans le dossier racine project.
  2. **/*.scss : c’est une version plus extrême du pattern précédent, qui cible tous les fichiers ayant une extension .scss dans le dossier racine et dans n’importe quel dossier enfant.
  3. !pas-moi.scss : le ! indique que Gulp doit exclure ce pattern, ce qui est utile si vous voulez exclure un fichier au sein d’un pattern. Dans notre exemple, le fichier pas-moi.scss sera exclu.
  4. *.+(scss|sass) : le plus + et les parenthèses () permettent à Gulp de cibler des patterns multiples, chaque pattern étant séparé des autres par un caractère “barre verticale” | (sur un clavier Mac, faire ⇧ + ⌥ + l). Dans notre exemple, Gulp ciblera tous les fichiers ayant une extension .scss ou .sass dans le dossier racine.

Nous pouvons maintenant remplacer app/scss/styles.scss par un pattern scss/**/*.scss qui matchera tous les fichiers ayant une extension .scss dans app/scss ou dans un dossier enfant.

//JS
gulp.task('sass', function() {
  return gulp.src('app/scss/**/*.scss') // Gets all files ending with .scss in app/scss and children directories
    .pipe(sass())
    .pipe(gulp.dest('app/css'))
})

Tout autre fichier Sass trouvé dans app/scss sera automatiquement inclus dans la tâche. Si vous ajoutez un fichier print.scss, vous verrez que print.css sera généré à l’intérieur de app/css.

print.css généré dans le dossier CSS
print.css a été ajouté par Gulp

Nous savons maintenant comment compiler tous les fichiers Sass avec une commande unique. Mais bon, ce travail manuel est un peu fastidieux, n’est-ce pas ? Heureusement, Gulp peut lancer cette tâche automatiquement à chaque fois qu’un fichier est sauvegardé, à travers un processus appelé “watching” (observation).

Observer les modifications de fichiers

Gulp propose une méthode watch qui suit les modifications de fichiers. La syntaxe de la méthode watch est la suivante :

//JS
gulp.watch('fichiers-a-suivre', ['tache1', 'tache2', 'tache3']); 

Si nous voulons observer tous les fichiers Sass et lancer la tâche sass à chaque fois qu’un fichier Sass est sauvegardé, il nous suffit de remplacer fichiers-a-suivre par app/scss/**/*.scss et ['tache1', 'tache2', 'tache3'] par ['sass'] :

//JS
gulp.watch('app/scss/**/*.scss', ['sass']);

Le plus souvent, nous voudrons observer plus d’un type de fichier à la fois. Pour cela, nous pouvons regrouper plusieurs processus dans une même tâche watch :

//JS
gulp.task('watch', function(){
  gulp.watch('app/scss/**/*.scss', ['sass']); 
  // autres observations
})

Si vous lancez la commande gulp watch maintenant, vous verrez que Gulp commence son observation immédiatement.

lancement de la commande watch

Dès que vous sauvegardez un fichier .scss Gulp lance automatiquement la tâche sass.

compilation immédiate

Allons plus loin et demandons à Gulp de rafraîchir le navigateur à chaque fois que nous sauvegardons un fichier .scss, grâce à Browser Sync.

Rafraîchissement en live avec Browser Sync

Browser Sync simplifie le développement web en créant un serveur web nous permettant un rafraîchissement live. Il offre par ailleurs d’autres fonctionnalités, par exemple la synchronisation sur plusieurs devices.

Installons d’abord Browser Sync :

$ npm install browser-sync --save-dev

Vous avez sans doute remarqué qu’il n’y a pas de préfixe gulp- : Browser Sync fonctionne avec Gulp, c’est pourquoi nous n’avons pas à utiliser de plugin. Il nous faut, comme d’habitude, requérir Browser Sync :

//JS
var browserSync = require('browser-sync');

Nous créons une tâche browserSync pour permettre la création d’un serveur, et nous indiquons à Browser Sync où doit se trouver la racine du serveur. Dans notre cas, c’est le dossier app :

//JS
gulp.task('browserSync', function() {
  browserSync({
    server: {
      baseDir: 'app'
    },
  })
})

Il nous faut modifier légèrement notre tâche sass afin que Browser Sync puisse injecter les nouveaux styles CSS (c’est à dire mettre à jour CSS) dans le navigateur à chaque fois que la tâche sass est lancée.

//JS
gulp.task('sass', function() {
  return gulp.src('app/scss/**/*.scss') // Gets all files ending with .scss in app/scss
    .pipe(sass())
    .pipe(gulp.dest('app/css'))
    .pipe(browserSync.reload({
      stream: true
    }))
});

Et voilà, la configuration de Browser Sync est terminée. Maintenant, nous devons lancer à la fois watch et browserSync en même temps pour un rafraîchissement en live.

Il serait évidemment lourd d’ouvrir deux consoles séparées pour lancer les deux tâches, donc demandons à Gulp de la lancer toutes les deux en indiquant que browserSync doit être achevée avant que watch soit lancée. Pour cela, on ajoute un deuxième argument à la tâche watch, en suivant cette syntaxe :

//JS
gulp.task('watch', ['array', 'of', 'tasks', 'to', 'complete','before', 'watch'], function (){
  // ...
})

Ici, nous ajoutons la tâche browserSync :

//JS
gulp.task('watch', ['browserSync'], function (){
  gulp.watch('app/scss/**/*.scss', ['sass']); 
  // Other watchers
})

Nous voulons également nous assurer que sass est lancé avant watch, de sorte que CSS sera toujours le dernier quelle que soit la commande lancée.

//JS
gulp.task('watch', ['browserSync', 'sass'], function (){
  gulp.watch('app/scss/**/*.scss', ['sass']); 
  // Other watchers
});

À présent, si vous lancez gulp watch, Gulp lancera les deux tâches sass et browserSync simultanément, et lorsqu’elles sont toutes deux achevées, watch est lancé.

l’ordre des tâches dans la console
Sass et browserSync commencent, puis watch quand elles sont terminées

En même temps, une fenêtre de navigateur pointant vers app/index.html s’ouvre. Si vous modifiez le fichier styles.scss, vous verrez que le navigateur est automatiquement rafraîchi.

image animée montrant le rafraîchissement du navigateur

Encore une chose avant de clore ce chapitre. Puisque nous observons déjà nos fichiers .scss pour le rafraîchissement, pourquoi ne pas faire de même avec les fichiers HTML ou JavaScript ?

Nous pouvons le faire en ajoutant deux processus watch et en appelant la fonction browserSync.reload lorsqu’un fichier est sauvegardé.

//JS
gulp.task('watch', ['browserSync', 'sass'], function (){
  gulp.watch('app/scss/**/*.scss', ['sass']); 
  // Reloads the browser whenever HTML or JS files change
  gulp.watch('app/*.html', browserSync.reload); 
  gulp.watch('app/js/**/*.js', browserSync.reload); 
});

Jusqu’ici dans ce tutoriel nous avons réalisé 3 choses :

  1. Créer un serveur pour le développement web
  2. Utiliser le préprocesseur Sass
  3. Rafraîchir le navigateur lorsqu’un fichier est sauvegardé

Passons maintenant à l’optimisation des ressources. Nous commencerons avec l’optimisation des fichiers CSS et JavaScript.



Optimiser les fichiers CSS et JavaScript

Optimiser CSS et JavaScript pour la production consiste essentiellement à minifier et concaténer les fichiers. Un des problèmes qui se posent lorsqu’on automatise ce process est l’ordre de concaténation. Admettons que nous ayons 3 scripts dans index.html :

//HTML
<body>
  <!-- other stuff -->
  <script src="js/lib/a-library.js"></script>
  <script src="js/lib/another-library.js"></script>
  <script src="js/main.js"></script>
</body>

Ces scripts proviennent de deux dossiers différents. Il sera difficile de les concaténer à l’aide de plugins traditionnels comme gulp-concatenate.

Heureusement, il existe un plugin Gulp bien pratique, gulp-useref, qui résout ce problème.

gulp-useref concatène tous les fichiers CSS ou JavaScript en un seul, via une syntaxe commençant par <!--build: et se terminant par <!--endbuild--> :

//HTML
<!-- build:<type> <path> -->
... HTML Markup, liste de scripts / liens
<!-- endbuild -->

<type> peut être js, css ou remove. Si vous utilisez ce dernier, Gulp éliminera le bloc de construction sans générer de fichier.

<path> correspond au chemin d’accès du fichier qui sera généré.

Dans l’exemple qui suit, nous voulons générer un fichier JavaScript main.min.js dans le dossier dist/js :

//HTML
<!--build:js js/main.min.js -->
<script src="js/lib/a-library.js"></script>
<script src="js/lib/another-library.js"></script>
<script src="js/main.js"></script>
<!-- endbuild -->

Configurons mainenant le plugin gulp-useref dans notre gulpfile. Nous devons d’abord installer le plugin...

$ npm install gulp-useref --save-dev

...et le requérir dans gulpfile.

//JS
var useref = require('gulp-useref');

Pour définir une tâche useref, on procède comme nous l’avons déjà fait, mais il nous faut appeler la fonction useref.assets() avant gulp.src comme ceci :

//JS
gulp.task('useref', function(){
  var assets = useref.assets();

  return gulp.src('app/*.html')
    .pipe(assets)
    .pipe(assets.restore())
    .pipe(useref())
    .pipe(gulp.dest('dist'))
});

Et maintenant, si vous lancez cette tâche useref, Gulp va chercher les 3 scripts et les concaténer dans dist/js/main.min.js.

un fichier unique dist/js/main.min.js

À ce stade toutefois le fichier n’est pas minifié. Pour ce faire, nous utilisons le plugin gulp-uglify :

$ npm install gulp-uglify --save-dev 

puis :

//JS
// Les autres requires...
var uglify = require('gulp-uglify');

gulp.task('useref', function(){
  var assets = useref.assets();

  return gulp.src('app/*.html')
    .pipe(assets)
    .pipe(uglify()) // pour minifier les fichiers Javascript
    .pipe(assets.restore())
    .pipe(useref())
    .pipe(gulp.dest('dist'))
});

Gulp minifiera automatiquement le fichier main.min.js à chaque fois que vous lancerez la tâche useref.

Une dernière chose sympa avec gulp-useref est qu’il modifie automatiquement tous les scripts situés à l’intérieur de <!--build: et <!--endbuild--> en un seul fichier JavaScript pointant vers js/main.min.js.

modification du html avec useref
À gauche le HTML avant useref, à droite le HTML après useref

Génial, non ?

Nous pouvons utiliser la même méthode pour concaténer et minifier nos fichiers CSS (si vous en avez plus d’un). Nous suivront le même processus, consistant à ajouter un commentaire build :

//HTML
<!--build:css css/styles.min.css-->
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/another-stylesheet.css">
<!--endbuild-->

La tâche useref doit être légèrement modifiée puisque nous avons maintenant des fichiers CSS. Nous ne voulons pas lancer uglify() sur les fichiers CSS parce que ça ne va pas leur faire de bien... Donc nous voulons qu’uglify ne minifie que les fichiers JavaScript, et pour cela nous allons faire appel au plugin gulp-if.

$ npm install gulp-if --save-dev

...qui nous permet d’indiquer une condition comme suit :

//JS
// Les autres requires...
var gulpIf = require('gulp-if');

gulp.task('useref', function(){
  var assets = useref.assets();

  return gulp.src('app/*.html')
    .pipe(assets)
    // Minifie seulement les fichiers Javascript
    .pipe(gulpIf('*.js', uglify()))
    .pipe(assets.restore())
    .pipe(useref())
    .pipe(gulp.dest('dist'))
});

Nous pouvons minifier les fichiers CSS avec gulp-useref également, mais nous utilisons le plugin gulp-minify-css pour cela.

$ npm install gulp-minify-css

puis :

//JS
var minifyCSS = require('gulp-minify-css');

gulp.task('useref', function(){
  var assets = useref.assets();

  return gulp.src('app/*.html')
    .pipe(assets)
    // Minifie seulement les fichiers CSS
    .pipe(gulpIf('*.css', minifyCSS()))
    // Minifie seulement les fichiers Javascript
    .pipe(gulpIf('*.js', uglify()))
    .pipe(assets.restore())
    .pipe(useref())
    .pipe(gulp.dest('dist'))
});

À présent, à chaque fois que vous lancerez la tâche useref, vous aurez un fichier CSS optimisé et un fichier JavaScript optimisé.

Passons maintenant à l’optimisation des images.

Optimiser les images

Comme vous l’avez sans doute déjà deviné, nous allons utiliser le plugin gulp-imagemin.

$ npm install gulp-imagemin --save-dev

puis :

//JS
var imagemin = require('gulp-imagemin');

Avec gulp-imagemin, nous pouvons minifier les png, les jpg, les gif et même les svg. Créons une tâche images à cet effet :

//JS
gulp.task('images', function(){
  return gulp.src('app/images/**/*.+(png|jpg|gif|svg)')
  .pipe(imagemin())
  .pipe(gulp.dest('dist/images'))
});

Du fait que différents types de fichiers peuvent être optimisés différemment, vous aurez peut être besoin d’ajouter des options à imagemin pour personnaliser l’optimisation.

Par exemple, vous pouvez créer des GIFs entrelacés (technique d’affichage progressif d’une image matricielle) via l’option interlaced réglée sur true.

//JS
gulp.task('images', function(){
  return gulp.src('app/images/**/*.+(png|jpg|jpeg|gif|svg)')
  .pipe(imagemin({
      interlaced: true
    }))
  .pipe(gulp.dest('dist/images'))
});

Vous pouvez explorer les autres options pour approfondir le sujet. L’optimisation des images est un processus assez long et il est souhaitable de ne pas le répéter lorsque ce n’est pas nécessaire. Pour cela, nous utiliserons le plugin gulp-cache.

$ npm install gulp-cache --save-dev

puis :

//JS
var cache = require('gulp-cache');

gulp.task('images', function(){
  return gulp.src('app/images/**/*.+(png|jpg|jpeg|gif|svg)')
  // Met en cache les images passées par imagemin
  .pipe(cache(imagemin({
      interlaced: true
    })))
  .pipe(gulp.dest('dist/images'))
});

Nous avons presque terminé le processus d’optimisation. Il nous reste un dossier à transférer de app vers dist, c’est le dossier des polices de caractères (fonts).

Copier les fonts vers dist

Les fonts sont déjà optimisées, donc nous n’avons rien de particulier à faire. Il nous faut juste les copier dans dist.

Nous pouvons copier des fichiers avec Gulp sans plugin, simplement en spécifiant les gulp.src et gulp.dest.

//JS
gulp.task('fonts', function() {
  return gulp.src('app/fonts/**/*')
  .pipe(gulp.dest('dist/fonts'))
})

Gulp copiera fonts depuis app vers dist à chaque fois que vous lancerez la tâche fonts.

copie de fonts
Les polices de caractères sont copiées depuis app/fonts vers dist/fonts

Nous avons 6 tâches différentes dans notre gulpfile et chacune doit être appelée individuellement dans la console, ce qui n’est vraiment pas pratique. Nous allons voir comment lier ces tâches dans une seule commande, mais avant cela, voyons comment nous pourrions nettoyer automatiquement les fichiers générés.

Nettoyer automatiquement les fichiers générés

Comme nous générons des fichiers de manière automatique, nous voulons nous assurer que les fichiers inutiles ne traînent pas quelque part. C’est ce qu’on appelle le nettoyage (ou plus simplement la suppression de fichiers).

On utilise le plugin del (delete) pour ce nettoyage.

npm install del --save-dev

puis :

//JS
var del = require('del');

La fonction del prend un tableau (array) de globs Node qui lui indiquent les dossiers à supprimer.

C’est une tâche à peu près aussi simple que notre “hello” initial :

//JS
gulp.task('clean', function() {
  del('dist');
})

Voilà, maintenant Gulp supprimera le dossier dist à chaque fois que vous lancerez la tâche clean (nettoyer).

Cependant, comme nous l’avons déjà dit, nous voulons éviter d’avoir à relancer la tâche images car elle prend beaucoup de temps. Nous allons créer une tâche de nettoyage distincte, que nous utiliserons lorsque nous voulons tout nettoyer sauf les images dans dist.

//JS
gulp.task('clean:dist', function(callback){
  del(['dist/**/*', '!dist/images', '!dist/images/**/*'], callback)
});

Tout d’abord, nous supprimons tous les fichiers situés dans dist/**/*. Toutefois nous ne voulons pas supprimer dist/images, d’où l’identifiant ! devant dist/images. De plus, nous ne voulons pas supprimer quoi que ce soit à l’intérieur de dist/images, d’où le troisième glob !dist/images/**/*.

Enfin, si nous voulons savoir quand la tâche clean:dist est achevée, nous devons fournir un argument de callback.

Par exemple, dans la tâche clean standard, nous allons nous assurer que le cache de Gulp est effacé afin de pouvoir optimiser à nouveau toutes nos images.

//JS
gulp.task('clean', function(callback) {
  del('dist');
  return cache.clearAll(callback);
})

Et maintenant, combinons toutes nos tâches !

Combiner les tâches Gulp

Résumons : jusqu’ici, nous avons créé deux ensembles distincts de tâches Gulp.

Le premier est un process de développement dans lequel nous compilons Sass en CSS, nous observons les modifications et nous rafraîchissons le navigateur en fonction des changements.

Le deuxième est un process d’optimisation grâce auquel nos fichiers sont prêts pour la production. Nous avons optimisé des fichiers CSS, JavaScript, images et nous avons copié nos fonts depuis app vers dist.

Nous avons déjà regroupé le premier ensemble de tâches dans un workflow simple avec la commande gulp watch :

//JS
gulp.task('watch', ['browserSync', 'sass'], function (){
  // ... watchers
})

Le deuxième ensemble est constitué de tâches que nous devons lancer pour créer le site en production. Parmi elles, clean:dist, sass, useref, images et fonts.

Dans le même ordre d’idées, nous pourrions créer une tâche build qui enchaînerait tout cela :

//JS
gulp.task('build', ['clean', 'sass', 'useref', 'images', 'fonts'], function (){
  console.log('Building files');
})

Malheureusement, nous ne pouvons pas écrire ainsi la tâche build car Gulp active simultanément toutes les tâches comprises dans le second argument. On pourrait imaginer que useref, images ou même fonts soient achevées avant clean, ce qui aurait pour conséquence que le dossier dist serait supprimé.

Pour assurer un bon ordre des tâches, nous devons utiliser le plugin Run Sequence.

$ npm install run-sequence --save-dev

Voici la syntaxe d’une suite de tâches réalisées avec Run Sequence :

//JS
var runSequence = require('run-sequence');

gulp.task('nom-de-la-tache', function(callback) {
  runSequence('tache1', 'tache2', 'tache3', callback);
});

Lorsque nom-de-la-tache est appelée, Gulp lance d’abord la tache1. Lorsque celle-ci est achevée, Gulp lance automatiquement la tache2 et, quand celle-ci est terminée, la tache3.

Run Sequence vous permet également de lancer plusieurs tâches simultanément si vous les placez dans un array :

//JS
gulp.task('nom-de-la-tache', function(callback) {
  runSequence('tache1', ['tache2','en','parallele'], 'tache3', callback);
});

Dans ce cas, Gulp lance d’abord tache1. Lorsque celle-ci est achevée, Gulp lance toutes les tâches du second argument simultanément (tache2, en, et parallele), et c’est seulement lorsque toutes ces tâches sont terminées qu’il lancera finalement la tache3.

Donc, nous pouvons maintenant créer une tâche qui nous garantit que clean:dist sera lancé d’abord, suivi des autres tâches.

//JS
gulp.task('build', function (callback) {
  runSequence('clean:dist', 
  ['sass', 'useref', 'images', 'fonts'],
  callback
  )
})

Pour la cohérence, nous pouvons construire la même séquence avec le premier groupe. Appelons cette tâche default :

//JS
gulp.task('default', function (callback) {
  runSequence(['sass','browserSync', 'watch'],
  callback
  )
})

Pourquoi default ? parce que lorsqu’une tâche s’appelle default, il suffit de taper gulp dans la console, c’est une économie de temps.

Vous pouvez retrouver tout le travail que nous venons de réaliser sur ce repo github.

Récapitulation

Nous avons passé en revue les bases de Gulp et créé un workflow nous permettant de compiler Sass en CSS tout en suivant les modifications de fichiers HTML et JS. Nous pouvons lancer cette tâche via la commande gulp dans la console.

Nous avons construit une deuxième tâche, build, qui crée un dossier dist pour le site en production. Nous avons compilé Sass en CSS, optimisé nos ressources et copié les fichiers nécessaires dans le dossier dist. Pour lancer cette tâche, il nous suffit de taper gulp build dans la console.

Enfin, nous avons une tâche clean qui supprime du dossier dist tous les caches d’images créées, ce qui nous permet de supprimer les vieux fichiers que nous aurions laissés dans dist par inadvertance.

Nous avons créé un workflow robuste qui sera suffisant pour la plupart des développeurs web. Il y a bien sûr beaucoup d’autres choses dans Gulp et bien d’autres workflows que nous pouvons explorer pour améliorer encore ce process. Voici quelques idées :

Pour le développement :

Pour l’optimisation :

  • Supprimer le CSS non utilisé avec unCSS
  • Optimiser plus encore les fichiers CSS avec gulp-csso (voir les explications dans la documentation)
  • Générer du CSS en ligne pour améliorer la performance du site avec Critical

Par ailleurs, vous pouvez ajouter des unités de tests JS avec gulp-jasmine et même déployer automatiquement votre dossier dist vers votre serveur de production via gulp-rsync.

Comme vous le voyez, même si notre workflow fait déjà pas mal de choses, il y a encore de la marge pour faire bien plus. C’est pourquoi j’ai écrit un livre sur l’automatisation de votre workflow et je vous invite à en télécharger 10 chapitre gratuitement si vous êtes intéressés. Vous pouvez également me contacter, je serai heureux de vous répondre !


Intéressé par les outils de développement ? Retrouvez une liste des meilleurs articles et ressources du web.

Tous les articles sur les workflows parus dans la Cascade.

Du même auteur, dans la Cascade :

Articles sur les mêmes thèmes dans la Cascade :


Ressources complémentaires en français

Ressources complémentaires en anglais




original paru le dans CSS-Tricks.

Sur l’auteur : est développeur et designer, il s’est formé lui-même à l’âge de 25 ans. On peut le suivre sur Twitter et sur son blog.

Traduit avec l’aimable autorisation de CSS-Tricks et de l’auteur.
Copyright CSS-Tricks © 2015.