Un Brunch avec Symfony2 !

Un Brunch avec Symfony2 !

Sur un de mes projets client, l’une de mes premières tâches était la mise en place des différents outils pour le frontend, seul pré-requis technique donné par le client concernant cette tâche : ne pas utiliser Gulp.

J’ai donc mis en place Grunt, mais lors de nos essais de build automatisé sur une petite instance DigitalOcean, le processus node de Grunt n’arrêtait pas de se faire tuer.

Il ne nous a pas fallu longtemps pour nous rendre compte que le système se débarrassait de Grunt à cause de sa trop grande consommation mémoire (> 500 Mo), sans compter le temps d’exécution entre chaque compilation, environ 12 secondes.

Je suis donc passé à Brunch et je vais vous présenter son utilisation avec Symfony2.

Brunch est un outil de build spécialisé dans les assets. Il permet ainsi :

  • la compilation des différents types de fichier (Less, Stylus Scss, Coffee, TypeScript, Jade…) ;
  • la concaténation des fichiers ;
  • la minification et l’optimisation des fichiers en mode production ;
  • la surveillance des fichiers (watcher) pendant le développement ;
  • la vérification syntaxique ;
  • etc.

Pour celles et ceux qui viennent de Grunt ou Gulp, la question est : Que vaut Brunch comparé à ces deux outils ?

Comparaison de Grunt, Gulp et Brunch

Examinons les différents fichiers de configurations de ces trois outils pour une même application :

On constate que Brunch a une configuration très simpliste, en effet, l’outil est orienté convention plutôt que configuration.

Grunt et Gulp proposent une syntaxe pour décrire un ensemble de tâches, ainsi que leur ordre d’exécution. La principale différence entre les deux outils est que Grunt exécute les tâches l’une après l’autre tandis que Gulp propose un fonctionnement en pipeline qui évite d’utiliser des fichiers temporaires et permet une exécution en parallèle. Gulp est donc meilleur que Grunt sur la rapidité d’exécution ainsi que sur la quantité de configuration.

Je me suis également arrêté sur la vitesse de compilation de ces trois outils pour une même application.

Ce test a été effectué avec les tâches suivantes : Sass + import de Foundation, Less + import de foundation, Stylus, Concat, Uglify, Babel, TypeScript, CoffeScript. J’ai pu rapidement faire le comparatif avec le generator-joli-symfony.

Grunt

Grunt

Gulp

Gulp

Brunch

Brunch

Brunch est le plus rapide, et sa configuration est plus simple : c’est notre nouveau jouet préféré à JoliCode !

Installation

L’installation se fait de façon très classique :

$ npm install -g brunch

Le fait de l’installer en global permet de bénéficier de la commande brunch dans tout notre système et ainsi pouvoir exécuter les deux commandes brunch build et brunch watch.

Brunch est écrit en CoffeeScript, il est donc possible de le configurer en CoffeeScript ou en JavaScript via les fichiers brunch-config.coffee ou brunch-config.js.

Nous allons partir sur une configuration minimale en JavaScript, brunch-config.js.

Ce fichier est tout simplement un module javascript qui exporte une propriété config à l’intérieur de laquelle on retrouve une propriété files.

On peut alors configurer trois types d’assets : javascripts, stylesheets et templates. La propriété joinTo qui est obligatoire nous permet de définir le ou les fichiers générés à la fin de la compilation.

./brunch-config.js

'use strict';

exports.config = {
  files: {
    javascripts: {
      joinTo: 'app.js'
    },
    stylesheets: {
      joinTo: 'app.css'
    },
    templates: {
      joinTo: 'template.js'
    }
  }
};

javascripts : concerne tous les fichiers qui seront compilés en javascript (CoffeeScript, TypeScript, Babel, …).

stylesheets : concerne tous les fichiers qui seront compilés en css (SASS, Less, Stylus, …).

templates : concerne tous les fichiers qui seront compilés en HTML (Jade, YAML, …).

Par exemple, si notre projet utilise du CoffeeScript ou du TypeScript, la configuration du fichier brunch-config sera identique.

Structures de dossier par défaut pour Brunch

Par défaut, Brunch se base sur cette structure de dossier :

Structure de l’application

- app
    - js
        - application.js
        - manager.js
    - assets
        - fonts
            - open-sans.eot
            - open-sans.svg
            - open-sans.ttf
            - open-sans.woff
    - style
        - _main.scss
        - _header.scss
        - style.scss
 - public

app : contient tous les fichiers source, à l’exception des fichiers JS fournis par des tiers (bower_component) et non conçus pour être enrobés en modules. On peut donc avoir des fichiers scripts, feuilles de style, et fichiers templates. Les fichiers préfixés par un underscore seront compilés uniquement s’ils sont importés par d’autres fichiers. (exemple avec Sass : @import "main")

