Grunt pour ceux qui pensent que Grunt est compliqué

On parle beaucoup de Grunt aujourd'hui, mais les articles disponibles sont souvent abscons et techniques. Pourtant Grunt n'est pas réservé aux spécialistes, comme le montre cette introduction de Chris Coyier.

Par

On donne souvent les conseils suivants aux intégrateurs web :

Divisez votre CSS et votre JavaScript en autant de parties que nécessaire, puis concaténez-les pour la version de production de votre site.
Compressez votre CSS, minifiez votre JavaScript, pour améliorer la performance de votre site.
Optimisez vos images pour réduire leur taille sans perdre en qualité.
Utilisez Sass pour votre CSS, en raison des nombreuses abstractions qu'il vous permet.

Il y en aurait encore beaucoup à ajouter, mais voilà déjà quelques basiques. On pourrait les appeler des tâches.

Je suis sûr que vous avez entendu parler de Grunt. Eh bien Grunt est un task runner, c'est à dire qu'il effectue des tâches pour vous. Une fois installé, ce qui n'est pas particulièrement difficile, il gèrera toutes ces tâches sans que vous n'ayez plus à y penser.

Mais soyons clair : Grunt est un de ces nouveaux joujoux à la mode que tous les cool kids utilisent, mais qui à première vue semble étrange et intimidant. Je vous comprends. Cette introduction est pour vous.

Écartons tout de suite quelques malentendus

Peut-être avez-vous entendu parler de Grunt mais ne l'avez pas utilisé vous-même. Je suis sûr que vous êtes nombreux dans ce cas, et que certaines réticences vous seront familières :

Je n'ai pas besoin de ce que fait Grunt
Probablement que si, en fait. Relisez la liste ci-dessus. Ce ne sont pas juste des ajouts sympas, ce sont des éléments vitaux du développement web aujourd'hui. Si vous faites déjà tout cela, c'est super. Peut-être avec des outils différents. Grunt peut vous aider à les réunir tous sous le même toit pour ainsi dire. Si vous ne faites pas tout cela, vous devriez sans doute, et Grunt est là pour vous y aider. Et lorsque vous aurez intégré ces tâches, vous pourrez en ajouter d'autres et Grunt vous rendra meilleur dans votre job.

Grunt fonctionne avec Node.js - Je ne connais pas Node
Vous n'avez pas besoin de connaître Node. De la même façon que vous n'avez pas besoin de connaître Ruby pour utiliser Sass, ou PHP pour utiliser WordPress, ou C++ pour écrire avec Word.

J'ai d'autres façons de faire tout cela
Sont-elles bien organisées en un endroit unique, configurées pour fonctionner automatiquement lorsque de besoin, partagées entre toutes les personnes qui travaillent sur votre projet ?

Grunt fonctionne avec la console - Je ne suis qu'un designer
Je suis designer, moi aussi. Je préfère les applis natives avec des interfaces graphiques. Mais je ne pense pas que cela se produira pour Grunt.

Voici tout ce que vous aurez besoin de faire sur la console :

  1. Naviguer jusqu'au dossier où se trouve votre projet.
  2. taper grunt et valider.

Après l'installation initiale bien sûr, qui vous allez le voir est très simple.

NdT : Après lecture de La console, une introduction pour les web designers, vous en saurez suffisamment pour vous débrouiller tous les jours avec la console.

Ok, on installe Grunt

Node est donc un prérequis pour Grunt. Si vous ne l'avez pas déjà installé, rien de plus simple : cliquez sur le bouton Install dans le site de Node.

On va installer Grunt à chaque fois pour un projet particulier. Dans le dossier de votre projet, créez un fichier package.json. Il suffit de le créer et de le laisser là.


vue du dossier avec package.json
package.json à la racine de votre dossier

Le contenu de ce fichier doit être au départ :

{
    "name": "exemple-de-projet",
    "version": "0.1.0",
    "devDependencies": {
        "grunt": "~0.4.1"
    }
}

Vous pouvez choisir le nom de votre projet et la version, mais devDependencies doit être écrit comme ci-dessus.

C'est comme cela que Node crée ses dépendances (dependencies). Node a un package manager qui s'appelle NPM (Node packaged modules) pour gérer ses dépendances (un peu comme un gem pour Ruby si vous connaissez mieux). Vous pourriez le voir comme un plug-in pour Wordpress.

