Allez plus loin avec les scripts du package.json
Beaucoup de projets utilisent Gulp ou Grunt pour gérer toutes les tâches liées à la vie du projet : transpiler les sources à partir du TypeScript ou encore optimiser les images, etc. Est-il possible de s'en passer et de simplifier la stack de nos projets ? Je pense que oui.
Attention, le but de cet article n'est pas de dire que Gulp ou Grunt ou autre task runners sont mauvais et doivent absolument être remplacés. Loin de là. Pour beaucoup de projets, les tasks runners restent la meilleure solution disponible.
Par contre, je pense que les « petits projets » ne devraient pas y avoir recours systématiquement. Cela apporte plus de complexité que cela ne résout de problèmes.
En vrac :
- les scripts des tasks runners sont autant de code qu'il faut maintenir
- les tasks runners ont chacun leur logique et leur API qu'il faut apprendre
- les tasks runners participent à la multiplication des outils et à la JavaScript fatigue.
- les task runners sont souvent inutiles quand les tâches du projet sont simples
Pour toutes les raisons ci-dessus, ils rendent plus compliqué l'arrivée de nouveau développeurs dans l'équipe (et même le départ du développeur qui part avec une partie du savoir de l'équipe).
De même, je ne compte plus les fois où j'ai dû passer une après-midi à déboguer un script Gulp qui ne voulait plus supprimer le répertoire dist/
avant de générer les sources de production au lieu de coder de vraies fonctionnalités qui ont de la valeur pour les utilisateurs.
Bref, vous avez compris l'idée, je vais vous montrer comment j'utilise les scripts du package.json.
Utiliser les exécutables des paquets NPM
Voici un des cas qui m'agace le plus lorsque je lis un script Gulp :
function clean(cb) {
rimraf('./dist')
cb();
}
exports.clean = clean
Installer un task runner, écrire un script de configuration, – et ainsi ajouter beaucoup de sources d'erreurs potentielles dans votre projet – pour finalement n'appeler qu'une ou deux commandes… Dans ma tête, c'est ajouter beaucoup de risques dans le projet pour peu de valeur ajoutée.
Mon premier réflexe est alors de déplacer la commande directement dans la partie « scripts » du package.json
de cette façon :
{
"scripts": {
"clean": "rimraf ./dist"
}
}
Ça marche parce que NPM (et les autres packages manager) sont capables d'aller chercher les exécutables des paquets directement dans le dossier node_modules
.
En d'autres termes, lorsque vous exécutez un script NPM, NPM charge les exécutables du node_modules
en plus du dossier PATH
.
Utiliser le ET logique
Je sais que beaucoup migrent vers un script Gulp dès qu'ils ont besoin de lancer plus d'une commande l'une à la suite de l'autre.
Sauf que ce n'est pas si compliqué de lancer plusieurs commandes en une seule ligne depuis les scripts NPM. Il suffit d'utiliser le ET logique de bash :
{
"scripts": {
"clean": "rimraf ./dist && ts-node"
}
}
Votre interpréteur de commande exécutera d'abord rimraf ./dist
puis si tout se passe bien, il exécutera ts-node
. Vous pouvez ainsi multiplier les commandes grâce au &&
.
Utiliser les Commande Line Interface
Je me suis rendu compte que beaucoup de paquets NPM que j'utilisais dans mes scripts Gulp ne sont en réalité que des wrappers ou des copies en JavaScript de commande existante sur le système.
Par exemple, le paquet rimraf
qui nous permet de faire un rm -rf
. Pourquoi ne pas le remplacer par rm -rf
? On supprime de cette façon plein de dépendances pas forcément très utiles en fonction de notre projet.
Je ne dis pas que rimraf
ne sert à rien de plus que de faire rm -rf
. Il gère des cas spécifiques, comme par exemple, les équipes qui travaillent sur plusieurs OS. En effet, rm -rf
ne fonctionne pas sur Windows par exemple. Sauf que quand je travaille sur mon side project, ce n'est pas un problème que j'ai à résoudre en vrai. rm -rf
suffit dans la plupart des cas.
Appeler un script NPM depuis un script NPM
Je n'aime pas la duplication de code. Dans mes scripts, c'est pareil. J'aime bien ne pas me répéter, et ainsi ne pas répéter les corrections de bugs que j'aurai à faire plus tard.
Considérez, par exemple, ces scripts NPM :
{
"scripts": {
"build": "ts-node",
"dev": "NODE_ENV=develop ts-node -w",
"clean": "rm -rf ./build",
"prod": "rm -rf ./build && ts-node && zip -r dist.zip ./build"
}
}
Il y a beaucoup de répétitions. Il est possible de simplifier le code et d'éviter la duplication de code en appelant des commandes NPM existantes.
{
"scripts": {
"build": "ts-node",
"clean": "rm -rf ./build",
"dev": "NODE_ENV=develop npm run build -- -w",
"prod": "npm run clean && npm run build && zip -r dist.zip ./build"
}
}
Maintenant, si j'ai besoin de modifier la commande de build, je sais que les commandes dev
et prod
seront également modifiés.
Utiliser les scripts exécutables
Lorsque mes scripts NPM commencent à devenir trop longs et que je ressens le besoin de les extraire de mon fichier package.json
, je ne me précipite pas tout de suite vers un task runner.
Mon premier réflexe est d'utiliser les scripts bash/sh/zsh. Pourquoi ?
- Parce que ces langages de scripting ont été conçus pour ça. J'ai l'impression que c'est le bon outil pour la bonne tâche ;
- Parce que c'est déjà installé sur mon système d'exploitation et que j'ai passé des heures à customiser cet outil, autant l'utiliser ;
- Parce que je peux juste copier mon script NPM et le coller dans mon fichier
.sh
. Il fonctionnera parfaitement. Ça ne me coûtera pas plus d'efforts que ça ;
Du coup, j'ai souvent un dossier scripts
à la racine de mes projets pour rassembler les scripts que j'ai écrit. Ensuite, je les utilise comme ceci dans mon fichier package.json
:
{
"scripts": {
"build": "zsh ./scripts/build.sh",
}
}
Conclusion
Mon but avec cet article est de faire réaliser que la majorité des projets n'ont pas forcément besoin de toute la toolchain qu'on a l'habitude d'installer à chaque fois. Malheureusement, tuer une mouche avec un bazouka est, en plus d'être un peu bête, aussi capable de causer des problèmes que l'on pourrait facilement s'éviter si seulement on utilisait les outils adaptés à notre situation.
Certains projets, certaines situations justifient largement l'utilisation de tous ces outils. Pourtant, ils ne devraient pas être le choix par défaut lorsque l'on configure un nouveau projet de développement.