Tutoriel pour apprendre à bien organiser son projet Angular : Exécution !

Nicolas GARIN (Soat)

Désormais, nous avons notre projet Angular avec notre belle organisation. Cependant, vous noterez qu'il nous est impossible de le lancer en l'état. Dans cet article nous expliquerons notre script puis nous listerons les tâches et détaillerons les plugins Gulp utilisés pour ce projet.

Commentez

- Titre : Tutoriel pour apprendre à bien organiser son projet Angular : Exécution !

- Auteur : Nicolas GARIN (Soat)

- Parution : 1 février 2017

- Licence : Copyright ® 2016 Nicolas GARIN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Qu'est-ce qu'un tasks runner ?

- II. Pourquoi Gulp ?

- III. Posons des bases solides

- IV. Les Tâches

- IV-A. connectTask

- IV-B. openTask

- IV-C. SassTask

- IV-D. jsTask

- IV-E. bowerTask

- IV-F. assetsTask

- IV-G. injectTask

- IV-H. jsonTask

- IV-I. htmlTask

- IV-J. watchTask

- IV-K. runAllTasks

- IV-L. localTask, devTask et prodTask

- IV-M. Récapitulatif

- V. Conclusion

- VI. Remerciements

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 » :

1. //Gulp modules

2. var gulp = require('gulp'),

3. connect = require('gulp-connect'),

4. history = require('connect-history-api-fallback'),

5. open = require('gulp-open'),

6. sass = require('gulp-sass'),

7. uglify = require('gulp-uglify'),

8. rename = require('gulp-rename'),

9. jsonminify = require('gulp-jsonminify'),

10. minifyhtml = require('gulp-minify-html'),

11. plumber = require('gulp-plumber'),

12. concat = require('gulp-concat'),

13. inject = require('gulp-inject'),

14. runSequence = require('run-sequence'),

15. 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).

1. //Config

2. var environment = 'local', //(local|dev|prod)

3. bowerFolder = 'source/bower_components'

4. host = 'localhost',

5. 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.

1. //Datas

2. var htmlFiles = [

3. 'source/shared/**/*.html',

4. 'source/components/**/*.html',

5. 'source/modules/**/*.html'

6. ],

7. sassFiles = [

8. 'source/sass/**/*.scss',

9. 'source/components/**/*.scss',

10. 'source/shared/**/*.scss',

11. 'source/modules/**/*.scss'

12. ],

13. jsFiles = [

14. 'source/app.module.js',

15. 'source/app.config.js',

16. 'source/components/**/*.js',

17. 'source/services/**/*.js',

18. 'source/shared/**/*.js',

19. 'source/modules/**/*.module.js',

20. 'source/modules/**/*.js'

21. ];

Là, nous récupérons les paths des fichiers source que nous allons traiter :

1. //Gulp tasks

2. gulp.task('connectTask', connectTask);

3. gulp.task('openTask', openTask);

4. gulp.task('sassTask', sassTask);

5. gulp.task('jsTask', jsTask);

6. gulp.task('jsonTask', jsonTask);

7. gulp.task('htmlTask', htmlTask);

8. gulp.task('bowerTask', bowerTask);

9. gulp.task('assetsTask', assetsTask);

10. gulp.task('injectTask', injectTask);

11. gulp.task('watch', watchTask);

12. gulp.task('local', localTask);

13. gulp.task('dev', devTask);

14. gulp.task('recette', recetteTask);

15. gulp.task('prod', prodTask);

16. 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 :

1. function connectTask() {

2. var parameters = {

3. host: host,

4. port: port,

5. root: environment,

6. livereload: true,

7. middleware: function() {

8. return [history()];

9. }

10. };

12. connect.server(parameters);

13. }

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 :

1. function openTask() {

2. var parameters = {

3. uri: 'http://' + host + ':' + port

4. }

6. return gulp

7. .src(__filename)

8. .pipe(open(parameters));

9. }

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 »

1. function sassTask() {

2. var parameters = {};

4. if(environment == 'prod')

5. parameters.outputStyle = 'compressed';

7. return gulp

8. .src([

9. 'source/modules/**/*.scss',

10. 'source/shared/**/*.scss',

11. 'source/components/**/*.scss',

12. 'source/sass/_fonts.scss',

13. 'source/sass/_global.scss',

14. 'source/sass/screen.scss'

15. ])

16. .pipe(concat('screen.scss'))

17. .pipe(plumber())

18. .pipe(sass(parameters).on('error', sass.logError))

19. .pipe(plumber.stop())

20. .pipe(gulp.dest(environment + '/css'))

21. .pipe(connect.reload());

22. }

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.