app/assets : copié-collé dans le dossier cible, tel quel. Parfait pour les fonts, images, etc.

public : est le dossier cible par défaut afin de retrouver tous les fichiers compilés. Si on prend notre fichier de configuration, on peut voir que nos différents fichiers après compilation arrivent bien dans le dossier public/app.js, public/app.css et public/template.js.

Tous les fichiers installés avec Bower seront inclus automatiquement avant les fichiers du dossier app, fini la mise à jour manuelle de la liste des vendors !

Nous allons maintenant configurer Brunch pour l’adapter à la structure d’un projet Symfony2.

Structures pour un projet Symfony2

Voici la structure des fichiers que je compte utiliser :

- app
    - Resources
        - assets
            - fonts
                - open-sans.eot
                - open-sans.svg
                - open-sans.ttf
                - open-sans.woff
        - js
            - application.js
            - manager.js
        - scss
            - _main.scss
            - _header.scss
            - style.scss
 - src
 - web
 - bower_component
     - foundation

Voici le fichier brunch-config.js mis à jour pour l’adapter à Symfony2 :

'use strict';

exports.config = {
  paths: {
    'public': 'web',
    'watched': ['app/Resources']
  },
  conventions: {
    'assets': /^app\/Resources\/assets/
  },
  files: {
    javascripts: {
      joinTo: {
        'js/app.js': /^app/,
        'js/vendor.js': /^(?!app)/
      }
    },
    stylesheets: {
      joinTo: 'css/style.css'
    }
  }
};

Personnalisation de Brunch

Le dossier cible peut être modifié avec paths.public, je lui indique donc que mes fichiers seront maintenant compilés dans le dossier web. Par défaut, le dossier cible est public.

paths.watched: est simplement la liste des fichiers sources utilisés dans l’application. Par défaut ['app', 'test', 'vendor']. Mon nouveau dossier surveillé app/Resources.

conventions.assets: Le dossier assets sera copié-collé (récursivement) dans le dossier cible sans aucun traitement. C’est l’équivalent de la tâche Copy que nous croisons très souvent dans grunt ou gulp. Par défaut cela correspond au dossier app/assets. Il attend une regex ou une fonction. J’ai opté pour la regex /^app\/Resources\/assets/.

On peut aussi définir certains fichiers ou dossiers à ignorer avec conventions.ignored.

J’ai modifié l’objet javascripts afin d’avoir après compilation deux fichiers javascript app.js et vendor.js.

Le fichier app.js contiendra tous mes fichiers sources définis par cette regex /^app/ et le fichier vendor.js contiendra tous les autres fichiers, en excluant le dossier app, définis par cette regex /^(?!app)/ comme les librairies installées avec bower.

   javascripts: {
     joinTo: {
       'js/app.js': /^app/,
       'js/vendor.js': /^(?!app)/
     }
   },

Vous pouvez consulter la configuration de référence ici . Nous allons maintenant mettre en pratique Brunch en utilisant les fichiers suivants :

app/Resources/js/calculator.js

'use strict';

var Calculator = {
  add: function add(a, b) {
    return console.log(a + b);
  },
  substract: function substract(a, b) {
    return console.log(a - b);
  }
};

module.exports = Calculator;

Pour compliquer la tâche, on va utiliser du Scss plutôt que du CSS.

app/Resources/scss/style.scss

@import "colors";
@import "main";

app/Resources/scss/_main.scss

body {
    backgound-color: $primary;
}

app/Resources/scss/_colors.scss

$primary: #ff0;

Pour poursuivre, nous avons besoin d’installer les modules suivants avec npm : brunch, javascript-brunch , et sass-brunch. Ces modules sont des plugins, mais contrairement à Grunt, nous n’avons pas besoin de les déclarer pour qu’ils soit utilisés.

$ npm install brunch javascript-brunch sass-brunch --save
$ brunch build ## Ça fonctionne ! :-)

les commandes de Brunch

Avec Brunch, nous avons deux commandes utiles à disposition :

$ brunch build

En lui passant l’option -P, on active le mode production permettant ainsi d’optimiser, de minifier et de désactiver les fichiers source maps.

Version simplifiée : $ brunch b.

$ brunch watch

Surveille les répertoires définis et relance une compilation à chaque changement. Lors de la modification d’un fichier, il re-compile uniquement ce qui est nécessaire (compilation incrémentale), cela lui permet d’être ultra-rapide.

Version simplifiée : $ brunch w.

Allez plus loin avec bower

Maintenant, je veux ajouter Foundation dans mon projet avec bower. Si vous ne connaissez pas bower, je vous invite à lire cet article.

Pensez à créer le fichier bower.json.

{
  "name": "brunch-sf2",
  "version": "0.1.0",
  "authors": [
    "Laurent Brunet "
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ]
}

$ bower install --save foundation

Foundation est livré avec quelques dépendances :

