Grunt et WordPress
Plus de 8 minutes 🙂
Introduction
Petite pause dans mes articles du WCEU. Je vais aujourd’hui vous expliquer comment mettre en place un grunt pour vos développements de plugins/themes.
Pourquoi grunt ?
Grunt est un lanceur de tâches, en gros lorsque vous développez du javascript ou du css avec less ou sass ou autre, il incombe une étape de test/compilation/concaténation/qualité de code etc. Ces étapes sont rébarbatives, longues et surtout ne sont pas infaillibles en tant qu’humain.
C’est là que Grunt entre en action il permet, entre autre, de concaténer des scripts, de les compresser, de compiler du less, du sass, faire des tests unitaitres, lancer un jshint/lint etc.
Il faut savoir que WordPress lui même implémente ce genre de procédure et le tout est disponible sur http://develop.svn.wordpress.org/trunk/.
Vous allez donc automatiser vos constructions de ressources statiques de façon sûre et créer un code de qualité.
Installer Node.js
La première étape est d’installer node.js, il suffit de se rendre sur leur site http://nodejs.org/ et de télécharger l’installeur. Etant sur Windows, l’installation est rapide et facile, rien à faire et tout fonctionne. Sur les autres plateformes c’est encore plus facile.
Installer Grunt
Pour installer grunt il suffit d’ouvrir une invite de commande en faisant Démarrer > Executer > cmd.exe puis entrée.
Puis entrer le code suivant :
npm install -g grunt-cli
Vous devriez avoir quelque chose du genre :
Jusque là, tout va bien.
Créer son plugin avec grunt
Nous allons donc créer une extension qui va utiliser à son avantage les fonctionnalités de npm et aussi de grunt.
La première étape est donc de créer un dossier dans plugins, faisons ça tout doucement, il suffit de créer un dossier pour le plugin dans le dossier plugins.
A partir de là, il faut que depuis sa console on arrive jusqu’au dossier du plugin comme par exemple :
Pas besoin de créer le fichier racine pour le plugin, on va juste mettre en place grunt avec ses ressources.
Créer le fichier package.json
Pour créer le plugin correctement il faut ajouter le fichier package.json, ce fichier va servir à décrire le plugin, sa version, ses dépendance l’auteur etc.. Bref un fichier readme.txt d’un plugin WordPress normal mais cette fois en json :
{ "name": "plugin-grunt", "version": "0.0.0", "description": "", "author": "" }
Là on a pratiquement rien mis, mais c’est déjà la base. Pour que notre projet soit bien décris, il est important de donner les dépendances de celui-ci dans grunt, c’est à dire en spécifiant celles-ci :
{ "name": "plugin-grunt", "version": "0.0.0", "description": "", "author": "", "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-jshint": "~0.6.3", "grunt-contrib-uglify": "~0.2.2", "grunt-contrib-concat":"~0.3.0", "grunt-contrib-watch":"~0.5.3", "grunt-contrib-imagemin" : "~0.3.0", "grunt-contrib-csslint" : "~0.1.2", "grunt-contrib-cssmin" : "~0.7.0" } }
Donc là le gestionnaire de dépendances saura que pour faire fonctionner notre plugin avec grunt il va falloir avoir les modules de nodejs suivants installés. La liste des modules pour grunt est disponible sur http://gruntjs.com/plugins.
Petite description rapide des modules installés :
- jshint : Vérification de la qualité du code js basé sur des « standards », ; oublié, virgule en trop à la fin d’un objet javascript etc.. Les petites erreurs de code sont évitées.
- uglify : Comme son nom l’indique, uglify va rendre le javascript moche, peu lisible mais très compact.
- concat : concatène de fichiers entre eux, donc évite de devoir assembler soit même les fichiers.
- watch : module très important, il permet de surveiller les modifications sur des fichiers et de faire des actions à ce moment là.
- imagemin : optimisation des images, jpg, gif, png etc..
- csslint : un peu comme jshint, l’extension va vérifier que le css est bien écrit et performant.
- cssmin : minifie tout simplement les fichiers CSS.
- csslint : un peu comme le jshint, mais pour le CSS.
L’intérêt d’avoir plusieurs modules différents c’est que l’on peut faire des tâches différentes suivant les cas, une tâche pour le dev et une tâche pour build le code.
A partir de là, on peut simplement lancer la commande suivante pour installer tous les modules nécessaires au bon fonctionnement de grunt :
npm install
Et là nodejs va aller chercher les packages nécessaires et les mettre dans le dossier node_modules. Dans la console ça devrait donner quelque chose du genre :
Il faut attendre la fin de la procédure.
Créer le fichier Gruntfile.js
Pour que grunt puisse s’y retrouver, il faut lui donner un fichier de configuration qui s’appelle Gruntfile.js.
Ce fichier va contenir les différentes tâches à faire en fonction de ce que l’on a entré dans la console. La base à écrire est :
// Ici c'est du javascript module.exports = function(grunt) { grunt.initConfig({ // On lit le fichier de package pkg: grunt.file.readJSON('package.json') }); };
Mais là il ne se passe rien du tout, on ne lui a pas spécifié de tâches ou autre. Partons du principe que nous avons l’arborescence suivante :
-assets --js --script.js --script2.js --script3.js ---libs ---lib1.js ---lib2.js ---lib3.js --css --mon-style.css ---libs ---lib1.css ---lib2.css --images ---src ----sprite.png ----image1.png ----image2.png
Nous avons donc deux cas :
- Développement : je veux avoir des tests sur mes fichiers, voir s’ils sont bien indentés etc.
- Construction : je veux créer un fichier final, minifié, sans les commentaires.
Il faut donc créer deux tâches qui vont exécuter deux actions différentes.
La tâche de développement
Je veux donc voir si mon code correspond à des normes et qu’il soit de bonne qualité. Nous allons donc utiliser le jshint et le csslint, mais nous pourrions utiliser des librairies de test unitaires pour le js aussi.
Nous allons donc écrire notre première tâche grunt :
// Ici c'est du javascript
module.exports = function(grunt) {
grunt.initConfig({
// On lit le fichier de package
pkg: grunt.file.readJSON('package.json'),
// Execution du csslint
csslint : {
// Sous élément dev
dev : {
src:['assets/css/**/*.css']
}
},
// Execution du jshint
jshint : {
// Sous élément jshint pour le dev
dev : {
// Tous les fichiers sauf les librairies
src: [ 'Gruntfile.js', 'assets/*.js' ],
options: {
// options here to override JSHint defaults
globals: {
jQuery: true,
console: true,
document: true
}
}
}
}
});
// On charge les plugins
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks("grunt-contrib-imagemin");
grunt.loadNpmTasks("grunt-contrib-csslint");
grunt.loadNpmTasks("grunt-contrib-cssmin");
// Par défault on est en dev, donc on va charger les tâches de dev
grunt.registerTask('default', ['csslint:dev', 'jshint:dev']);
};
En allant dans la console et en lançant :
grunt
Grunt va lancer la tâche default et hop les fichiers seront csslint et jshint.
Seulement, il faudrait lancer cette commande à chaque fois que on enregistre son fichier… C’est un peu embêtant, non ? C’est là que grunt-contrib-watch entre en jeu, son rôle ? Regarder ce qu’il se passe sur des fichiers et lancer des tâches en conséquence :
// Ici c'est du javascript
module.exports = function(grunt) {
grunt.initConfig({
// On lit le fichier de package
pkg: grunt.file.readJSON('package.json'),
watch: {
scripts : {
files: ['assets/js/*.js'],
tasks: ['jshint:dev']
},
styles : {
files: ['assets/css/**/*.css'],
tasks: ['csslint:dev']
}
},
// Execution du csslint
csslint : {
// Sous élément dev
dev : {
src:['assets/css/**/*.css']
}
},
// Execution du jshint
jshint : {
// Sous élément jshint pour le dev
dev : {
// Tous les fichiers sauf les librairies
src: [ 'Gruntfile.js', 'assets/*.js' ],
options: {
// options here to override JSHint defaults
globals: {
jQuery: true,
console: true,
document: true
}
}
}
}
});
// On charge les plugins
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks("grunt-contrib-imagemin");
grunt.loadNpmTasks("grunt-contrib-csslint");
grunt.loadNpmTasks("grunt-contrib-cssmin");
// Par défault on est en dev, donc on va charger les tâches de dev
grunt.registerTask('default', ['watch:scripts', 'watch:styles'] );
// Là on charge les tâches watch:script et watch:styles en dev
};
Ce qui nous donne dans la console :
Donc là au moment où j’ai enregistré mon fichier css, la tâche associée s’est lancée.
Juste en dessous la tâche watch n’est pas terminée, elle attend qu’un autre fichier soit modifié donc pas besoin de relancer la tâche à chaque fois.
La construction des fichiers pour la « production »
Imaginons que notre développement est terminé et que l’on doit livrer le plugin terminé. Il faut faire en sorte que les fichiers prennent moins de place et surtout qu’ils soient rassemblés.
On va donc créer une nouvelle tâche pour la ‘compilation’ de notre application :
Js
uglify : {
dist : {
files: {
// Application minifiée
'assets/js/app.min.js': [
// Là je liste les fichiers
// On commence par les libraries
'assets/js/libs/lib1.js',
'assets/js/libs/lib2.js',
'assets/js/libs/lib3.js',
// Puis mes fichiers personnels
'assets/js/script.js',
'assets/js/script2.js',
'assets/js/script3.js'
],
}
}
}
Dans notre cas on veut avoir les librairies dans notre fichier minifié mais on pourrait vouloir compiler dans deux fichiers différents ce qui donnerait ça :
uglify : {
dist : {
files: {
// Application minifiée
'assets/js/app.min.js': [
// Mes fichiers personnels
'assets/js/script.js',
'assets/js/script2.js',
'assets/js/script3.js'
],
// Les librairies
'assets/js/vendor.min.js' : [
'assets/js/libs/lib1.js',
'assets/js/libs/lib2.js',
'assets/js/libs/lib3.js',
]
}
}
}
Vouez avez compris le principe, on peut alors ajouter autant d’éléments que l’on veut. Il y a plusieurs options disponibles dans grunt uglify, vous pouvez aller voir la doc pour tout savoir.
CSS
Le CSS se passera comme pour le JS sauf que l’on va utiliser cssmin au lieu de uglify, le plugin a aussi ses propres options.
On a donc ça au final :
cssmin : {
dist : {
files: {
// Application minifiée
'assets/css/app.min.css': [
// Mes fichiers personnels
'assets/css/mon-style.css'
],
// Les librairies
'assets/css/vendor.min.js' : [
'assets/css/libs/lib1.css',
'assets/css/libs/lib2.css'
]
}
}
}
Ce qui va donc créer deux fichiers. Sachez que des opérateurs du type ‘assets/css/libs/*.css’ vont aussi faire le boulot, sachant que les fichiers seront pris dans l’ordre alphabétique du dossier pour être compilés, cela peut être utile si vous avez beaucoup de fichiers et vous ne voulez pas gérer le fait de devoir ajouter les fichier dans la liste.
Images
Oui, on veut aussi optimiser un petit peu nos images. C’est aussi simple que pour le reste des modules :
dist : {
options: {
// On spécifie un niveau d'optimsations importat
optimizationLevel: 7
},
files: {
'assets/images/sprite.png' : 'assets/images/src/sprite.png', // 'destination': 'source'
'assets/images/image1.png' : 'assets/images/src/image1.png',
'assets/images/image2.png' : 'assets/images/src/image2.png'
}
}
N’oubliez pas d’aller voir dans la doc pour plus d’options 😉.
La compilation
Il faut juste ajouter la tâche à la fin du fichier Gruntifle.js :
grunt.registerTask('dist', ['uglify:dist', 'cssmin:dist', 'imagemin:dist'] );
Maintenant on va dans sa console et on tape :
grunt dist
Comme vous pouvez le voir, j’ai spécifié la tâche à executer. Et là on peut avoir un résultat équivalent à ça :
Voilà en plus grunt nous donne un rapport complet de la compilation, sachez qu’en ajoutant -v dans la ligne de commande on va pouvoir avoir un mode verbeux et donc beaucoup plus d’informations, plus de commandes sur le site de grunt.
Conclusion
Bon vous avez un peu la vision de la base de la base de ce que l’on peut faire avec grunt, sachez que vous pouvez lancer des tâches lors d’actions git par exemple. Vous arrivez sur votre serveur de production, vous faites un « git pull » et là automatiquement grunt va recréer, reminifier les scripts et css automatiquement, pratique non ?
Il y a beaucoup d’autres applications disponibles et de manières de faire, regardez ce qu’on fait les créateurs de WordPress, c’est tout autre chose. Bref allez voir la doc ça ne vous fera pas de mal :).
Si je devais résumer l’article en quelques mots :
Automatisez, automatisez automatisez