1. function jsTask() {

2. var task = gulp

3. .src(jsFiles)

4. .pipe(concat('app.js'));

6. if(environment == 'prod') {

7. task

8. .pipe(plumber())

9. .pipe(uglify())

10. .pipe(plumber.stop())

11. .pipe(rename(function(path) {

12. path.basename += '.min';

13. }))

14. }

16. return task

17. .pipe(gulp.dest(environment + '/app'))

18. .pipe(connect.reload());

19. }

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.

1. function bowerTask() {

2. var bowerComponents = [

3. bowerFolder + '/angular/angular.min.js',

4. bowerFolder + '/angular-route/angular-route.min.js',

5. bowerFolder + '/angular/angular.min.js.map',

6. bowerFolder + '/angular-route/angular-route.min.js.map'

7. ];

9. return gulp

10. .src(bowerComponents)

11. .pipe(gulp.dest(environment + '/vendor'));

12. }

IV-F - assetsTask

Ici, nous allons copier toutes nos images, polices et icônes dans un endroit dédié au sein de l'environnement.

1. function assetsTask() {

2. //Favicons

3. gulp.src('source/assets/favicon/*')

4. .pipe(gulp.dest(environment + '/favicon'));

5. //Fonts

6. gulp.src('source/assets/fonts/**/*')

7. .pipe(gulp.dest(environment + '/fonts'));

8. //Images

9. return gulp.src('source/assets/images/**/*')

10. .pipe(gulp.dest(environment + '/img'));

11. }

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

1. function injectTask() {

2. var css = environment + '/css/screen.css',

3. app = (environment == 'prod') ? environment + '/app/app.min.js' : environment + '/app/app.js',

4. bower = [

5. environment + '/vendor/angular.min.js',

6. environment + '/vendor/angular-route.min.js',

7. ];

9. return gulp

10. .src('source/index.html')

11. .pipe(gulp.dest(environment))

12. .pipe(inject(gulp.src(css, {read: false}), {name: 'css', relative: true, addRootSlash: true}))

13. .pipe(inject(gulp.src(app, {read: false}), {name: 'app', relative: true, addRootSlash: true}))

14. .pipe(inject(gulp.src(bower, {read: false}), {name: 'vendor', relative: true, addRootSlash: true}))

15. .pipe(gulp.dest(environment))

16. .pipe(connect.reload());

17. }

IV-H - jsonTask

Le plugin « Gulp jsonminify » va minifier nos fichiers JSON (dans notre cas « locale-fr_FR.json, articles.json et news.json »).

1. function jsonTask() {

2. gulp

3. .src('source/data/*.json')

4. .pipe(plumber())

5. .pipe(jsonminify())

6. .pipe(plumber.stop())

7. .pipe(gulp.dest(environment + '/data'))

8. .pipe(connect.reload())

10. return gulp

11. .src('source/resources/*.json')

12. .pipe(plumber())

13. .pipe(jsonminify())

14. .pipe(plumber.stop())

15. .pipe(gulp.dest(environment + '/resources'))

16. .pipe(connect.reload());

17. }

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.

1. function htmlTask() {

2. return gulp

3. .src(htmlFiles)

4. .pipe(plumber())

5. .pipe(minifyhtml())

6. .pipe(plumber.stop())

7. .pipe(rename({dirname: ''}))

8. .pipe(gulp.dest(environment + '/partials'))

9. .pipe(connect.reload());

10. }

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 ».

1. function watchTask() {

2. watch(jsFiles, jsTask);

3. watch(sassFiles, sassTask);

4. watch('source/resources/*.json', jsonTask);

5. watch(htmlFiles, htmlTask);

6. watch(jsFiles, jsTask);

7. watch('source/index.html', injectTask);

8. }

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.

1. function runAllTasks() {

2. runSequence('bowerTask', 'jsTask', 'sassTask', 'jsonTask', 'htmlTask', 'assetsTask', injectTask);

3. }

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 »

1. function localTask() {

2. environment = 'local';

4. runAllTasks();

5. connectTask();

6. openTask();

7. watchTask();

8. }

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).

1. function devTask() {

2. environment = 'dev';

4. runAllTasks();

5. }

7. function prodTask() {

8. environment = 'prod';

10. runAllTasks();

11. }

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.