Intégrer Grunt JS dans Symfony et remplacer Assetic

Intégrer Grunt JS dans Symfony et remplacer Assetic

Je connaissais Grunt JS sans l’avoir jamais utilisé mais il aura fallu que j’entende un développeur Symfony m’en faire la messe pour que je me penche sur cette solution. Dans un premier temps, remplacer Assetic par Grunt JS était pour moi une solution pour faire fonctionner mes projets Symfony avec HHVM. Désormais le serveur PHP de Facebook fonctionne parfaitement avec Assetic mais je trouve de moins en moins de raisons de continuer à utiliser Assetic. Pour finir, l’utilisation de Grunt, un plugin basé sur Node JS, pousse la plupart des développeurs à se servir de Bower, un autre plugin permettant de gérer les dépendances. Nous reparlerons de Bower plus bas. Faisons un bref tour des pour et des contre.

Attention ! Avant de vous lancer dans la migration de votre application, je vous conseille de versionner votre code et de faire quelques essais. Lire l’intégralité de l’article avant de vous lancer dans cette mission de longue haleine est fortement conseillé.

Introduction

Assetic, l’ami mal compris

Pour ma part, je considère qu’Assetic est mal compris et mal utiliser. Je vais vous exposer ma pensée. (Par ailleurs, j’ai corrigé cet article suite à une incompréhension de ma part, merci à Manuel Klein pour ses retours).

Avantage

L’utilisation d’Assetic permet d’annuler le cache sur les ressources car le nom des ressources JS et CSS générés change à chaque fois que vous lancerez la commande “assetic:dump”. Un avantage certains !

Inconvénients
  • Assetic propose peu d’options pour configurer la manière dont sont générés les ressources.
  • Il est nécessaire d’installer des bundles PHP pour générer des ressources exotiques comme le LESS. Le code PHP est installé dans les vendors alors que cette tâche n’est utile que pour le client. Non pour le serveur. De plus cela augmente le nombre de votre projet.
  • Du code PHP est nécessaire dans les templates Twig utilisant Assetic soit quasiment tous. Tandis qu’avec Grunt, la ressource compilé sera appelé via le nom que vous lui aurez donné.

Réellement se servir d’Assetic dans ses templates

Je vois souvent dans les templates Twig que je croise, des ressources liées tantôt via Assetic, tantôt via les assets de Symfony.

1
2
3
4
5
# Utilisation d'Assetic
@NamespaceMyBundle/Resources/public/css/main.css
# Assetic inutilisé, utilisation des Assets
/bundles/namespacemy/css/main.css

Dans le premier cas, Assetic s’applique vraiment. Dans le deuxième cas, Assetic est complètement occulté pour ne se servir des assets de Symfony. L’utilisation des assets est d’ailleurs une erreur selon moi, du moins pour l’accès aux ressources JS et CSS. Fin de la parenthèse…

Grunt JS le compilateur Sooo flexible !

Grunt JS repose sur Node.js. C’est un outils côté client qui va grâce à des modules permettre d’effectuer de nombreuses actions telle que la concaténation de fichiers, minification, compression d’image, compilation de LESS, TypeScript… Contrairement à Assetic, vous devrez mettre en place certaines configurations mais rassurez-vous, cet investissement initial sera payant et au final bien plus rapide qu’Assetic.

Initiation à Bower, le composer.json des ressources

Grunt JS est bien et vient se placer sur le même segment qu’Assetic. Mais tant qu’à utiliser Node.js, autant se servir de Bower ! C’est un gestionnaire de dépendances. Pour faire simple, vous définissez les ressources dont vous avez besoin, « telle librairie dans telle version » et Bower s’occupe de récupérer la version que vous souhaitez. Bien évidement, tout comme composer.json pour PHP, vous pouvez ajouter des plages de version. Si vous voulez une librairie dans sa version 3.3., *Bower vous mettra la dernière version disponible. Une solution permettant de faire facilement des mises à jour de ces ressources. Un problème récurrent sur nos projets standards car nous avons (avions !) tendance à télécharger une version et de ne jamais la mettre à jour la suite. Nous n’avions pas les mises à jour mineures qui sont parfois si utiles.

Techniquement, Bower utilise le fichier bower.json. Sa syntaxe est très simple. Je vous incite à l’utiliser même si vous pouvez continuer sans. Le fichier devra être versionné contrairement aux ressources que Bower viendra télécharger pour vous. Pour vous lancer je vous donne un exemple plus bas.