Une fois le fichier package.json en place, allez sur votre console (dans Mac elle s'appelle Terminal et se trouve dans Applications/Utilitaires) et naviguez jusqu'à votre dossier. Les ploucs de la console comme moi font comme ça : taper cd suivi d'un espace puis cliquer sur le dossier, le déplacer vers la console et faire enter.

une vue animée de la console
Vue du plouc en action sur ma console

Puis tapez la commande :

npm install

NdT : Si vous êtes sur Mac et que cette commande vous renvoie un message d'erreur, vous devrez sans doute essayer d'utiliser sudo avant npm install : tapez sudo npm install. La console vous demandera d'entrer votre mot de passe, c'est celui que vous utilisez pour entrer sur votre ordi (si vous en avez mis un), ou par exemple pour installer une application. Il ne s'affichera pas lorsque vous le tapez, pas d'inquiétude, c'est normal, et si vous vous êtes trompé il vous invitera à l'entrer de nouveau.

Après avoir tapé cette commande, un nouveau dossier appelé node_modules apparaît dans le dossier de votre projet.

dossier node modules dans mon dossier projet
Exemple de dossier node_modules dans un projet

Les autres fichiers que vous voyez dans le projet, README.md et LICENSE sont là parce que je vais mettre le projet sur GitHub.

La dernière étape est l'installation de Grunt CLI (command line interface), c'est ce qui fait fonctionner la commande grunt dans la console. Sans elle, si vous tapez grunt vous aurez une erreur du genre “Command Not Found”. L'installation se fait à part, et une fois pour toutes, pour des raisons d'efficacité. Sinon, vous installeriez 10 fois Grunt CLI si vous aviez 10 projet.

Pour l'installer globalement, le plus simple est d'ouvrir une nouvelle fenêtre de votre console, afin de retrouver la ligne de commande que vous aviez avant de faire l'installation du plouc.

Là encore, c'est juste l'affaire d'une ligne. Dans votre console, tapez :

npm install -g grunt-cli

Après ça, fermez la console et réouvrez la. C'est une bonne pratique, pour être sûr que les choses fonctionnent correctement. Un peu comme de redémarrer votre ordinateur après avoir installé une application, dans le bon vieux temps.

Grunt au boulot ! Concaténation de fichiers

Admettons que nous ayons 3 fichiers JavaScript séparés dans notre projet:

  1. jquery.js - la bibliothèque que nous utilisons.
  2. carousel.js - un plug-in jQuery importé.
  3. global.js - Notre fichier JavaScript dans lequel nous configurons et importons le plug-in.

En production, nous devrions concaténer ces fichiers pour des raisons de performance (une requête vaut mieux que trois). Nous allons demander à Grunt de le faire pour nous.

Mais, attendez une minute : en fait, Grunt ne fait rien tout seul. Rappelez-vous, Grunt est un task-runner, il accomplit des tâches, mais encore faut-il qu'on lui dise lesquelles. Nous n'avons pas encore ajouté de tâches, allons-y.

Le plug-in officiel de Grunt pour concaténer des fichiers est grunt-contrib-concat. Vous pouvez trouver des infos à son sujet sur GitHub si vous voulez, mais pour vous en servir dans votre projet tout ce que vous avez à faire c'est de taper la commande suivante dans votre console (il va sans dire vous devez taper cette commande depuis le dossier racine de votre projet, comme précédemment) :

npm install grunt-contrib-concat --save-dev

Un truc sympa avec cette façon de faire : votre fichier package.json est mis à jour automatiquement pour intégrer cette dépendance. Ouvrez-le, vous verrez une nouvelle ligne :

"grunt-contrib-concat": "~0.3.0"

Maintenant nous pouvons l'utiliser. Pour cela, nous devons configurer Grunt pour lui dire ce qu'il doit faire. Ça se passe dans le fichier Gruntfile.js. Pour l'instant ce fichier n'existe pas, il faut le créer à la racine de votre dossier.

Tout comme notre fichier package.json, notre Gruntfile.js a un format bien particulier qu'il faut respecter. Ne vous inquiétez pas trop de ce que chaque mot signifie, regardez juste le format :

module.exports = function(grunt) {

// 1. Toutes les configurations vont ici: 
grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    concat: {
        // 2. la configuration pour la concaténation va ici.
    }

});

