Skip to content

Instantly share code, notes, and snippets.

@jgrenat
Created May 14, 2014 15:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jgrenat/cddaccc4f847278e7d11 to your computer and use it in GitHub Desktop.
Save jgrenat/cddaccc4f847278e7d11 to your computer and use it in GitHub Desktop.
Grunt technique

Grunt : un lanceur de tâches javascript (2/3)

Introduction

Dans le précédent article, je présentais Grunt d'un point de vue fonctionnel, sans rentrer dans les détails techniques. Ce second article va donc s'intéresser plus directement à l'installation et à la configuration de Grunt pour parvenir à réaliser ses tâches et workflows personnalisés afin de faciliter au mieux le développement du projet. Plus tard, un troisième article vous apprendra comment définir vos propres tâches complexes.

Pour des raisons pratiques, nous allons utiliser comme fil rouge la mise en place des workflows d'une application web présente sous la forme d'une unique page index.html avec du javascript et du CSS.

Mise en place

Prérequis

Le projet

Il est temps de commencer un nouveau projet ! Créez pour cela un répertoire dédié :

mkdir myAwesomeApp && cd $_

Nous allons maintenant créer notre application web simpliste. Créez les fichiers suivants dans l'arborescence :

./index.html

<!doctype html>
<html>
    <head>
        <link rel="stylesheet" href="css/main.css" type="text/css" />
    </head>
    <body>
        <h1>Page web simple</h1>
        <p>Cette page inclut des fichiers CSS et javascript</p>
        <p>Petite addition : 5 + 7 = <input id="addResult" type="text" /></p>
		<p>Petite soustraction : 12 - 8 = <input id="subResult" type="text" /></p>
		
        <script src="js/add.js"></script>
        <script src="js/substract.js"></script>
		<script>
			document.addEventListener('DOMContentLoaded', function() {
				document.getElementById('addResult').value = add(5, 7);
				document.getElementById('subResult').value = substract(12, 8);
			});
		</script>
    </body>
</html>

./css/main.css

h1 {
  border-left: 6px solid green;
  border-bottom: 3px solid green;
  padding-left: 6px;
  padding-bottom: 3px; 
}

./js/add.js

function add(x, y) {
  return x + y;
}

./js/substract.js

function substract(x, y) {
  return x - y;
}

Installer Grunt

Maintenant que nous avons notre application basique, nous allons pouvoir nous concentrer sur Grunt en lui-même. Il y a deux éléments à installer. Tout d'abord, installons le package global (sans oublier d'installer Node.js si ça n'est pas déjà fait) :

sudo apt-get install nodejs
npm install -g grunt-cli

Puis nous allons installer le package local, en n'oubliant pas de créer au passage un fichier package.json pour stocker nos dépendances.

npm init
# Répondez aux quelques questions simples pour créer le package.json
npm install grunt --save-dev
# Le save-dev permet d'ajouter la dépendance dans le package.json

Le GruntFile

Maintenant que tout est installé, il est temps de commencer à configurer Grunt ! Tout se passe dans un seul fichier, le fichier Gruntfile.js. Celui-ci va définir toutes les tâches qui pourront être exécutées. Et le contenu minimal de ce fichier tient sur... une ligne !

module.exports = function(grunt) {};

Bon, bien évidemment, un fichier aussi minimal ne permet de lancer aucune tâche, mais c'est dans cette simple fonction que nous allons oeuvrer. On peut remarquer l'objet grunt passé en argument qui expose de nombreuses fonctionnalités que nous allons pouvoir utiliser pour configurer Grunt.

La première que nous allons voir, grunt.initConfig(), est la plus importante, puisqu'elle va nous permettre de configurer chacun des plugins que nous allons utiliser par la suite. Elle prend en argument un simple objet qui contiendra les paramètres à passer aux plugins (Grunt privilégie la configuration au code) :

module.exports = function(grunt) {
    grunt.initConfig({
        // Configuration des plugins
    });
};

Charger les plugins Grunt

