Nicolas Juen

Menu

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 :
grunt-install

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 :
grunt-plugin-folder

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 :grunt-npm-install

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 :

  1. Développement : je veux avoir des tests sur mes fichiers, voir s’ils sont bien indentés etc.
  2. 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 :
grunt-watch-result

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 :

grunt-compilation-result

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

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.