// 3. Nous disons à Grunt que nous voulons utiliser ce plug-in.
grunt.loadNpmTasks('grunt-contrib-concat');

// 4. Nous disons à Grunt quoi faire lorsque nous tapons "grunt" dans la console.
grunt.registerTask('default', ['concat']);

};

Allons-y maintenant pour la configuration. La documentation peut être impressionnante alors concentrons-nous sur l'exemple simple d'utilisation.

Vous vous rappelez que nous avons trois fichiers JavaScript à concaténer. Nous allons lister les chemins d'accès aux fichier dans src (source) sous forme de table (array) de chemins, et nous donnerons un fichier de destination dans dest. Le fichier de destination n'a pas besoin d'exister pour l'instant, il sera créé lorsque la tâche sera exécutée.

Nos deux fichiers jquery.js et carousel.js sont des bibliothèques, nous n'allons pas les modifier. Pour la clarté de l'organisation, nous allons les ranger dans un dossier /js/libs/. C'est dans notre fichier global.js que nous écrivons notre code, donc nous allons le ranger dans le dossier /js/. Demandons maintenant à Grunt de nous trouver tous ces fichiers et de les compresser en un fichier unique appelé production.js, dénomination qui indique qu'il sera utilisé pour la version en ligne de notre site.

concat: {   
    dist: {
        src: [
            'js/libs/*.js', // tous les JS dans libs
            'js/global.js'  // ce fichier là
        ],
        dest: 'js/build/production.js'
    }
}

Note : tout au long de cet article il y aura des petits bouts de code comme celui-ci. Je voudrais me focaliser sur les parties importantes, mais on ne comprend pas toujours bien comment un bout de code s'intègre dans un ensemble plus grand. Si vous voulez voir la partie et le tout, vous pouvez vous reporter au fichier complet.

Une fois la configuration en place, ouvrez la console, tapez :

grunt

et admirez la merveille ! production.js est créé, et contient une concaténation parfaite de nos trois fichiers. Ça a été un grand moment de “aha!” pour moi la première fois.


Grunt au boulot ! Minifier JavaScript

Tout le travail de préparation est achevé, l'addition de nouvelles tâches est un jeu d'enfant. Il nous suffit de :

  1. Trouver un plug-in Grunt pour effectuer la tâche.
  2. Apprendre à configurer ce plug-in.
  3. Écrire cette configuration pour la faire fonctionner dans notre projet.

Le plug-in officiel pour la minification est grunt-contrib-uglify. Comme d'habitude, nous utilisons une commande NPM pour l'installer :

npm install grunt-contrib-uglify --save-dev

Puis nous changeons notre Gruntfile.js pour qu'il puisse charger le plug-in :

grunt.loadNpmTasks('grunt-contrib-uglify');

Puis nous le configurons :

uglify: {
    build: {
        src: 'js/build/production.js',
        dest: 'js/build/production.min.js'
    }
}

Puis nous mettons à jour la tâche default afin qu'elle intègre aussi la minification :

grunt.registerTask('default', ['concat', 'uglify']);

Pas vraiment différent du réglage de la concaténation, n'est-ce pas ?

Tapez grunt dans la console et vous obtiendrez un JavaScript délicieusement minifié :

un exemple de javascript minifié
JavaScript minifié

Ce fichier production.min.js est ce que nous chargerons en production pour utilisation dans notre fichier index.html.

Grunt au boulot ! Optimisation des images

À présent nous maîtrisons bien le sujet. Continuons sur notre lancée. Le plug-in officiel de Grunt pour la minification des images est grunt-contrib-imagemin. Installons-le :

npm install grunt-contrib-imagemin --save-dev

Enregistrons-le dans Gruntfile.js :

grunt.loadNpmTasks('grunt-contrib-imagemin');

Configurons-le :

imagemin: {
    dynamic: {
        files: [{
            expand: true,
            cwd: 'images/',
            src: ['**/*.{png,jpg,gif}'],
            dest: 'images/build/'
        }]
    }
}

Et finalement :

grunt.registerTask('default', ['concat', 'uglify', 'imagemin']);