Versionnage des ressources compilés

Pour ma part, j’avais pris l’habitude de ne pas versionner mes ressources compilés pour Assetic mais j’ai décidé de faire l’inverse pour Grunt JS. Puisque je me passe de ressources gérer par Bower (voir Initiation à Bower), je vais compiler mes ressources et les gitters. De cette manière, je pourrai déployer rapidement mon application. Je n’ai jamais eu de retour sur cette pratique mais cela me semble respectable car on annule deux opérations au moment de déploiement (récupération des ressources avec Bower et compilation avec Grunt JS), ainsi que la nécessité d’avoir Node.js sur sa machine.

Enlever Assetic de notre projet Symfony

Assetic a été choisie pour être le composant par défaut responsable de la compilation des ressources. Sachez que malgré tout, il est très facile à enlever.

Supprimer les blocs Twig de type javascript et stylesheet. Ils ne sont pas nécessaire. Gardez de côté les fichiers que vous utilisiez. À l’avenir, les fichiers que vous voudriez utiliser seront définis dans le fichier Gruntfile.js. Nous verrons ça un peu plus loin.

1
2
3
4
5
6
7
8
# app/config/config.yml
# Supprimer la configuration de assetic. Il y en avoir également dans config_prod.yml, config_dev.yml et autres fichiers chargés
assetic:
debug: "%kernel.debug%"
use_controller: false
bundles: [ ]
filters:
cssrewrite: ~
1
2
3
4
5
# app/AppKernel.php
# Arrêter le chargement de AsseticBundle en supprimant la ligne de AppKernel
...
new Symfony\Bundle\AsseticBundle\AsseticBundle()
...
1
2
3
4
5
# composer.json
# Enlever le bundle assetic
...
"symfony/assetic-bundle": "~2.3"
...

Et voilà, vous avez désactivé Assetic de manière global. De cette manière, n’espérez plus trouver la commande assetic:dump et assetic:watch.

Comment va fonctionner Grunt JS au sein de notre projet

Nous allons devoir configurer notre projet avec trois fichiers :

  • package.json qui va nous permettre d’installer les plugins Node.js (Grunt JS et ses contributions) ;
  • bower.json pour faire l’état des dépendances à résoudre pour pouvoir compiler nos ressources ;
  • Gruntfile.js sur lequel vous allez travailler le plus. Il contiendra toutes les actions possibles et/ou à réaliser pour compiler une partie ou l’intégralité de votre projet.

package.json pour installer Grunt JS

1
2
3
4
5
6
7
8
9
10
11
{
"dependencies": {
"grunt": "^0.4.5",
"grunt-contrib-less": "~0.11.0",
"grunt-contrib-uglify": "^0.6.0",
"load-grunt-tasks": "^0.6.0"
},
"devDependencies": {
"grunt": "^0.4.5"
}
}

Dans un premier temps, utilisez ce contenu. Il requiert l’installation de Grunt JS et de contributions, dont deux (grunt-contrib-less et grunt-contrib-uglify) vont vous permettre de compiler vos fichiers JavaScript et fichiers Lesscss.

bower.json pour résoudre mes dépendances (bootstrap, font-awesome, …)

1
2
3
4
5
6
7
8
9
10
11
12
{
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"bootstrap": "3.3.*"
}
}

Voici un fichier minimal. Dans mon cas, j’ai demandé à ce que la dernière version de la branche 3.3 de Bootstrap soit téléchargé. Par défaut, quand vous ferez un bower install vos dépendances seront installées dans le dossier bower_components. Via l’utilisation du .bowerrc vous pouvez changer le dossier de destination. Vous trouverez plus d’informations sur le site de Bower.

1
2
# Installer les dépendances grâce à Bower
$ bower install

Gruntfile.js - Là où tout commence

L’intégralité des commandes et actions que vous voudriez ajouter ce fait dans le fichier Gruntfile.js. Entre autres, vous pourrez compiler des fichiers lesscss et sass, concaténer tout type de fichiers, compiler du TypeScript, minifier des fichiers JavaScript, des images ainsi que des fichiers HTML. Vous pourrez aussi redimensionner des images et copier des fichiers (comme des fonts).

