Cet article est inspiré et traduit d'un article de Florian Eckerstorfer, accessible en anglais à l'adresse : https://florian.ec/articles/buliding-symfony2-with-gulp/
Assetic, outil largement utilisé dans les projets Symfony pour la gestion des assets, est de plus en plus remplacé par des outils tels que Grunt ou Gulp.
Cet article présente une façon d'utiliser Gulp dans un projet Symfony 2.
Gulp sera utilisé non seulement pour construire les assets, mais également pour lancer les tests, et des outils tels que des tests de couverture de code et checkstyle.
[ toc ]
Tout d'abord, voici les outils qui seront utilisés pour faire fonctionner le projet.
- Gulp pour lancer les différentes tâches (ainsi qu'un certain nombre de plugins et de modules Node.js)
- Bower pour installer et gérer nos assets.
- Sass
- RequireJS pour charger dynamiquement nos fichiers Javascript
- Bootstrap
- jQuery
- PHPUnit
- PHP_CodeSniffer
Chaque projet Symfony qui utilise l'édition standard possède la même structure. Par convention, la commande assets:install
copiera tout ce qui ce trouve dans le répertoire Resources/public
dans le répertoire web
du projet. Nous utiliserons cette convention pour gérer les chemins dans les fichiers Sass, Javascript et Gulp. Voici donc un apperçu de l'arborescence des fichiers de notre projet d'exemple :
- src/Bundle/
- AppBundle/
- Resources/public/
- js/
- sass/
- FrontBundle/
- Resources/public/
- js/
- sass/
- UserBundle/
- Resources/public
- js/
- sass/
- web/
- bundles/
- components/
- css/
- fonts/
- js/
- Gulpfile.js
Lorsque nous exécutons la commande assets:install --symlink
, Symfony crééra des liens symboliques entre le répertoire web/bundles/
et le répértoire public de chaque Bundle. Avec la structure précédement citée, le répertoire web/bundles
ressemblera à ça :
- web/bundles/
- app/
- front/
- user/
le répertoire web/css/
contiendra les fichiers CSS compilés, web/fonts/
les différentes fonts utilisées, web/js/
les fichiers Javascript, et enfin web/coponents/
les fichiers téléchargés par Bower. Nous y reviendrons plus tard.
si vous n'avez jamais entendu parler de Gulp et vous demandez comment l'installer, vous pouvez vous rendre sur ces pages Getting started guide ou Building with Gulp. Pour la faire courte, vous pouvez installer Gulp avec NPM.
Voici les commandes pour installer Gulp globalement et localement dans votre projet :
$ npm install -g gulp
$ npm install --save-dev gulp
Puis, vous pouvez créer un fichier Gulpfile.js
et faire un appel au module gulp
:
var gulp = require('gulp');
gulp.task('default', function () {});
Premièrement, nous allons nous occuper des css dans le projet
Sass propose de nombreuses fonctionnalités qui le rende supérieur au CSS. Une de ces fonctionnalité est la possibilité de définir des variables. Cependant, is l'on compile les différents fichiers Sass séparément et qu'on les concatène ensuite, nous ne pouvons pas référencer les variables depuis différents fichiers sources.
Etant donné que nous ne souhaitons avoir q'un seul fichier Sass, nous allons utiliser la directive import
pour les regrouper tous dans un seul fichier. nous allons placer un master.scss
dans chacun des bundles et importer tous les fichiers .scss
du bundle. Le fichier master.scss
du bundle FrontBundle incluera les fichiers master.scss
de l'ensemble des autres Bundles. Il ressemblera à cela :
// src/Acme/Bundle/FrontBundle/Resources/public/sass/master.scss
@import '../../user/sass/master';
@import '../../app/sass/master';
Avant de parler de l'utilisation de Bootstrap, nous allos évoquer Bower. La première chose à faire pour utiliser Bower dans notre contexte est de changer le répertoire de téléchargement par défauten créant un fichier .bowerrc
.
{
"directory": "web/components"
}
Ensuite, nous installerons bootstrap-sass-official
en utilisant la commande :
bower install --save bootstrap-sass-official
Une fois Bootstrap installé vi Bower, nous allons mettre à jour le fichier master.scss
présent dans FrontBundle
// src/FrontBundle/Resources/public/sass/master.scss
@import '../../../components/bootstrap-sass-official/assets/stylesheets/_bootstrap';
@import '../../acmeuser/sass/master';
@import '../../acmeother/sass/master';
Enfin, nous en arrivons au point ou nous pouvons parler de la compilation des feuilles de style, c'est à dire la conversion des fichiers Sass en CSS. Pour compiler le Sass, nous utiliserons gulp-sass et le portage de Sass en Node.js
npm install --save-dev gulp-sass
Etant donné que nous importons tout ce dont nous avons besoin dans notre fichier master.scss
, le code de la tâche Gulp est très simple :
// Gulpfile.js
var sass = sass = require('gulp-sass');
gulp.task('sass', function () {
gulp.src('./web/bundles/front/sass/master.scss')
.pipe(sass({sourceComments: 'map', errLogToConsole: true}))
.pipe(gulp.dest('./web/css/'));
});
La compilation se fait donc en deux étapes :
Tout d'abord, la publication dans web/bundles/
des fichiers par la commande :
php app/console assets:install --symlink
Ensuite, le regroupement de l'ensemble des fichiers issus des Bundles et de Bootstrap dans le fichier web/css/master.css
via l'exécution de la commande :
gulp sass
Dès lors, nous pouvons faire appel à ce fichier css dans notre template de base :
<!-- app/Resources/views/base.html.twig -->
<link href="{{ asset('/css/master.css') }}" rel="stylesheet">
Tout ce qui concerne les feuilles de style devrait fonctionner maintenant, à l'exception des Glyphicons founirs par Bootstrap.
Bootstrap utilise Glyphicons, une font d'icônes, qui est référencée dans le dossier bootstrap/
. Cependant, les fonts sont situées dans le répertoire web/components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/
et le code CSS dans web/css/
. Nous pourrions changer cela, mais nous avons précisé plus tôt que les fonts seront situées dans le répertoire ``web/fonts/`.
Nous allons donc utiliser le plugin gulp-copy qui propose cette fonctionnalité. Une option prefix
permet de supprimer les répertoires supperflux du chemin d'origine.
Nous pourrons ensuite éditer notre fichier Gulpfile.js
:
// Gulpfile.js
var copy = copy = require('gulp-copy');
gulp.task('fonts', function () {
return gulp.src('./web/components/bootstrap-sass-official/assets/fonts/bootstrap/*')
.pipe(copy('./web/fonts', {prefix: 7}));
});
Cependant, le chemin est toujours incorrect, car à la place de bootstap/
il faudrait que nous ayions ../fonts/
. Heureusement, Bootstrap utilise une variable pour gérer ce chemin et nous pouvons la surcharger en ajoutant une ligne avant d'importer Bootstrap dans notre Sass.
// src/FrontBundle/Resources/public/sass/master.scss
$icon-font-path: '../fonts/';
Le lancement de la commande gulp fonts sass
exécutera à la fois la tâche fonts
et sass
et tout devrait fonctionner.
Nous allons utiliser RequireJS en tant que module et pour charger les fichiers dynamiquement. Le point d'entrée du Javascript sera le fichier app.js
situé dans FrontBundle. Chaque Bundle possède son propre main.js
qui chargera également les fichiers et modules nécessaires.
Le fichier app.js
va également prendre en charge la configuration de jQuery et des plugin jQuery fournis par Bootstrap, car ces derniers ne disposent pas de modules RequireJS définis.
// src/FrontBundle/Resources/public/js/app.js
require.config({
paths: {
'bootstrap': '../../bootstrap',
'jquery': '../../jquery'
},
shim: {
'bootstrap/affix': { deps: ['jquery'], exports: '$.fn.affix' },
'bootstrap/alert': { deps: ['jquery'], exports: '$.fn.alert' },
'bootstrap/button': { deps: ['jquery'], exports: '$.fn.button' },
'bootstrap/carousel': { deps: ['jquery'], exports: '$.fn.carousel' },
'bootstrap/collapse': { deps: ['jquery'], exports: '$.fn.collapse' },
'bootstrap/dropdown': { deps: ['jquery'], exports: '$.fn.dropdown' },
'bootstrap/modal': { deps: ['jquery'], exports: '$.fn.modal' },
'bootstrap/popover': { deps: ['jquery'], exports: '$.fn.popover' },
'bootstrap/scrollspy': { deps: ['jquery'], exports: '$.fn.scrollspy' },
'bootstrap/tab': { deps: ['jquery'], exports: '$.fn.tab' },
'bootstrap/tooltip': { deps: ['jquery'], exports: '$.fn.tooltip' },
'bootstrap/transition': { deps: ['jquery'], exports: '$.fn.transition' }
}
});
require(['main']);
La dernière ligne de ce code doit attirer votre attention : elle indique à RequireJS de charger le fichier main.js
situé dans le même répertoire. Notre projet ne contient pas énormément de javascript, mais voici ce que contient le fichier main.js
:
// src/FrontBundle/Resources/public/js/main.js
define(function (require) {
require(['jquery', 'bootstrap/alert'], function() {
$('.alert').alert();
});
});
Que se passerait il si nous souhaiterions charger du javascript issus d'autres Bundles ? Il suffirait pour cela d'ajouter de nouvelles entrées à l'option paths
du fichier app.js
, puis faire appel à celle-ci à la fin de notre fichier :
// src/FrontBundle/Resources/public/js/app.js
require.config({
paths: {
// ...
'user': '../../user/js'
},
shim: {
// ...
}
});
require(['main', 'user/main']);
Le fichier main.js
de UserBundle peut maintenant faire appel à d'autres modules.
// src/UserBundle/Resources/public/js/main.js
define(function (require) {
require(['jquery'], function() {
$('.alert').addClass('hello-world');
});
});
Finalement, Gulp ne nous servira pas vraiement à compiler notre Javascript, car RequireJS se charge déjà de les concaténer. Nous nous en servirons pour copier ces fichiers dans le répertoire web/js/
.
// Gulpfile.js
gulp.task('js', function() {
gulp.src([
'./web/bundles/*/js/**/*.js',
'./web/components/bootstrap-sass-official/assets/javascripts/bootstrap/*.js',
'./web/components/jquery/dist/jquery.js',
'./web/components/requirejs/require.js'
])
.pipe(gulp.dest('./web/js'));
});
Il peut être assez fastidieux de relancer les tâches sass
et js
à chaque fois que le les fichiers .scss
et .js
sont modifiés. Gulp, comme beaucoups d'outils modernes, intègre une fonctionnalité de surveillance (watch), qui automatise le lancement de ces tâches. De plus, nous allons utiliser LiveReload pour automatiquement actualiser notre navigateur à chaque fois qu'une modification sera effectuée.
Gulp intègre la fonction watch()
par défault. Nous allons utiliser un expression régulère pour différencier la surveillance des fichiers Sass et Javascript.
// Gulpfile.js
gulp.task('watch', function () {
var onChange = function (event) {
console.log('File '+event.path+' has been '+event.type);
};
gulp.watch('./src/*/Resources/public/sass/**/*.scss', ['sass'])
.on('change', onChange);
gulp.watch('./src/*/Resources/public/js/**/*.js', ['js'])
.on('change', onChange);
});
Pour pouvoir utiliser LiveReload et actualiser la fenêtre du navigateur à chaque modification de fichier, nous allons installer le plugin gulp-livereload. Nous utiliserons un bout de code dans notre template, en mode dev. Nous aurions pu installer une extension sur notre navigateur, mais cela nous permet de tout sous contrôle dans notre code source.
Après avoir installé le plugin, nous allons adapter la tâche watch
afin d'informer LiveReload que des fichiers ont été modifiés.
// Gulpfile.js
var livereload = require('gulp-livereload');
gulp.task('watch', function () {
var onChange = function (event) {
console.log('File '+event.path+' has been '+event.type);
// Tell LiveReload to reload the window
livereload.changed();
};
// Starts the server
livereload.listen();
gulp.watch('./src/*/Resources/public/sass/**/*.scss', ['sass'])
.on('change', onChange);
gulp.watch('./src/*/Resources/public/js/**/*.js', ['js'])
.on('change', onChange);
});
Notre layout va tester si nous sommes en environnement de dev
pour inclure un bout de code LiveReload :
// app/Resources/views/base.html.twig
{% if app.environment == 'dev' %}
<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')</script>
{% endif %}
Nous utiliserons le plugin gulp-phpunit pour lancer PHPUnir via Gulp.
npm install --save-dev gulp-phpunit
Nous allons utiliser la fonction task()
pour créer une tâche que nous appelerons test
. La fonction src()
nous permet d'utiliser une expression régulière pour sélectionner les fichiers de test.
// Gulpfile.js
var phpunit = phpunit = require('gulp-phpunit');
gulp.task('test', function () {
return gulp.src('./src/*/Tests/**/*.php')
.pipe(phpunit('./bin/phpunit', {debug: false, configurationFile: './app/phpunit.xml'}));
});
Nous pouvons maintenant lancer la tâche gulp test
. Le plugin gulp-phpunit
met à notre disposition un grand nombre d'options. Nous allons en profiter pour créer une tâche de génération de rapport de couverture de code.
// Gulpfile.js
gulp.task('coverage', function () {
return gulp.src('./src/*/Tests/**/*.php')
.pipe(phpunit(
'./bin/phpunit',
{debug: false, configurationFile: './app/phpunit.xml', coverageHtml: './build/coverage'}
));
});
Nous utiliserons le plugin gulp-phpcs pour lancer PHP_CodeSniffer via Gulp.
// Gulpfile.js
var phpcs = require('gulp-phpcs');
gulp.task('checkstyle', function () {
return gulp.src(['src/**/*.php'])
.pipe(phpcs({bin: './bin/phpcs', standard: 'PSR2', warningSeverity: 0}))
.pipe(phpcs.reporter('log'));
});
Nous allons également créer une tâche verify
en tant que raccourci :
// Gulpfile.js
gulp.task('verify', ['coverage', 'checkstyle']);
Durant les étapes de compilation, nous aurons besoin de lancer des commandes Symfony. Plutôt que d'utiliser un plugin supplémentaire, nous utiliserons le module child_process
fourni avec Node.js et qui nous permet d'exécuter des commandes Shell.
// Gulpfile.js
var exec = require('child_process').exec;
gulp.task('installAssets', function () {
exec('php app/console assets:install --symlink', logStdOutAndErr);
});
// Without this function exec() will not show any output
var logStdOutAndErr = function (err, stdout, stderr) {
console.log(stdout + stderr);
};
Comme vous le voyez, nous avons créé une tâche Gulp qui lance la commande d'installation des assets. Nous pouvons intégrer cette tâche à une autre tâche et nous n'aurions ainsi plus à penser à lancer cette dernière.
Nous disposons d'un certain nombre de tâches Gulp qui nous permettent d'accélerer et faciliter notre développement sur Symfony. Vous pouvez donc lancer les commandes suivantes :
# Compiler les fichiers sass, js et fonts
gulp sass js font
# Lancer la commande Symfony pour installer les assets
gulp installAssets
# Observer les fichiers scss et js pour les compiler et recharger la page de votre navigateur
gulp watch
# Lancer phpUnit et phpcs
gulp verify
Vous retrouverez un projet Symfony en exemple à l'adresse : https://github.com/acseo/gulp-symfony-tutoriel
Cet article est inspiré et traduit d'un article de Florian Eckerstorfer, accessible en anglais à l'adresse : https://florian.ec/articles/buliding-symfony2-with-gulp/