Tapez gruntdans la console et voyez la belle compression :

un exemple de compression d'images
Images compressées

Super amélioration des performances pour à peu près zéro effort.

Soyons malins, automatisons

Jusqu'ici, ce que nous avons fait est super et incroyablement utile. Mais il y a encore des choses qu'on pourrait faire plus intelligemment et qui nous faciliteraient encore plus la vie :

  1. Effectuer ces tâches automatiquement
  2. Ne le faire que si besoin

Par exemple :

  1. Concaténer et minifier JavaScript lorsqu'il est modifié.
  2. Optimiser les images lorsqu'une nouvelle image est ajoutée ou lorsqu'une image existante est modifiée.

Nous pouvons le faire en surveillant (watch) les fichiers. Nous pouvons demander à Grunt de garder un oeil sur tel ou tel fichier et d'effectuer des tâches spécifiques lorsqu'il détecte un changement. Tout cela est possible grâce au plug-in officiel de Grunt grunt-contrib-watch.

Je vous laisse l'installer, c'est exactement le même processus que les deux derniers plug-ins que nous avons installés.

Nous le configurons en lui indiquant des fichiers à surveiller. Par surveiller, j'entends suivre les changements, suppressions ou ajouts de fichiers. Puis nous lui disons quoi faire lorsqu'il détecte un changement.

Nous voulons que la tâche de concaténation et minification soit lancée à chaque fois qu'il y a un changement dans le dossier /js/. Lorsque des choses arrivent ailleurs, nous ne voulons pas que cette tâche soit lancée :

watch: {
    scripts: {
        files: ['js/*.js'],
        tasks: ['concat', 'uglify'],
        options: {
            spawn: false,
        },
    } 
}

Simple et confortable pour l'instant, non ? La seule chose un peu bizarre c'est ce spawn. Et vous savez quoi ? Je ne sais pas vraiment à quoi ça sert. D'après ce que je comprends de la documentation, il s'agirait d'une valeur par défaut. Le développement c'est souvent cela, laisser tomber si ça fonctionne, approfondir le sujet si ça ne marche pas.

Notez que grunt-watch continuera à fonctionner tant que la console n'est pas fermée ou qu'on ne l'arrête pas (ce qu'on peut faire avec Ctrl+C sur un Mac).

Note : C'est vraiment frustrant lorsque quelque chose qui semble si simple dans un tutoriel ne fonctionne pas pour nous. Si vous n'arrivez pas à faire marcher Grunt après une modification, c'est très probablement à cause d'une erreur de syntaxe dans votre fichier Gruntfile.js. Ça pourrait ressembler à quelque chose comme ça dans votre console :

messages d'erreur dans la console
Messages d'erreur dans la console

En général Grunt est assez précis dans la description de ce qui ne va pas, lisez bien le message d'erreur. Dans l'exemple ci-dessus, j'ai eu un message d'erreur pour une virgule manquante. L'ajout de la virgule a résolu le problème.

Utiliser Grunt pour le préprocessing

Il reste encore un point dans notre liste de début d'article, c'est l'utilisation de Sass - encore une tâche pour laquelle Grunt est bien adapté. Mais... un instant : Sass est dans Ruby ? Eh oui, c'est vrai, mais il y a une version de Sass qui marche dans Node et évite ainsi d'ajouter une nouvelle dépendance à notre projet. Elle est encore en développement, alors nous utiliserons le plug-in officiel de Grunt, grunt-contrib-sass, qui part du principe que vous avez Sass installé sur votre machine. Si ce n'est pas le cas, suivez les instructions avec la console (command line).

Sass fait la concaténation et la minification tout seul, donc pour notre projet nous avons juste besoin de compiler notre fichier principal global.scss :

sass: {
    dist: {
        options: {
            style: 'compressed'
        },
        files: {
            'css/build/global.css': 'css/global.scss'
        }
    } 
}

Nous n'avons pas envie de gérer cette tâche manuellement, et comme nous avons déjà le plug-in watch installé, nous allons l'utiliser ! À l'intérieur de la configuration watch, nous allons ajouter une nouvelle tâche :

css: {
    files: ['css/*.scss'],
    tasks: ['sass'],
    options: {
        spawn: false,
    }
}