Aujourd’hui je vais juste vous montrez comment faire pour compiler des fichiers .less et minifier des fichiers .js. Des actions que j’effectue tous les jours et qui me permette aujourd’hui de facilement utiliser telle ou telle partie de bootstrap. Plutôt que d’utiliser l’intégralité de bootstrap quand vous n’avez besoin que des glyphicons, il peut être intéressant de travailler sur une librairie customisée.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
grunt.initConfig({
less: {
dist: {
options: {
compress: true,
yuicompress: true,
optimization: 2
},
files: {
"web/css/main.css": [
"bower_components/bootsrap/dist/css/bootstrap.css",
"src/Namespace/MyBundle/Resources/public/css/main.less"
]
}
}
},
uglify: {
options: {
mangle: false,
sourceMap: true
},
dist: {
files: {
'web/js/main.js': [
'src/Namespace/MyBundle/Resources/public/js/main.js',
]
}
}
}
});
grunt.registerTask('default', ["less", "uglify"]);
};

Voici un fichier parfaitement fonctionnel. Je l’utilise d’ailleurs sur un projet possédant peu de frame car il s’agit d’une API. Assetic était dans mon cas bien inutile. J’ai donc préféré m’en passer pour utiliser un outils externe au serveur et livrer les ressources parfaitement construites au moment de déploiement.

Décrivons les actions pour Gruntfile.js

Dans le fichier précédent, je configure les deux contributions de Grunt JS (_less _et uglify) que j’ai installé grâce à mon package.json. Vous remarquerez qu’on distingue facilement les plugins les un et des autres. Prenons le cas de l’extension less.

Chaque contribution Grunt que vous allez utilisez doit être déclaré via un mot clé définit pour la contribution. Exemple, pour grunt-contrib-less le mot clé est less. Sous cette partie, on peut définir des targets. Sous le terme target comprenez sous-partie. En cas concret, on pourrait définir un target bootstrap pour compiler la librairie du même nom depuis les sources less. On pourrait définir un autre ensemble dist pour compiler les sources de notre projet. L’utilisation de plusieurs sous ensemble n’est pas une nécessité mais vous aurez l’occasion de vous en servir plus tard lorsque vous utiliserez la contribution watch pour gagner du temps.

Ensuite la structure des sous ensembles d’une même extension sont ISO. À Contrario, la structure de chaque contribution diffère. Les contributions standards sont souvent présentés avec de nombreux exemples. Les plugins officiels sont disponibles sur le repository GitHub.

Reprenons notre exemple et la configuration de la compilation de nos fichiers .less. Dans la partie files nous ajoutons le nom des fichiers finaux en guise de clé, et en valeur, un tableau des fichiers en entrées à compiler. Je pense qu’il n’est pas nécessaire de détailler plus car la syntaxe est assez intuitive.

La contribution uglify utilisée pour la concaténation, la minification et la compression des fichiers JavaScript, reprend la même structure. Dans ce cas, il est assez simple d’utiliser ses deux contributions.

L’art de compiler

Et maintenant, compilons nos sources. Avec Assetic, nous lancions cette commande :

1
php app/console assetic:dump

Désormais, nous avons enlevé AsseticBundle. Nous ne possédons plus cette commande. Nous allons donc exécuter notre plugin de Node.

1
grunt

Vous voyez c’est quand même pas très compliqué… Dans ce cas, les tâches qui vont être exécuté vont être celle donnée pour le profil default. Il a été défini dans la dernière ligne.

1
grunt.registerTask('default', ["less", "uglify"]);

Vous auriez très bien définir un profil javascript qui n’aurait exécuté de uglify.

1
grunt.registerTask('javascript', ['uglify']);

Dans le cas vous auriez pu lancer la commande Grunt avec le nom du profil après.

1
grunt javascript

Vous pouvez définir autant de profil que vous voulez. Dans le cas de mon profil javascript, l’intérêt est moindre car vous auriez également pu faire l’appel suivant.

1
grunt uglify

Conclusion

Maintenant, vous savez compiler vos ressources. Si vous n’avez pas saisie toutes les subtilités, vous devriez rapidement pouvoir apprécier toute la flexibilité qu’offre Grunt vis à vis d’Assetic. Qui plus est, en dehors de ressources types comme les javascript et les css, vous pouvez également traiter les fonts, les images… Un atout qui m’a rapidement converti.

Le réel atout de Grunt est sa flexibilité son utilisation. Grunt et Bower incarne à eux deux une alternative à Assetic. Sans Bower vous pouvez toujours travailler avec Grunt mais l’expérience ne sera pas totale.

J’espère que cet article vous permettra de facilement appréhender l’utilisation de Grunt. Un peu de courage, vous y êtes presque !