I. Qu'est-ce qu'un tasks runner ?▲
Un tasks runner (ou automatiseur de tâches) permet entre autres de lancer une suite de tâches fastidieuses à faire à la main. Celles-ci peuvent être de la transpilation de Typescript vers JavaScript, SASS pour convertir des fichiers scss vers css, déplacement de fichier, concaténation, etc. Vous serez tous d'accord que de lancer ces outils les uns après les autres via une ligne de commande n'est pas ce qu'il y a de plus simple à faire. Ces tâches peuvent être automatisées et documentées grâce à un Tasks runner. En gros, grâce à cela nous pouvons créer notre builder personnalisé.
II. Pourquoi Gulp ?▲
Pour faire court, contrairement à d'autres tasks runner, Gulp fonctionne avec des flux de données (streams) ce qui évite de trop travailler sur des fichiers et augmente donc considérablement la rapidité d'exécution des tâches. Ensuite, Gulp propose plus de 2000 plugins, en principe, il est relativement simple de trouver son bonheur.
Petit plus : le fichier de conf. est un script JS et non un JSON, objectivement cela ne représente pas un atout par rapport aux autres, mais j'ai une préférence pour Gulp à ce niveau.
III. Posons des bases solides▲
Voici les premières lignes de notre fichier « gulpfile.js » :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
//Gulp modules
var gulp =
require
(
'gulp'
),
connect =
require
(
'gulp-connect'
),
history
=
require
(
'connect-history-api-fallback'
),
open
=
require
(
'gulp-open'
),
sass =
require
(
'gulp-sass'
),
uglify =
require
(
'gulp-uglify'
),
rename =
require
(
'gulp-rename'
),
jsonminify =
require
(
'gulp-jsonminify'
),
minifyhtml =
require
(
'gulp-minify-html'
),
plumber =
require
(
'gulp-plumber'
),
concat =
require
(
'gulp-concat'
),
inject =
require
(
'gulp-inject'
),
runSequence =
require
(
'run-sequence'
),
watch =
require
(
'gulp-watch'
);
Dans ces lignes, nous affectons les plugins à des variables afin de pouvoir les utiliser de manière simple (c'est la méthode la plus courante avec Gulp).
2.
3.
4.
5.
//Config
var environment =
'local'
,
//(local|dev|prod)
bowerFolder =
'source/bower_components'
host =
'localhost'
,
port =
'8080'
;
Ici, nous paramétrons les valeurs par défaut de la configuration nécessaire au bon fonctionnement des tâches :
- environment : l'environnement dans lequel nous allons compiler (par défaut local) ;
- bowerFolder : le dossier bower contenant toutes nos dépendances ;
- host : l'hôte utilisé en local (pour notre serveur web entre autres) ;
- port : le numéro de port utilisé pour notre serveur local.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
//Datas
var htmlFiles =
[
'source/shared/**/*.html'
,
'source/components/**/*.html'
,
'source/modules/**/*.html'
],
sassFiles =
[
'source/sass/**/*.scss'
,
'source/components/**/*.scss'
,
'source/shared/**/*.scss'
,
'source/modules/**/*.scss'
],
jsFiles =
[
'source/app.module.js'
,
'source/app.config.js'
,
'source/components/**/*.js'
,
'source/services/**/*.js'
,
'source/shared/**/*.js'
,
'source/modules/**/*.module.js'
,
'source/modules/**/*.js'
];
Là, nous récupérons les paths des fichiers source que nous allons traiter :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
//Gulp tasks
gulp.task
(
'connectTask'
,
connectTask);
gulp.task
(
'openTask'
,
openTask);
gulp.task
(
'sassTask'
,
sassTask);
gulp.task
(
'jsTask'
,
jsTask);
gulp.task
(
'jsonTask'
,
jsonTask);
gulp.task
(
'htmlTask'
,
htmlTask);
gulp.task
(
'bowerTask'
,
bowerTask);
gulp.task
(
'assetsTask'
,
assetsTask);
gulp.task
(
'injectTask'
,
injectTask);
gulp.task
(
'watch'
,
watchTask);
gulp.task
(
'local'
,
localTask);
gulp.task
(
'dev'
,
devTask);
gulp.task
(
'recette'
,
recetteTask);
gulp.task
(
'prod'
,
prodTask);
gulp.task
(
'default'
,
[
'local'
]
);
Enfin, nous affectons les tâches à Gulp. Le premier paramètre de chaque méthode « task » représente le nom de la tâche, le deuxième paramètre est la tâche en elle-même (qui est une fonction que nous détaillerons plus bas) excepté pour la tâche « default » qui est un tableau de string représentant le nom d'une tâche à exécuter (en l'occurrence la tâche « local »).
IV. Les Tâches▲
IV-A. connectTask▲
Le plugin « Gulp connect » va nous permettre de créer un serveur web en local. J'ai préféré utiliser celui-ci, car il propose une option livereload (qui va exécuter une tâche automatiquement après avoir modifié un fichier) ainsi qu'une méthode middleware (qui peut être utile si on veut créer un proxy par exemple).
« Connect history api fallback » nous donnera la possibilité d'utiliser l'API « history » de HTML 5 sur notre serveur, nécessaire dans une « Single page application » comme la nôtre utilisant cette option.
Passons au script :
Afin de lancer le serveur, il suffit d'appeler la méthode « server » et de lui fournir quelques paramètres :
- host : représentant l'hôte qui sera utilisé (ici : localhost) ;
- port : le port utilisé (8080) ;
- root : le dossier racine (par défaut : local) ;
- livereload : si on veut ou non activer le livereload ;
- middleware : c'est ici que nous insérons notre « Connect history api fallback ».
IV-B. openTask▲
Le plugin « Gulp open » va tout simplement nous permettre de lancer un navigateur internet automatiquement avec une URL prédéfinie :
IV-C. SassTask▲
Là nous allons pouvoir compiler nos fichiers .scss en css et les minifier en fonction de l'environnement en utilisant le plugin « Gulp sass »
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
function sassTask
(
) {
var parameters =
{};
if(
environment ==
'prod'
)
parameters.
outputStyle =
'compressed'
;
return gulp
.src
([
'source/modules/**/*.scss'
,
'source/shared/**/*.scss'
,
'source/components/**/*.scss'
,
'source/sass/_fonts.scss'
,
'source/sass/_global.scss'
,
'source/sass/screen.scss'
]
)
.pipe
(
concat
(
'screen.scss'
))
.pipe
(
plumber
(
))
.pipe
(
sass
(
parameters).on
(
'error'
,
sass.
logError))
.pipe
(
plumber.stop
(
))
.pipe
(
gulp.dest
(
environment +
'/css'
))
.pipe
(
connect.reload
(
));
}
Par ailleurs, si nous sommes en environnement de prod, il est d'usage de minifier les fichiers, donc nous ajoutons l'attribut « outputStyle » avec pour valeur « compressed ».
Dans un premier temps, nous allons récupérer tous les fichiers .scss et les concaténer dans une seule et même ressource grâce au plugin Gulp concat, puis Gulp sass va la convertir en css (et le minifier selon l'environnement). « gulp.dest » va écrire les données dans un fichier et dans le bon environnement. « connect.reload » sera lancé chaque fois que cette tâche sera exécutée ce qui va permettre au livereload d'actualiser la page dans notre navigateur.
Nous pouvons aussi constater un autre plugin utilisé : « plumber ». Ce plugin évite de faire planter l'exécution de gulp en cas d'erreur.
IV-D. jsTask▲
« Gulp uglify » va nous permettre d'« uglifier » notre JS.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
function jsTask
(
) {
var task =
gulp
.src
(
jsFiles)
.pipe
(
concat
(
'app.js'
));
if(
environment ==
'prod'
) {
task
.pipe
(
plumber
(
))
.pipe
(
uglify
(
))
.pipe
(
plumber.stop
(
))
.pipe
(
rename
(
function(
path) {
path.
basename +=
'.min'
;
}
))
}
return task
.pipe
(
gulp.dest
(
environment +
'/app'
))
.pipe
(
connect.reload
(
));
}
D'abord comme pour la tâche sass, nous récupérons tous nos fichiers sources JS pour les concaténer dans une seule ressource, puis selon notre environnement nous allons uglifier celle-ci pour enfin les retranscrire dans un fichier.
IV-E. bowerTask▲
Cette tâche va récupérer toutes les dépendances pour les placer au bon endroit dans notre environnement.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
function bowerTask
(
) {
var bowerComponents =
[
bowerFolder +
'/angular/angular.min.js'
,
bowerFolder +
'/angular-route/angular-route.min.js'
,
bowerFolder +
'/angular/angular.min.js.map'
,
bowerFolder +
'/angular-route/angular-route.min.js.map'
];
return gulp
.src
(
bowerComponents)
.pipe
(
gulp.dest
(
environment +
'/vendor'
));
}
IV-F. assetsTask▲
Ici, nous allons copier toutes nos images, polices et icônes dans un endroit dédié au sein de l'environnement.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
function assetsTask
(
) {
//Favicons
gulp.src
(
'source/assets/favicon/*'
)
.pipe
(
gulp.dest
(
environment +
'/favicon'
));
//Fonts
gulp.src
(
'source/assets/fonts/**/*'
)
.pipe
(
gulp.dest
(
environment +
'/fonts'
));
//Images
return gulp.src
(
'source/assets/images/**/*'
)
.pipe
(
gulp.dest
(
environment +
'/img'
));
}
IV-G. injectTask▲
« Gulp inject » nous permettra d'injecter dans la page index.html les paths vers les fichiers compilés. Je vous laisse voir la page du plugin pour plus d'informations
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
function injectTask
(
) {
var css =
environment +
'/css/screen.css'
,
app = (
environment ==
'prod'
) ?
environment +
'/app/app.min.js'
:
environment +
'/app/app.js'
,
bower =
[
environment +
'/vendor/angular.min.js'
,
environment +
'/vendor/angular-route.min.js'
,
];
return gulp
.src
(
'source/index.html'
)
.pipe
(
gulp.dest
(
environment))
.pipe
(
inject
(
gulp.src
(
css,
{
read
:
false}
),
{
name
:
'css'
,
relative
:
true,
addRootSlash
:
true}
))
.pipe
(
inject
(
gulp.src
(
app,
{
read
:
false}
),
{
name
:
'app'
,
relative
:
true,
addRootSlash
:
true}
))
.pipe
(
inject
(
gulp.src
(
bower,
{
read
:
false}
),
{
name
:
'vendor'
,
relative
:
true,
addRootSlash
:
true}
))
.pipe
(
gulp.dest
(
environment))
.pipe
(
connect.reload
(
));
}
IV-H. jsonTask▲
Le plugin « Gulp jsonminify » va minifier nos fichiers JSON (dans notre cas « locale-fr_FR.json, articles.json et news.json »).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
function jsonTask
(
) {
gulp
.src
(
'source/data/*.json'
)
.pipe
(
plumber
(
))
.pipe
(
jsonminify
(
))
.pipe
(
plumber.stop
(
))
.pipe
(
gulp.dest
(
environment +
'/data'
))
.pipe
(
connect.reload
(
))
return gulp
.src
(
'source/resources/*.json'
)
.pipe
(
plumber
(
))
.pipe
(
jsonminify
(
))
.pipe
(
plumber.stop
(
))
.pipe
(
gulp.dest
(
environment +
'/resources'
))
.pipe
(
connect.reload
(
));
}
Nous récupérons nos JSON, puis nous les minifions et enfin nous les plaçons au bon endroit dans notre environnement.
IV-I. htmlTask▲
Dans cette tâche, nous allons utiliser le plugin « Gulp minifyhtml » nous permettant de minifier tous nos fichiers .html.
IV-J. watchTask▲
Gulp offre par défaut un plugin « watch » mais celui-ci peut planter en cas d'erreur sur le code, c'est pourquoi j'ai préféré utiliser le plugin « Gulp watch ».
2.
3.
4.
5.
6.
7.
8.
function watchTask
(
) {
watch
(
jsFiles,
jsTask);
watch
(
sassFiles,
sassTask);
watch
(
'source/resources/*.json'
,
jsonTask);
watch
(
htmlFiles,
htmlTask);
watch
(
jsFiles,
jsTask);
watch
(
'source/index.html'
,
injectTask);
}
Il suffit de lui donner en paramètres les fichiers à surveiller et la tâche à effectuer.
IV-K. runAllTasks▲
Là encore j'ai préféré utiliser le plugin « Run sequence » afin de lancer les tâches dans un ordre bien précis. En effet, ici la tâche « injectTask » était lancée en même temps que les autres, mais pour fonctionner correctement, il faut que les tâches « sassTask », « jsTask » et « bowerTask » soient terminées.
2.
3.
function runAllTasks
(
) {
runSequence
(
'bowerTask'
,
'jsTask'
,
'sassTask'
,
'jsonTask'
,
'htmlTask'
,
'assetsTask'
,
injectTask);
}
On lui donne en paramètres les tâches que l'on souhaite exécuter en premier puis celles qu'on a besoin d'exécuter ensuite.
IV-L. localTask, devTask et prodTask▲
Ce sont les tâches qui seront appelées avec le terminal, celles qui vont lancer toutes les autres tâches. Les fichiers compilés seront respectivement dans les dossiers « local », « dev » ou « prod »
2.
3.
4.
5.
6.
7.
8.
function localTask
(
) {
environment =
'local'
;
runAllTasks
(
);
connectTask
(
);
openTask
(
);
watchTask
(
);
}
On met l'environnement en mode local et en plus des tâches standards, on lance le serveur web, le navigateur et le watch. Il suffira de taper dans le terminal « gulp local » ou « gulp » (comme on est en local par défaut).
Pour les tâches « devTask » et « prodTask » on met l'environnement en mode développement ou production et on lance les tâches standards.
IV-M. Récapitulatif▲
En gros voici ce que fait Gulp :
- il récupère toutes les dépendances (fournies par Bower) pour les mettre dans un dossier disponible par le serveur ;
- il va prendre tous les fichiers script de notre application afin de les concaténer, les uglyfier si l'on est en production et les placer sur le serveur ;
- il exécute SASS afin de convertir les fichiers scss en css, les concaténer et les placer au bon endroit sur le serveur ;
- il minifie les fichiers JSON et les place sur le serveur ;
- il minifie les vues HTLM et les place sur le serveur ;
- il récupère tous les assets (images, fonts, etc.) pour les placer sur le serveur ;
- il injecte toutes les dépendances (application, bowers, css…) dans le fichier index.html et le déplace à la racine du serveur ;
- en local il va lancer un serveur web avec un livereload, lancer un navigateur avec la page de l'application, puis lancer un watch sur les fichiers afin de lancer les tâches concernées et notifier le livereload qu'il y a eu du changement.
Afin d'exécuter ce Gulp, il existe trois commandes, mettez-vous à la racine du projet à l'aide de votre terminal et tapez gulp pour lancer une tâche pour travailler en local, gulp dev pour faire un build pour un environnement de dev ou gulp prod pour un environnement de production.
V. Conclusion▲
Dans une époque où les applications évoluent rapidement et où les performances priment, ce script m'a permis de garder une organisation claire et facile à maintenir ou à faire évoluer, et de générer des applications légères et rapides à exécuter.
Il n'y a pas de manière unique pour configurer ses tâches Gulp. Celle que je viens de présenter est la mienne et m'a fait gagner un temps précieux sur mes projets. Cependant, JavaScript évolue rapidement et ne nous a pas encore révélé tout son potentiel. D'autres tasks runner commencent à faire parler d'eux, je pense notamment à WebPack qui apporte une approche différente pour packager ses applications.
VI. Remerciements▲
Nous tenons à remercier la société Soat qui nous a autorisés à publier ce tutoriel.
Nous remercions également Winjerome pour la mise au gabarit et Claude Leloup pour la relecture orthographique.