NPM, Gulp, ou les deux ?
Ce titre pourrait faire partie d'un "sel et poivre" du Buger Quizz. Pour les plus jeunes, je vous invite à regarder ce lien.
Le besoin de tasks runner
Le sujet est d'évoquer les tasks runner. En effet, dans tout projet, nous avons besoin de lancer un certain nombre de commandes pour piloter le projet. Par exemple, nous voulons pouvoir:- lancer des métriques sur le projet (génération de la documentation, vérifier la qualité du code),
- lancer des tests et de la couverture de code
- lancer l'application et l'arrêter
Dans le monde Java, au début, nous utilisions Ant dont le principe est de déclarer des tâches qui font faire des actions. Ensuite, nous avons des solutions émergentes comme Maven ou encore Graddle qui eux ont pour vocation de proposer un cycle de vie qui va lancer des tâches.
Dans le monde JavaScript, nous avons une multitude de solutions. Dans le titre, j'ai évoqué seulement NPM et Gulp. Mais je peux également citer Grunt, Brunch (qui lui apporte un peu comme Maven un cycle de vie). Maintenant, la question est de savoir, quelle solution devons-nous choisir ?
La réponse est évidente: ça dépend :)
A mes yeux, il existe deux types de projets JavaScript: des projets NodeJs et des projets Web (composant Web, framework pour le Web ou application Web). Dans les deux cas, nous allons avoir à utiliser NodeJs pour s'outiller (lancer les tess avec Karma, builder l'application avec Gulp, ...). Sachant qu'un projet NodeJs peut être à destination ou tout du moins utiliser dans un projet Web. Si je prends le projet lodash, il a une structure NodeJs, mais via Bower, il peut être utiliser dans une application Web.
Et chacun de ces types de projets va avoir un cycle de vie différent.
Dans le cas d'un projet NodeJs, celui-ci en règle générale doit être utilisable en tant que tel, sans avoir à compiler des fichiers ou avoir à lancer des commandes. Ainsi, même si nous écrivons notre module NodeJs en CoffeeScript, TypeScript ou en ES6 (que nous transpilerons ensuite en ES5 via BabelJs), nous allons configurer NodeJs pour exposer le code compilé et donc exclure les fichiers sources. Pour cela, nous allons configurer le fichier "package.json", en spécifiant non seulement la propriété "main" afin de savoir quel est le fichier principal qui expose les fonctionnalités du module, mais également "files" qui permet de lister tous les fichiers à exposer quand nous utiliserons le module en tant que dépendance dans un autre module.
Pour le cas d'un projet Web, c'est un peu différent. Nous aurons souvent besoin d'avoir à compiler l'applicaiton, en minifiant du JavaScript, CSS, voir de les compiler. Du coup, nous risquons d'avoir à faire plus de traitements. De plus, il sera ne forcément une dépendance d'un autre module NodeJs.
Ainsi, pour un projet NodeJs, l'usage de NPM suffit. Et pour un projet Web, je conseille plutôt d'utiliser un task runner comme Gulp (même si Brunch est intéressant. Je vous invite à lire l'excellent article de Christophe Porteneuve). Néanmoins, pour ce dernier, j'invite à lancer les commandes du task runner via NPM.
Quelle est l'avantage de cela ? Cela permet de s'abstraire de la solution que nous voulons utiliser en tant que task runner. Imaginons, nous avons un projet de longue haleine. Nous mettons donc rapidement en place une usine logicielle. Nous devons spécifier les commandes à lancer. Ce projet nécessitera peut-être des scripts Bash, PowerShell qui appelleront aussi ces commandes. Si dans le temps, pour une raison X, le choix du task runner change, nous devrons changer tout ça. Tandis que si nous utilions NPM comme interface, ce sera juste le fichier "package.json" à modifier.
Cas pratique
Maintenant, regardons des exemples de configuration du fichier "package.json". L'objectif ici sera de définir des commandes pour:- Générer un fichier checkstyle pour la qualité de code
- Générer un fichier JUnit de résultats de tests
- Générer un rapport Cobertura pour le code coverage
- Générer la documentation
- Générer le fichier "npm-shrinkwrap"
A noter que lorsque nous définissons des commandes custom, nous devrons les lancer par la suite en faisant:
> npm run mycommand
Nous pourrons alors lancer les commandes:
> npm run checkstyle
> npm run test
> npm run coverage
> npm run documentation
> npm run shrinkwrap
Application NodeJs classique
L'exemple ci-dessous vous montre que nous pouvons directement utiliser les modules JsDoc, Eslint, Mocha et Istanbul directement dans la section "scripts". En effet, ce sont des modules qui être utilisés en tant que ligne de commande. Du coup, en pointant vers le bon fichier dans le répertoire "node_modules", nous pouvons lancer facilement les commandes sans usage de Gulp ou autre. De plus, aller chercher les fichiers dans le répertoire "node_modules" local, et non pas en tapant la commande du module, nous garantit que nous allons utiliser la version qui nous intéresse. En effet, rien ne nous dit que nous n'avons pas installer Mocha 1.9.0 en global mais que nous voulons utiliser en local la version 2.1.0 (ou inversement). Ce qui peut apporter des changements de comportement.
{ "name": "node-project", "version": "0.0.1", "private": false, "scripts": { "checkstyle": "(node node_modules/eslint/bin/eslint.js --config=.eslintrc --output-file ./target/eslint-reporter-checkstyle.xml --format checkstyle ./lib) || (exit 0)",
"coverage": "node ./node_modules/istanbul/lib/cli.js cover node_modules/mocha/bin/_mocha -- --recursive --ui bdd --reporter mocha-junit-reporter --reporter-options mochaFile=./target/mocha-reporter-junit.xml --colors test/**/*Spec.js",
"documentation": "node ./node_modules/jsdoc/jsdoc.js --recurse --destination ./target/doc --verbose ./lib",
"shrinkwrap": "npm shrinkwrap --dev",
"test": "node ./node_modules/mocha/bin/mocha --recursive --ui bdd --reporter mocha-junit-reporter --reporter-options mochaFile=./target/mocha-reporter-junit.xml --colors test/*.js" } }
A noter que dans les commandes ci-dessus, nous ne supprimons jamais l'ancien fichier de résultats. Du coup, il est conseillé de se mettre une commande "delete" que nous pourrons utiliser pour supprimer par la suite l'ancien fichier de résultat et lancer la commande. Pour ça, nous allons utiliser le module trash de Sindre Sorhus qui permet de faire ça simplement, en ligne de commande. Un exemple:
{ "name": "node-project", "version": "0.0.1", "private": false, "scripts": { "checkstyle": "npm run delete -f target/eslint-reporter-checkstyle.xml && node node_modules/eslint/bin/eslint.js --config=.eslintrc --output-file ./target/eslint-reporter-checkstyle.xml --format checkstyle ./lib",
"delete": "node node_modules/trash/cli.js"
}
}
Application WebApp
Ici, dans le cas de notre application Web, nous allons utiliser Gulp (car nous pouvons avoir besoin de plus de traitement ou configuration à injecter) et Bower pour la gestion des dépendances. Comme évoqué ci-dessus, nous voulons garantir que lorsque nous utilisons Gulp ou Bower, nous utilisons la version de notre projet, et non pas celle installé en global, nous allons les rajouter dans la section "scripts".
Ainsi, nous avons deux nouvelles commandes:
> npm run gulp
> npm run bower
Et au final, nous simplement la configuration suivante:
{ "name": "webapp-project", "version": "0.0.1", "private": false, "scripts": { "bower": "node node_modules/bower/bin/bower", "gulp": "node node_modules/gulp/bin/gulp.js", "checkstyle": "npm run gulp checkstyle", "coverage": "npm run gulp test", "documentation": "npm run gulp documentation", "shrinkwrap": "npm shrinkwrap --dev", "test": "npm run gulp test"
}
}
Au final
Nous voyons qu'au final, en fonction du projet que nous souhaitons réaliser, il est très simple de définir un task runner se basant sur NPM comme interface. Et j'applique régulièrement ces recettes dans mes projets.
Commentaires
Enregistrer un commentaire