Grunt (2) : L’API Grunt
Dans l’article précédent, Grunt : The JavaScript Task Runner, je vous présentais Grunt dans son ensemble, ses liens avec la plateforme Node.js, son paramétrage et comment s’articule un projet web avec Grunt.
Avant d’aller plus loin et d’entrer dans le cœur du sujet – la création, la configuration et l’exécution des tâches Grunt – faisons un tour d’horizon de l’API Grunt.
Gruntfile.js : piqûre de rappel
Je vous ai présenté, dans le précédent article consacré à Grunt, le Gruntfile comme la pierre angulaire de la gestion des tâches avec Grunt. Ce fichier est constitué par la définition d’une fonction module.exports
avec comme unique argument le paramètre grunt
.
module.exports = function(grunt) {
// Code relatif à grunt
};
Grunt expose toutes ses méthodes et propriétés via cet objet grunt
. Toutes ces méthodes et propriétés sont détaillées dans la documentation de l’API Grunt, les principales sont présentées dans ce qui suit.
L’API Grunt
L’API Grunt comporte 9 namespaces :
grunt.config
: Accès à la configuration du projet.grunt.task
: Enregistrer, de charger et de lancer des tâches externes.grunt.file
: Rechercher, lire et écrire des fichiers.grunt.fail
: Avertir quand quelque chose se passe mal.grunt.log
: Afficher des messages de sortie sur la console.grunt.option
: Partager des paramètres entre plusieurs tâches et accéder aux options de la ligne de commande.grunt.event
: Gérer les événements.grunt.template
: Gérer les templates.grunt.util
: Utilitaires pour le Gruntfile et les tâches.
Dans la suite de cet article, les quatre premiers namespaces de l’API et l’objet this
dans le contexte d’une tâche sont présentés. Cela vous donnera une idée du fonctionnement global de l’API et vous aidera à aborder les prochains articles de cette série pour créer, configurer et exécuter des tâches Grunt.
grunt.config
L’objet grunt.config
donne un accès à la configuration du projet définie dans le Gruntfile.
Initialiser les données
grunt.config.init
Initialise l’objet de configuration du projet. L’objet configObject
est utilisé par les tâches et est accessible via la méthode grunt.config
.
grunt.config.init(configObject)
grunt.initConfig(configObject)
Requérir une donnée
grunt.config.requires
Met en échec la tâche courante si au moins l’une des propriétés requises de la configuration est manquante, null
ou undefined
. Une ou plusieurs propriétés ou tableaux de propriétés de la configuration peuvent être passés en paramètre.
grunt.config.requires(p1 [, p2 [, …]])
this.requiresConfig(p1 [, p2 [, …]]) // depuis le contexte d'une tâche
Accéder aux données : accesseurs
grunt.config.getRaw
Retourne une valeur brute de la configuration du projet Grunt, sans traiter les chaînes template <% %>
. Si la propriété spécifiée existe, sa valeur est retournée, sinon null
. Si aucune propriété n’est spécifiée, une copie de l’objet config est retournée.
grunt.config.getRaw([prop])
grunt.config.process
Traite une valeur en évaluant récursivement les templates <% %>
.
grunt.config.process(value)
module.exports = function(grunt) {
grunt.config.init({
bar : "C'est mal bar' !",
foo : "<%= bar %>"
});
grunt.registerTask("default", "Trace des choses...", function() {
var processed = grunt.config.process(grunt.config("foo"));
grunt.log.write(processed).ok(); // retourne "C'est mal bar' !"
);
};
grunt.config.get
Retourne une valeur de la configuration du projet Grunt. Si la propriété spécifiée existe, sa valeur est retournée, sinon null
. Si aucune propriété n’est spécifiée, une copie de l’objet config est retournée. Les templates <% %>
sont traités récursivement par appel à la méthode grunt.config.process
.
grunt.config.get([prop])
grunt.config.set
Assigne une valeur à une propriété de la configuration du projet Grunt et la retourne.
grunt.config.set(prop, value)
grunt.config
Retourne ou assigne une propriété de la configuration du projet Grunt. Cette méthode est un alias de grunt.config.set
et grunt.config.get
.
grunt.config([prop [, value]])
grunt.config(prop); // alias de grunt.config.get(prop)
grunt.config(prop, value); // alias de grunt.config.set(prop, value)
grunt.config.escape
Echappe les points dans la propriété spécifiée
grunt.config.escape(propString)
module.exports = function(grunt) {
grunt.config.init({
foo: {
bar: "value"
}
});
grunt.registerTask("default","Trace des choses... ", function() {
grunt.log.write(grunt.config.escape("foo.bar")).ok(); // retourne "foo\.bar"
});
};
grunt.config.merge
Fusionne récursivement les propriétés de l’objet configObject
à la configuration du projet Grunt.
grunt.config.merge(configObject)
grunt.task
L’API task permet d’enregistrer, de charger et de lancer des tâches externes.
Créer des tâches
grunt.task.registerTask
Enregistre sous un alias une liste d’alias de tâches ou une tâche. Cette méthode a deux signatures :
// liste d'alias
grunt.task.registerTask(taskName, taskList)
grunt.registerTask(taskName, taskList)
// tâche
grunt.task.registerTask(taskName, description, taskFunction)
grunt.registerTask(taskName, description, taskFunction)
Si une liste d’alias est spécifiée, la nouvelle tâche sera un alias pour une ou plusieurs autres tâches. Quand cette tâche est lancée, les tâches de la taskList
sont lancées dans l’ordre spécifié. L’argument taskList
doit être un tableau d’alias de tâche.
Si une description et une taskFunction
sont passées, la fonction est exécutée quand la tâche est lancée. La description est affichée quand la commande grunt −−help
est exécutée.
grunt.task.registerTask('default', ['jshint', 'qunit', 'concat:dist', 'uglify:dist']);
grunt.task.registerTask(
'foo',
'Une tâche simple pour tracer des choses',
function(arg1, arg2) {
if (arguments.length === 0) {
grunt.log.writeln(this.name + ", pas d'argument");
}
else {
grunt.log.writeln(this.name + ", " + arg1 + " " + arg2);
}
}
);
grunt.task.registerMultiTask
Enregistre une multi-tâche ou tâche à cibles multiples. Une multitâche est une tâche qui se répète pour chacune de ses sous-propriétés (ou targets) si aucune target n’est spécifiée.
grunt.task.registerMultiTask(taskName, description, taskFunction)
grunt.registerMultiTask(taskName, description, taskFunction)
module.exports = function(grunt) {
grunt.initConfig({
log: {
foo: [1, 2, 3],
bar: "Salut monde !",
oque: false
}
});
grunt.task.registerMultiTask("log", "Trace des choses…", function() {
grunt.log.writeln(this.target + ": " + this.data);
});
};
Dans l’exemple précédent, l’exécution du log
multitâche provoque l’écriture dans la console pour chacune des trois sous-propriétés de la propriété log
de la configuration du projet. Par contre, en indiquant la target foo
, la tâche ne s’exécute qu’une fois pour la target spécifiée.
grunt.task.requires
Met en échec la tâche si une autre tâche est en échec ou n’a jamais été lancée.
grunt.task.requires(taskName)
grunt.task.exists
Vérifie en fonction de son nom si une tâche existe parmi les tâches enregistrées. Retourne un booléen.
grunt.task.exists(name)
grunt.task.renameTask
Renomme une tâche. Cela peut être utile pour remplacer le comportement par défaut d’une tâche, tout en conservant l’ancien nom.
grunt.task.renameTask(oldname, newname)
grunt.renameTask(oldname, newname)
Charger des tâches externes
Pour la plupart des projets, les tâches seront définies dans le Gruntfile. Pour de grands projets, ou dans le cas où l’on a besoin de partager les tâches entre projets, les tâches peuvent être chargées depuis un ou plusieurs chemins ou plugins Grunt installés avec npm.
grunt.task.loadTasks
Charge les fichiers de tâches depuis le chemin spécifié, relatif au répertoire du Gruntfile. Cette méthode peut être utilisée pour charger des fichiers de tâches depuis un plugin Grunt local.
grunt.task.loadTasks(tasksPath)
grunt.loadTasks(tasksPath)
grunt.task.loadNpmTasks
Charge les tâches du plugin Grunt spécifié. Ce plugin doit être installé localement via npm et relativement au Gruntfile.
grunt.task.loadNpmTasks(pluginName)
grunt.loadNpmTasks(pluginName)
Mettre les tâches en file d’attente
Grunt met automatiquement en file d’attente et lance toutes les tâches spécifiées dans la ligne de commande, mais chaque tâche peut met en file d’attente d’autres tâches à lancer.
grunt.task.run
Met en file d’attente une ou plusieurs tâches. Chaque tâche spécifiée dans taskList
sera lancée immédiatement après la fin de l’exécution de la précédente. La liste de tâches peut être un tableau de tâches ou une suite d’arguments.
grunt.task.run(taskList)
grunt.registerTask('foo-bar-oque', 'Met en file d\'attente.', function() {
grunt.task.run('foo', 'bar:man', 'oque');
grunt.task.run(['foo', 'bar:man', 'oque']);
});
grunt.task.clearQueue
Vide la file d’attente. A moins que des tâches soit de nouveau mises en file d’attente, plus aucune tâches ne seront lancées.
grunt.task.clearQueue()
grunt.task.normalizeMultiTaskFiles
Normalise l’objet de configuration d’une target de tâche dans un tableau de correspondances de fichiers src-dest.
grunt.task.normalizeMultiTaskFiles(data [, targetname])
grunt.registerTask('normalize', function(pattern) {
var result = grunt.task.normalizeMultiTaskFiles(['files/**/*'], "myTargetName");
grunt.log.writeln(JSON.stringify(result));
});
grunt.file
Il y a plein de méthodes fournies pour lire et écrire dans les fichiers, traverser le système de fichiers et trouver des fichiers. Beaucoup de ses méthodes encapsulent les fonctionnalités de base de Node.js, mais avec en plus de la gestion d’erreur, des traces et une normalisation de l’encodage de caractère.
Note : tous les chemins sont relatifs au répertoire du Gruntfile à moins que le répertoire de travail courant soit modifié avec grunt.file.setBase
ou l’option de ligne de commande −−base
.
Lecture et écriture
grunt.file.read / grunt.file.readJSON / grunt.file.readYAML
Lit et retourne un contenu de fichier. Retourne une chaîne de caractères, à moins que options.encoding
soit null
, auquel cas la méthode retourne un Buffer.
grunt.file.read(filepath [, options])
grunt.file.readJSON(filepath [, options]) // contenu parsé en JSON
grunt.file.readYAML(filepath [, options]) // contenu parsé en YAML
Les propriétés reconnues dans l’objet options sont :
var options = {
// Si aucun encodage n’est spécifié, par défaut à grunt.file.defaultEncoding.
// S’il est spécifié à null, retourne un Buffer non décodé au lieu d’une chaîne.
encoding: encodingName
};
grunt.file.write
Ecrit le contenu spécifié dans un fichier en créant le chemin si nécessaire.
grunt.file.write(filepath, contents [, options])
Les propriétés reconnues dans l’objet options sont :
var options = {
// Si aucun encodage n'est spécifié, par défaut à grunt.file.defaultEncoding.
// Si 'contents' est un Buffer, l'encodage est ignoré.
encoding: encodingName
};
grunt.file.copy
Copie un fichier source à une destination en créant le chemin si nécessaire.
grunt.file.copy(srcpath, destpath [, options])
Les propriétés reconnues dans l’objet options sont :
var options = {
// Si aucun encodage n'est spécifié, par défaut à grunt.file.defaultEncoding.
// Si null, la fonction 'process' reçoit un Buffer au lieu d'un String.
encoding: encodingName,
// bool processFunction(srcPath, srcContent, destPath)
// Si la fonction retourne 'false', la copie est interrompue.
process: processFunction,
// Patterns sur le chemin de la source. Si aucun ne correspond,
// le fichier ne sera pas traité par la fonction 'process'.
// Si globbingPatterns='true', le traitement n'aura pas lieu.
noProcess: globbingPatterns
};
grunt.file.delete
Supprime le chemin spécifié. Cela supprimera les fichiers et les répertoires de manière récursive.
grunt.file.delete(filepath [, options])
Les propriétés reconnues dans l’objet options sont :
var options = {
// Permet de supprimer en dehors du répertoire de travail courant.
// Cette option permet être surchargée par l'option de ligne de commande --force.
force: true
};
Répertoires
grunt.file.mkdir
Fonctionne comme mkdir −p
. Crée un chemin avec tous les répertoires intermédiaires. Si mode n’est pas spécifié, la valeur par défaut est 0777&(~process.umask())
.
grunt.file.mkdir(dirpath [, mode])
Types de fichiers
grunt.file.exists / grunt.file.isLink / grunt.file.isDir / grunt.file.isFile
Retourne un booléen indiquant si le chemin spécifié existe, est un lien, un chemin ou un fichier ; retourne faux si le chemin n’existe pas.
Comme la méthode path.join
de Node.js, ces méthodes joignent tous les arguments ensemble et normalise le chemin résultant.
grunt.file.exists(path1 [, path2 [, ...]])
grunt.file.isLink(path1 [, path2 [, ...]])
grunt.file.isDir (path1 [, path2 [, ...]])
grunt.file.isFile(path1 [, path2 [, ...]])
Chemins
grunt.file.isPathAbsolute
Retourne un booléen indiquant si le chemin spécifié est absolu.
Comme la méthode path.join
de Node.js, ces méthodes joignent tous les arguments ensemble et normalise le chemin résultant.
grunt.file.isPathAbsolute(path1 [, path2 [, ...]])
grunt.file.arePathsEquivalent
Retourne un booléen indiquant si tous les chemins spécifiés se réfèrent au même chemin.
grunt.file.arePathsEquivalent(path1 [, path2 [, ...]])
grunt.file.doesPathContain
Retourne un booléen indiquant si tous les chemins descendants sont contenus par le chemin ascendant.
grunt.file.doesPathContain(ancestorPath, descendantPath1 [, descendantPath2 [, ...]])
grunt.file.setBase
Change le chemin de travail courant. Par défaut tous les chemins sont relatifs au Grunfile.
Comme la méthode path.join
de Node.js, ces méthodes joignent tous les arguments ensemble et normalise le chemin résultant.
grunt.file.setBase(path1 [, path2 [, ...]])
grunt.fail
L’API fail est là pour vous aider quand quelque chose d’horrible survient ! Si quelque chose dysfonctionne ou est sur le point d’échouer dans une tâche, il est possible de forcer l’arrêt de Grunt.
grunt.fail.warn
Affiche une alerte et arrête Grunt immédiatement. Grunt continuera de traiter les tâches si l’option de ligne de commande −−force
est utilisée. L’argument error
peut être une chaîne de caractères ou un objet erreur.
grunt.fail.warn(error [, errorcode])
grunt.warn(error [, errorcode])
Si l’option de ligne de commande −−stack
est utilisée et qu’un objet erreur est passé, la stack trace sera affichée.
grunt.fail.fatal
Affiche une alerte et arrête immédiatement Grunt. L’argument error
peut être une chaîne de caractères ou un objet erreur.
grunt.fail.fatal(error [, errorcode])
grunt.fatal(error [, errorcode])
Si l’option de ligne de commande −−stack
est utilisée et qu’un objet erreur est passé, la stack trace sera affichée.
Un bip est émis à moins que l’option −−no−color
ne soit utilisée.
Dans les tâches
Pendant l’exécution d’une tâche, Grunt expose un certain nombre de propriétés et méthodes spécifiques aux tâches via l’objet this
. Le même objet est également exposé via grunt.task.current
dans les templates.
Dans toutes les tâches
this.async
Si une tâche est asynchrone, cette méthode doit être invoquée pour indiquer à Grunt d’attendre. Elle retourne une fonction done
qui doit être appelée quand la tâche s’est terminée. false
ou un objet erreur peut être passé à la méthode done
pour indiquer à Grunt que la tâche a échoué.
Si la méthode this.async
n’est pas appelée, la tâche s’exécutera de manière synchrone.
// Indique à Grunt que la tâche est asynchrone.
var done = this.async();
// Le code asynchrone.
setTimeout(function() {
// Simulons une erreur de manière aléatoire.
var error = Math.random() > 0.5;
// Terminé!
done(error);
}, 1000);
this.requires
Si une tâche dépend du succès de l’exécution d’une ou plusieurs autres tâches, cette méthode peut être utilisée pour forcer Grunt à s’arrêter si cette ou ces autres tâches n’ont pas été exécutées ou ont échoué. La liste de tâches peut être un tableau ou une suite de noms de tâches.
this.requires(tasksList)
this.requiresConfig
Met en échec la tâche courante si au moins l’une des propriétés de configuration requises est manquante. Un tableau ou une suite de propriétés de configuration peuvent être spécifiées.
this.requiresConfig(prop1 [, prop2 [, ...]])
Cette méthode est un alias de la méthode grunt.config.requires
.
this.name
Le nom de la tâche, tel que défini avec grunt.registerTask
. Dans le cas d’une multitâche, le nom est le même quelle que soit la target.
module.exports = function(grunt) {
grunt.initConfig({
exemple: {
foo: [1, 2, 3],
bar: 'Salut monde !',
oque: false
}
});
grunt.task.registerMultiTask('exemple', 'Un exemple de tâche…', function() {
grunt.log.writeln(this.name);
});
};
Si la tâche est renommée avec grunt.task.renameTask
, la propriété this.name
renvoie le nouveau nom.
this.nameArgs
Le nom de la tâche, incluant tous les arguments ou flags séparés par un deux-points spécifiés dans la ligne de commande. Par exemple, si une tâche exemple
est exécutée avec la commande grunt exemple:foo
, dans la tâche, this.nameArgs
sera exemple:foo
.
Si la tâche est renommée avec grunt.task.renameTask
, la propriété this.nameArgs
renvoie le nouveau nom.
this.args
Un tableau d’arguments passés à la tâche. Par exemple, si une tâche “exemple” est exécutée avec la commande grunt exemple:foo:tez
, dans la tâche, this.args
sera ["foo", "tez"]
.
Pour les multi-tâches, la target courante est omise du tableau this.args
.
this.flags
Un objet généré à partir des arguments passés à la tâche. Par exemple, si une tâche exemple
est exécutée avec la commande grunt exemple:foo:tez
, dans la tâche, this.flags
sera {foo: true, tez: true}
.
Pour les multitâches, la target courante est omise de l’objet this.flags
.
this.errorCount
Le nombre d’appels à grunt.log.error
pendant l’exécution de la tâche. Cela peut être utilisé pour mettre en échec la tâche si des erreurs sont logées pendant l’exécution de la tâche.
this.options
Retourne un objet options
. Les propriétés de l’argument optionnel defaultsObj
seront surchargées par les propriétés de l’objet options au niveau de la tâche, qui seront également surchargées dans les multitâches par les propriétés de l’objet options au niveau de la target.
this.options([defaultsObj])
Cette exemple montre comment une tâche peut utiliser la méthode this.options
:
var options = this.options({
enabled: false,
});
doSomething(options.enabled);
Dans les multitâches
this.target
Contient le nom de la target courante.
this.files
Tous les fichiers spécifiés en utilisant un format et des options supportés par Grunt, un motif ou une correspondance dynamique seront automatiquement normalisés dans un format unique : un tableau de fichiers.
Cela signifie que les tâches ne gèrent pas la normalisation des chemins de fichiers. L’action est réalisée en amont par Grunt.
La tâche doit itérer sur le tableau this.files
en utilisant les propriétés src
et dest
de chaque objet du tableau. La propriété this.files
sera toujours un tableau. La propriété src
sera également toujours un tableau car il est possible d’avoir plusieurs sources pour un fichier destination.
Comme il est possible que des fichiers non-existants soient inclus dans les sources, il est préférable de tester l’existence des fichiers source avant de les utiliser.
Cet exemple montre comment une tâche simple de concaténation peut utiliser la propriété this.files
:
this.files.forEach(function(file) {
var contents = file.src.filter(function(filepath) {
// Supprimer les fichiers non-existants (à vous de filtrer ou alerter).
if (!grunt.file.exists(filepath)) {
grunt.log.warn('Fichier source "' + filepath + '" non trouvé.');
return false;
}
else {
return true;
}
}).map(function(filepath) {
// Lit et retourne le fichier source.
return grunt.file.read(filepath);
}).join('\n');
// Ecrit les contenus joints dans le fichier destination filepath.
grunt.file.write(file.dest, contents);
// Imprime un message de succès.
grunt.log.writeln('Fichier "' + file.dest + '" créé.');
});
Si vous avez besoin des propriétés de l’objet file d’origine, elles sont disponibles sur chaque objet file dans la propriété orig
.
this.filesSrc
Tous les fichiers spécifiés par n’importe quel format sont réduits à un seul tableau. Si votre tâche est en “lecture seule” et ne tient aucun compte des chemins de destination, utilisez ce tableau plutôt que this.files
.
Cet exemple montre comment une tâche “lint” simple peut utiliser la propriété this.filesSrc
:
// Lint les fichiers spécifiés.
var files = this.filesSrc;
var errorCount = 0;
files.forEach(function(filepath) {
if (!lint(grunt.file.read(filepath))) {
errorCount++;
}
});
// Met la tâche en échec si des erreurs sont loggés.
if (errorCount > 0) {
return false;
}
// Autrement, imprime un message de succès.
grunt.log.ok('Fichiers lintés: ' + files.length);
this.data
Objet de configuration pour la target courante.
grunt.initConfig({
exemple: {
foo: [1, 2, 3],
bar: 'Salut monde !',
oque: { prop: 'value' }
}
});
grunt.task.registerMultiTask('exemple', 'Un exemple de tâche…', function() {
grunt.log.writeln(JSON.stringigy(this.data));
// grunt exemple:foo >> this.data = [1, 2, 3]
// grunt exemple:bar >> this.data = 'Salut monde !'
// grunt exemple:oque >> this.data = { prop: 'value' }
});
Il est recommandé d’utiliser this.options
, this.files
et this.filesSrc
au lieu de this.data
car leurs valeurs sont normalisées.
Et après ?
L’API Grunt est assez riche et il faut bien la connaître pour comprendre et mettre en œuvre des tâches tierces ou pour créer et paramétrer ses propres tâches.
Les quatre premiers namespaces de l’API vous ont été détaillés ici, ce qui vous permettra d’aborder en toute quiétude la suite de cette série d’articles consacrées à Grunt. Je ne peux que vous recommander d’aller vous documenter sur les autres namespaces sur le site de Grunt car ça ne peut que vous être profitable par la suite.
Dans le prochain article, nous verrons comment configurer les tâches Grunt.