Un plugin Grunt doit être chargé avec grunt.loadNpmTasks('nom-du-plugin');. Pour nous simplifier la vie, il existe un plugin qui va charger automatiquement tous les modules présents dans notre package.json dont le nom commence par grunt-, ce qui représente la quasi-totalité des plugins grunt. Commençons par l'installer :

npm install load-grunt-tasks --save-dev

Puis nous lançons simplement le plugin :

module.exports = function (grunt) {
    // Charge les plugins Grunt automatiquement
    require('load-grunt-tasks')(grunt);
    
    grunt.initConfig({
        // Configuration des plugins
    });
};

Maintenant que tout est installé, nous allons pouvoir réfléchir à ce que nous voulons faire.

Lorsqu'on souhaite envoyer notre site en production, nous allons assembler et minifier nos fichiers CSS (même s'il n'y en a qu'un pour l'instant). Puis nous allons minifier et regrouper en un seul fichier nos ressources javascript et donc remplacer automatiquement les scripts inclus dans notre page index.html. Tout cela permettra de générer un dossier dist contenant les sources de notre application.

Configuration de notre GruntFile

Créer nos tâches principales

Nous allons donc avoir une tâche principale qui, par convention, s'appelera build et qui sera chargée de générer ce dossier dist prêt pour une mise en production.

Avec Grunt, cela se fait très facilement grâce à la commande grunt.registerTask qui prend comme paramètres le nom de la tâche à créer, et la liste des tâches à appeler.

module.exports = function (grunt) {
    // Charge les plugins Grunt automatiquement
    require('load-grunt-tasks')(grunt);
    
    grunt.initConfig({
        // Configuration des plugins
    });
    
    // La liste de tâches est vide, nous la remplirons par la suite.
    grunt.registerTask('build', []);
};

Attention ! Il convient, avant de lire la suite, d'étudier la façon dont sont représentés les fichiers. En effet, pour faciliter le développement et l'utilisation des plugins, Grunt met en place une abstraction très performante pour cibler ceux-ci. Les explications justifieraient à elle seule l'écriture d'un article, je vous recommande donc de consulter la documentation à ce sujet pour appréhender tous les différents formats utilisables et leurs possibilités.

Copier les fichiers

Pour générer un dossier dist contenant notre site prêt à être mis en production, il va nous falloir copier tous les fichiers dont nous avons besoin. Cela peut être réalisé très simplement grâce à la tâche copy. Installons-la directement :

npm install --save-dev grunt-contrib-copy

Il nous suffit simplement d'indiquer les fichiers à copier et la destination. Nous ne prenons pas en compte ici les fichiers javascript et CSS qui seront copiés d'une autre façon. Voici le rendu de la configuration :

copy: {
    dist: {
    	src: ['*.html', 'images/**/*'], 
    	// Vous pouvez bien sûr rajouter d'autres fichiers selon les besoins
    	dest: 'dist/'
    }
}
grunt.registerTask('build', ['copy:dist']);

A chaque appel à la tâche build, nos fichiers vont être copiés dans le répertoire dist/. Mais cela signifie également qu'à chaque appel, les fichiers précédemment copiés seront toujours là ! En général, cela ne pose pas de soucis, mais si nous supprimons un fichier dans notre projet, il y a fort à parier qu'il sera toujours présent dans le répertoire dist/ par la suite !

Il faut donc également penser à vider le répertoire avant de copier les fichiers, ce qui se fait très facilement avec la tâche clean, que nous allons installer et configurer très facilement :

npm install --save-dev grunt-contrib-clean
clean: {
    dist: ['dist/*']
}
// On appelle la sous-tâche "dist" de la tâche "concat".
grunt.registerTask('build', ['clean:dist', 'copy:dist']);

Regrouper et minifier les fichiers CSS

La tâche cssmin permet de regrouper les fichiers très simplement. Cela doit devenir un automatisme, nous allons installer le module npm correspondant :

npm install --save-dev grunt-contrib-cssmin

En nous penchant sur la documentation, on voit la syntaxe :