- 'bower_components/foundation/',
- 'bower_components/fastclick/',
- 'bower_components/jquery/',
- 'bower_components/jquery.cookie/',
- 'bower_components/jquery-placeholder/',
- 'bower_components/modernizr/',

D’après ma configuration, tous les fichiers javascript seront compilés dans mon fichier vendor.js, mais je veux juste utiliser les fichiers Sass de Foundation. Je vais donc spécifier à Brunch tous les fichiers ou dossiers que je veux ignorer avec conventions.ignored. Ils ne seront pas pris en compte lors de la compilation.

./brunch-config.js

exports.config = {
  conventions: {
    ignored: [
      'bower_components/foundation/',
      'bower_components/fastclick/',
      'bower_components/jquery/',
      'bower_components/jquery.cookie/',
      'bower_components/jquery-placeholder/',
      'bower_components/modernizr/',
    ],
  }
};

Il est possible d’avoir des erreurs lorsqu’on installe certains plugins avec bower. Lorsque je compile, brunch m’indique une erreur qui concerne modernizr.

error: [Error: Component JSON file "/Users/lbrunet/Documents/sandbox/Yo-test/bower_components/modernizr/.bower.json" must have `main` property. See https://github.com/paulmillr/read-components#README]

L’erreur Main Property

L’explication est très simple, chaque composant géré avec bower doit contenir une propriété main dans son fichier bower.json. Cela permet à Brunch de pouvoir définir automatiquement les composants afin de pouvoir les compiler, sinon l’analyse échouera. Aujourd’hui 50% des modules ne définissent pas cette propriété dans leur fichier bower.json.

Par exemple, dans le bower.json de Foundation, la propriété main indique les trois fichiers suivants :

{
  "name": "foundation",
  "version": "5.5.2",
  "main": [
    "css/foundation.css",
    "css/foundation.css.map",
    "js/foundation.js"
  ],
  "ignore": [],
}

Si on regarde le bower.json de modernizr, il n’y a pas de propriété main définie.

{
  "name": "modernizr",
  "homepage": "https://github.com/Modernizr/Modernizr",
  "version": "2.8.3",
  "_release": "2.8.3",
  "_resolution": {
    "type": "version",
    "tag": "v2.8.3",
    "commit": "d6bb30c0f12ebb3ddd01e90b0bf435e1c34e6f11"
  },
  "_source": "git://github.com/Modernizr/Modernizr.git",
  "_target": ">= 2.7.2",
  "_originalSource": "modernizr"
}

Nous allons y remédier en modifiant ainsi notre fichier bower.json

./bower.json

{
  "name": "brunch-sf2",
  "dependencies": {
    "foundation": "~5.5.2"
  },
  "overrides": {
    "modernizr": {
      "main": [
        "modernizr.js"
      ]
    }
  }
}

Le but est de spécifier le ou les fichiers javascripts ou stylesheets en utilisant overrides qui prend en paramètre le nom du dossier modernizr et lui affecter un main avec l’emplacement des fichiers.

Les plugins

Pour qu’un plugin soit actif et utilisé, il suffit qu’il soit installé, donc qu’il figure au package.json et ait été installé par npm.

Ainsi un simple $ npm install --save imageoptmizer-brunch suffit à optimiser les images lors d’un build production et un $ npm install --save eslint-brunch suffit à ajouter le linter sur vos fichiers Javascripts.

Brunch va instancier automatiquement les plugins dans leurs configurations par défaut, plus besoin de les charger avec un require ou loadNpmTasks.

il est possible d’affiner le comportement d’un plugin via une configuration dédiée, à définir dans le fichier brunch-coffee.js sous la clé plugins. Consultez la documentation du plugin pour être certain de la configuration à utiliser.

./brunch-config.js

exports.config = {
  plugins: {
    cleancss: {
      keepSpecialComments: 0,
      removeEmpty: true
    },
    uglify: {
      mangle: true,
      compress: false
    }
  }
};

Je viens à peine de vous rencontrer, mais je vous aime déjà !

Je peux vous affirmer dès à présent que tous mes projets Symfony2 seront brunchés ! C’est vraiment un gain de temps sur la compilation lorsque l’on développe mais aussi en simplicité à mettre en oeuvre.

D’ailleurs, vous pouvez vous aussi créer vos projet avec brunch, Gulp ou Grunt, les vendors de Symfony2 installés, Assetic supprimé et ce avec une seule commande :

$ yo joli-symfony

Retrouve le générateur-joli-Symfony à cette adresse : https://github.com/jolicode/generator-joli-symfony, nous venons de sortir la version 1.0 !

Enfin, n’hésitez pas à explorer l’utilisation de Brunch avec d’autres technologies. Vous pouvez consulter par exemple notre démo d’une application React & Flux écrite en ES6.

Je vous laisse digérer tout ça et à la prochaine !

Ressources

blog comments powered by Disqus