Ça devrait le faire. À partir de maintenant, à chaque fois que nous modifierons l'un de nos fichiers Sass, le CSS sera automatiquement mis à jour.

Allons un peu plus loin (ça en vaut absolument la peine !) et ajoutons LiveReload. Avec LiveReload, vous n'aurez pas besoin de revenir à votre navigateur pour rafraîchir la page. Le rafraîchissement est automatique et dans le cas de CSS les nouveaux styles sont injectés sans avoir besoin de rafraîchir la page.

C'est très facile à installer, la fonctionnalité LiveReload s'intégrant dans le plug-in watch. Pour cela :

  1. Installez le plug-in navigateur.
  2. Ajoutez au début de la configuration watch :

    watch: {
        options: {
            livereload: true,
        },
        scripts: {   
        /* etc */
    
  3. Redémarrez votre navigateur et cliquez sur l'icône LiveReload pour l'activer.
  4. Mettez à jour un fichier Sass et observez le changement immédiat à l'écran.
changements instantanés dans le navigateur
Les changements s'effectuent en live !

(NdT: en cas de problème avec LiveReload, voir notes du traducteur ci-dessous).

Si vous préférez une vidéo sur ce sujet (en anglais), j'ai préparé un petit screencast pour accompagner cet article : CSS-Tricks: First moments with Grunt.


Notes du Traducteur :

Après avoir lu pas mal de documents et guides sur Grunt, l'article d'intro de Chris Coyier a été le fameux moment de “aha!”. Cependant j'ai encore noté pas mal de choses :

  • l'utilisation de watch me renvoyait une “erreur sur le bus 10”. Il s'agit d'un bug corrigé dans Node, pour l'éviter il faut mettre à jour votre version de Node. La dernière version stable s'obtient ainsi :

sudo npm install n -g
sudo n stable

(l'utilisation de sudo impose que vous entriez votre mot de passe, c'est le même que lorsque vous installez un programme. Le mot de passe ne s'affichera pas quand vous le tapez, pas d'inquiétude c'est normal).

  • LiveReload ne marchait pas. En fait, LiveReload fonctionne par défaut sur le port 35729, si vous rencontrez le même problème il suffit donc d'insérer le script suivant dans la partie head de votre HTML :
    <script src="http://localhost:35729/livereload.js"></script>

  • Si vous avez beaucoup de tâches, plutôt que de les enregistrer individuellement vous pouvez les enregistrer avec une ligne de code unique, à insérer dans gruntfile.js avant grunt.initConfig :

require("matchdep").filterDev("grunt-*").forEach(grunt.loadNpmTasks);

Il faut pour cela installer le module matchdep de la même façon que les plugins :

npm install matchdep --save-dev


Ressources complémentaires en anglais
dans l'ordre croissant de difficulté - et donc d'intérêt :)

Getting started with Grunt article d'intro à Grunt, par Matt West (Treehouse blog)
Get up and running with Grunt mettre en route Grunt, par Mike Cunsolo
Grunt Boilerplate par Integralist
Advanced Grunt tooling de Chris Wren, pour approfondir Grunt

Ressources complémentaires en français

Automatiser son workflow avec Grunt de Ronan Levesque
Introduction à Grunt, vidéo par Grafikart
Premiers pas avec Grunt, par Putain de Code


original paru le dans 24 Ways.

Sur l'auteur : est designer, blogger, conférencier. Créateur de CSS-Tricks, de CodePen, animateur du Podcast Shop Talk, il est l'auteur avec Jeff Starr de "Digging into WordPress". Vous pouvez le retrouver sur Twitter, Google+.

NdT : CSS-Tricks est un site ressource d'une richesse exceptionnelle, axé sur CSS et le web design en général. Vous pouvez suivre son actualité sur Twitter, Facebook, YouTube, GitHub. Mélange d'articles, de vidéos, de forums, de ressources diverses (jetez un coup d'oeil à son "Almanac" ), il est très populaire sur le web anglophone.

CodePen est un site où vous pouvez tester votre code, faire des maquettes, proposer vos dernières créations et recueillir les observations de vos pairs. C'est aussi une source d'inspiration pour vos propres designs.

Traduit avec l'aimable permission de 24 Ways et de l'auteur.
Copyright 24 Ways © 2013.