cssmin: {
    dist: { // Le nom de notre sous-tâche est "dist"
        files: {
            'dist/public/global.min.css': ['css/**/*.css'] 
            // On regroupe tous les fichiers CSS minifiés dans un global.min.css
        }
    }
}

Il ne faut maintenant pas oublier d'appeler la tâche lorsqu'on appelle la tâche build :

// On appelle la sous-tâche "dist" de la tâche "concat".
grunt.registerTask('build', ['clean:dist', 'copy:dist', 'cssmin:dist']);

Regrouper et minifier les fichiers Javascript

L'équivalent de la tâche cssmin pour le javascript est uglify. Aucune surprise, le fonctionnement est très semblable :

npm install --save-dev grunt-contrib-uglify
uglify: {
    dist: {
        files: {
            'dist/public/scripts.min.js': ['js/**/*.js'] 
        }
    }
}
grunt.registerTask('build', ['clean:dist', 'copy:dist', 'cssmin:dist', 'uglify:dist']);

Nous avons dorénavant nos fichiers javascript et css minifiés... Mais il reste un problème ! En effet, nos fichiers sont bien créés, mais ce sont toujours les anciens fichiers non minifiés qui sont inclus dans nos pages ! Nous allons voir maintenant comment rectifier cela.

Modifier les fichiers inclus

Voici le problème qui se pose : nous devons analyser le code source de nos pages pour retirer les inclusions de script et de css et les remplacer par des inclusions de nos fichiers uniques minifiés.

Une fois encore, un simple plugin va nous simplifier la vie ! Il s'agit cette fois-ci du plugin usemin. Je vous invite à consulter la documentation pour ce plugin, mais voici en résumé ce qu'il fait :

  • Une tâche useminPrepare va analyser le code des fichiers spécifiés à la recherche de blocs
<!-- build:{type} {destination} --> 
{...} 
<!-- endbuild -->
  • Cette tâche configure ensuite les tâches précisées (par défaut : concat puis uglify pour le JS, concat et cssmin pour le CSS) pour mettre en place un workflow de changements
  • La tâche usemin remplace ensuite les blocs par la référence vers le bon fichier

Cela signifie entre autre que nous n'aurons plus besoin des configurations des tâches uglify et cssmin que nous avons mises en place plus haut. Enlevez-les donc ! Dorénavant, c'est usemin qui va s'en occuper ! Passons tout de suite à la mise en place :

npm install --save-dev grunt-usemin

Dans le index.html, formez vos blocs de la façon suivante :

...

<!-- build:css public/global.css -->
<link rel="stylesheet" href="css/main.css" type="text/css" />
<!-- endbuild -->

...

<!-- build:js public/scripts.js -->
<script src="js/add.js"></script>
<script src="js/substract.js"></script>
<!-- endbuild -->

...

On configure ensuite nos workflows et les fichiers à analyser à la recherche des blocs :

useminPrepare: {
    html: {
	    src: ['dist/index.html']
    },
    options: {
		flow: {
			steps: {
			    js: ['uglifyjs'],
			    css: ['cssmin'],
			},
			post: {}
		}
    }
}

Puis on indique où remplacer les blocs à usemin :

usemin: {
    html: 'dist/index.html'
}

Enfin, on configure notre tâche build :

grunt.registerTask('build', [
    'clean:dist', 
    'copy:dist',
    'useminPrepare',
    'cssmin', 
    'uglify',
    'usemin'
]);

Il suffit alors de lancer notre tâche grunt en se plaçant dans le répertoire principal lorsque l'on souhaite effectuer une mise en production :

grunt build

Conclusion

L'ensemble des sources de cet article sont disponibles sur ce dépôt Git.

Nous avons vu ici comment mettre en place une tâche de mise en production pour noter application, mais il faut garder à l'esprit que les possibilités de Grunt sont presque infinies !

Comme nous l'avons constaté, il existe de très nombreux plugins permettant d'effectuer presque toutes les tâches possibles et imaginables. Il existe cependant des cas où ceux-ci ne suffiront pas.

Dans cette optique, nous verrons dans un troisième article la manière de créer nos propres tâches Grunt personnalisées.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment