Fedora-Fr - Communauté francophone Fedora - Linux

Planet de Fedora-Fr : la communauté francophone autour de la distribution Linux Fedora

A propos

Cette page est actualisée toutes les heures.

Cette page est une sélection de blogs autour de Fedora. Fedora-Fr.org décline toute responsabilité au sujet des propos tenus par les auteurs des blogs de ce planet. Leurs propos sont leur entière responsabilité.

Le contenu de ce planet appartient à leurs auteurs respectifs. Merci de consulter leur blogs pour obtenir les licences respectives.

Mot-clefs : Développement

Atom pour remplacer Netbeans

Guillaume Kulakowski

J’avais, par le passé, fait un article sur mon passage d’Eclipse vers Netbeans. Je dois dire qu’au fil du temps, cet IDE m’a déçu : Cycle de vie assez long. Plugin Python qui n’est plus maintenu depuis près de 3 ans. Lourd (qui a dit Java ?). Oracle, qui via sa politique de rachat se […]

Cet article Atom pour remplacer Netbeans est apparu en premier sur Guillaume Kulakowski's blog.

End of PHP 7.2 FTBFS marathon

Remi Collet

QA is a very important part of my daily work, and since PHP 7.2 is available in Fedora rawhide, we have to ensure everything works as expected with this new version.

 

As already explained, Koschei is our QA tool, used to monitor the full PHP stack, including ~60 extensions and ~500 libraries.

After the initial build of PHP 7.2.0RC3 in rawhide (September 29th) we have around one hundred FTBFS packages (Failed To Build From Sources).

Today everything is ok, all FTBFS have been fixed.

1. Extensions

Most PHP extensions are now compatible with PHP 7.2, excepted

  • XDebug, but version 2.6.0-dev works and a beta should be released soon
  • Timecop, this have been reported upstream, searching for a fix.

2. Mcrypt

Lot of packages were broken because they sadly still rely on the old deprecated mcrypt extension.

Despite I'm fighting for years to be able to remove it (see about libmcrypt and php-mcrypt), we still need it, so I have created the new php-pecl-mcrypt package from the PECL cemetery. This is obviously only a temporary solution, this extension is deprecated, un-maintained and should die.

3. Upstream patches

Most of PHP projects consider fix for new PHP version as standard bugfix, which means, could be done in a simple minor version, without requiring any major change.

So, some projects have already made the few minor changes needed, but have not yet released new version including theses changes. So the work was only about finding these fix, and applying them in the Fedora packages.

4. Pull Requests

Most projects are not yet monitoring PHP 7.2 (not enabled in travis) so were not really aware of needed changes.

So, of course, the first work was to report this failure upstream, and usually providing a possible fix (PR).

Some are already merged, some are still waiting for review.

5. Skip some

For a very few packages, as no real good fix exists for now, we have to temporarily skip some tests with 7.2. Most are about the session change, which breaks unit tests (session_start() failing)  without any real impact on the real usage.

6. Common error

The 2 more common errors, requiring a fix, are :

  • stricter prototype checking, fix for #73987, originally applied in 7.1.2RC1 then reverted as introduce a small BC break.
  • count on not countable
  • object is a reserved keywork

7. Conclusion

We are ready for PHP 7.2 in Fedora, and as usually, we done this the Fedora way: upstream first.

I also consider that having most extensions / libraries ready is a important criteria for the new version adoption by users.

Partir de github

Remi Collet

Depuis quelques années, le développement des paquets pour mon dépôt était géré dans un depôt github : https://github.com/remicollet/remirepo.

C'était évidement une solution de facilité.

Au contraire de la mode actuelle d'utiliser ce service gratuit, mais pas vraiment libre, j'ai décidé d'auto-héberger mon travail sur le serveur dédié utilisé pour mon dépôt, mon blog et le forum.

L'ensemble des sources des paquets, des outils et des sites seront donc progressivement déplacés vers le serveur git.remirepo.net, est sont consultables sur https://git.remirepo.net/cgit.

Le dépôt github restera ouvert uniquement pour les rapports de bug ou demande diverses, mais les proposition de correctifs devront être transmises par messagerie (en utilisant de préférence git format-patch afin que je puisse les appliquer avec git am).

J'envisage aussi de remplacer, dès que possible, cgit par pagure.

ZipArchive avec cryptage

Remi Collet

Un petit point d'avancement du développement de l'extension zip version 1.14.0 qui intègre désormais le support des archives cryptées.

L'implémentation de cette nouvelle fonctionnalité repose sur l'utilisation de la bibliothèque libzip version 1.2.0 récemment publiée.

Actuellement seule la compilation avec la bibliothèque système offre ce support, mais une mise à jour de la version embarquée est prévue.

Lorsque tout sera validé, la version sera publiée et intégrée aux sources de php (ext/zip), sans doute pour PHP 7.2.

il s'agit d'un développement en cours, rien n'est définitif, et les méthodes proposées peuvent encore changer.

Installation en RPM

Le paquet php-pecl-zip-1.4.0.0-0.2.20170301dev est disponible dans le dépôt remi-test (et remi-php70-test, remi-php71-test).

Installation depuis les sources

Depuis un clone des sources disponibles dans github :

$ phpize
$ ./configure --with-libzip
...
checking for libzip... from pkgconfig: version 1.2.0 found in /usr/lib64
checking for zip_open in -lzip... yes
checking for zip_file_set_encryption in -lzip... yes
...
$ make
...
Build complete.
Don't forget to run 'make test'.
$ make test
...
PASS ZipArchive::setEncryption*() functions [tests/oo_encryption.phpt]

Création d'un archive cryptée

Trois méthodes permettent de gérer le cryptage

ZipArchive::setEncryptionName($name, $method [, $password]);
ZipArchive::setEncryptionIndex($index, $method [, $password]);
ZipArchive::setPassword($password);

La méthode de cryptage devant être une des constantes ZipArchive::EM_NONE, ZipArchive::EM_AES_128, ZipArchive::EM_AES_192 ou , ZipArchive::EM_AES_256.

Exemple :

$zip = new ZipArchive;
$zip->open(__DIR__ . '/encrypted.zip',   ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
$zip->addFile(__FILE__, 'foo.php');
$zip->setEncryptionName('foo.php', ZipArchive::EM_AES_256, 'secret');
$zip->close();

Lecture d'une archive cryptée

Exemple :

$zip = new ZipArchive;
$zip->open(__DIR__ . '/encrypted.zip');
print_r($zip->statName($file));
$zip->setPassword('secret');
$text = $zip->getFromName('foo.php');
$zip->close();

A noter :

  • utilisation de la méthode setPassword qui positionne le mot de passe utilisé par défaut.
  • la sortie de la méthode statName retourne une nouvelle information : encryption_method (259 pour ZipArchive::EM_AES_256)

Lecture d'un archive cryptée via les flux

Il est nécessaire de passer le mot de passe en utilisant un context.

Exemple :

$ctx = stream_context_create(array(
    'zip' => array(
        'password' => 'secret'
    )
));
$text = file_get_contents('zip://' . __DIR__ . '/encrypted.zip#foo.php', false, $ctx);

Conclusion

Le script utilisé dans les exemples ci-dessus est disponible dans les sources de l'extension : encryption.php

Cette nouvelle fonctionnalité me semble intéressante, et permet une meilleure compatibilité avec les outils existants, comme WinZip sous Windows ou 7za sous Linux.

Pour Fedora, cette évolution devrait être disponible dans Fedora 26 qui dispose déjà de libzip 1.2.0.

PHP 7.1 et contrôle des nombres

Remi Collet

PHP 7.1 introduit un nouveau contrôle lors de la conversion d'une chaine en nombre.

Exemple :

$ module load php70
$ php -v
PHP 7.0.8RC1 (cli) (built: Jun  8 2016 06:25:44) ( NTS )
$ php -r 'var_dump("1K" * 1024);'
int(1024)

$ module load php71
$ php -v
PHP 7.1.0alpha1 (cli) (built: Jun  8 2016 09:36:05) ( NTS )
$ php -r 'var_dump("1K" * 1024);'
PHP Notice:  A non well formed numeric value encountered in Command line code on line 1
int(1024)

Voir aussi sur 3v4l.org.

Il est évident que ce nouveau message est utile, mais c'est un exemple très fréquemment rencontré.

Quelque exemples, dans des projets rééls :

Vous pouvez commencer à vérifier votre code ;)

PHP 7.1 et contrôle des nombres

Remi Collet

PHP 7.1 introduit un nouveau contrôle lors de la conversion d'une chaine en nombre.

Exemple :

$ module load php70
$ php -v
PHP 7.0.8RC1 (cli) (built: Jun  8 2016 06:25:44) ( NTS )
$ php -r 'var_dump("1K" * 1024);'
int(1024)

$ module load php71
$ php -v
PHP 7.1.0alpha1 (cli) (built: Jun  8 2016 09:36:05) ( NTS )
$ php -r 'var_dump("1K" * 1024);'
PHP Notice:  A non well formed numeric value encountered in Command line code on line 1
int(1024)

Voir aussi sur 3v4l.org.

Il est évident que ce nouveau message est utile, mais c'est un exemple très fréquemment rencontré.

Quelque exemples, dans des projets rééls :

Vous pouvez commencer à vérifier votre code ;)

Tests de performance de PHPUnit et couverture de code

Remi Collet

Comme il a déjà été dit de nombreuses fois, PHP 7 est plus rapide que PHP 5.

Depuis PHPUnit 4.8 vous pouvez choisir entre  XDebug et phpdbg comme pilote pour récupérer les données de couverture du code, voir PHPUnit 4.8: Code Coverage Support.

Voici quelques résultats de tests de performance.

Tous les tests utilisent PHPUnit 5.0.8, PHP 5.6.15 en SCL ou PHP 7.0.0RC6 en SCL et XDebug 2.4.0beta1 (récemment publié avec quelques correctifs supplémentaires) sur les tests unitaires de composer.

PHP 5 sans couverture de code

$ php56 vendor/bin/phpunit -v
Runtime:       PHP 5.6.15
Time: 4.78 seconds, Memory: 40.25Mb

PHP 7 sans couverture de code

$ php70 vendor/bin/phpunit -v
Runtime:       PHP 7.0.0RC6
Time: 3.37 seconds, Memory: 22.00Mb

Donc PHP 7 est bien plus rapide et permet de gagner 30% de temps d'exécution et 45% de mémoire.

PHP 5 avec couverture de code

$ php56 vendor/bin/phpunit -v
Runtime:       PHP 5.6.15 with Xdebug 2.4.0beta1
Time: 1.89 minutes, Memory: 90.50Mb

PHP 7 avec couverture de code et XDebug

$ php70 vendor/bin/phpunit -v
Runtime:       PHP 7.0.0RC6 with Xdebug 2.4.0beta1
Time: 39.41 seconds, Memory: 52.00Mb

PHP 7 de nouveau vraiment plus rapide (65% de temps, 43% de mémoire)

PHP 7 avec couverture de code et phpdbg

$ php70-phpdbg -qrr vendor/bin/phpunit -v 
Runtime:       PHPDBG 7.0.0RC6
Time: 13.07 seconds, Memory: 92.00Mb

Terriblement plus rapide :) 66% du temps d'exécution économisé comparé à XDebug, et 89% comparé à PHP 5

J'ai remarqué que beaucoup de développeurs n'était pas au courant de cette dernière solution, quel dommage ! J'espère que ce billet vous encouragera à la tester.

 

Accès aux fichiers Windows depuis Linux

Remi Collet

Voici quelques information sur l'extension libsmbclient-php que je viens de découvrir.

En fouillant dans le code de owncloud et dans ses dépendances, j'ai remarqué quelques bibliothèques et une extension PHP :

Dans tous les scenarii, pour accéder à des fichier Windows depuis Linux, il faudra utiliser les outils du logiciel Samba.

Bien que je comprenne le besoin d'une implémentation pur PHP (icewind/smb), je pense qu'encapsuler les appels à la commande smbclient est remarquablement laid, et vraiment pas robuste.

Après une première étude, le projet libsmbclient-php m'a semblé intéressant mais un peu tordu à utiliser, car il est nécessaire d'utiliser un ensemble de fonctions dédiées (smbclient_*).

Par exemple, pour afficher le contenu d'un fichier Windows vous devez écrire :

// Creation d'un état
state = smbclient_state_new();
// Initialisation avec le domaine, nom d'utilisateur et mot de passe:
smbclient_state_init($state, null, 'testuser', 'password');
// Ouverture du fichier
$file = smbclient_open($state, 'smb://server/testshare/file.txt', 'r');
if ($file) {
  // Lecture séquentielle et affichage
  while ($data = smbclient_read($state, $file, 1000)) {
    echo $data;
  }
}
// Fermeture 
smbclient_close($state, $file)
// Liberation
smbclient_state_free($state);

Pas très amusant ;) évidement, il est possible de créer un streamWrapper pour simplifier l'utilisation, comme celui fournit par icewin/smb.

J'ai donc commencé à contribuer à ce projet pour l'améliorer :

Il est donc désormais possible d'écrire plus simple l'exemple précédent :

readfile("smb://testuser:password@server/testshare/file.txt");

Il reste d'autres travaux en cours :

Dans le futur, il pourrait aussi être intéressant de remplacer l'utilisation des ressources par des objects (e.g; SmbClient\State, SmbClient\File, SmbClient\Dir).

Évidement, les RPM de php-libsmbclient sont disponibles dans mon dépôt, la version 0.7.0 dans remi, remi-php55 et remi-php56 ou la version 0.8.0-dev dans remi-test et remi-php70.

Retour et commentaires sont les bienvenus.

 

 

Data URIs VS CSS Sprites

Guillaume Kulakowski

J'ai profité de la sortie de la branche 2.7 de Dotclear et de l'arrivée de Twig comme moteur de template pour faire quelques modifications sur le blog :

  • Migration vers des templates utilisant la syntaxe Twig et utilisation au maximum de l'héritage (la killer feature de Twig !).
  • Utilisation encore plus massive de Less.
  • Mise à jour des librairies JS et CSS (merci Bower).
  • Suppression des sprites CSS pour utiliser des images en data URI au sein de mes CSS.

Et c'est sur ce dernier point que je souhaitais discourir...

Les sprites CSS c'est bien... mais c'est chi**t !!!

En effet, les sprites CSS c'est bien pour les performances web client side (notamment : Minimize HTTP Requests & Preload Components). Le problème, c'est que c'est chi**t à manipuler ! Il existe des générateurs de sprites, mais ce n'est pas la panacée. Bilan : j'y réfléchi à 2 fois avant de changer une image et dans certains cas il est même obligatoire de mixer plusieurs images sprites (sur des background-repeat bien complexes).

Data URI, L'alternative ?

La solution alternative c'est d'utiliser des data URIs, c'est à dire de mettre le code base64 de l'image directement dans la CSS (où une CSS séparée). Vous me direz que faire un sprite CSS ou faire un base64 c'est aussi pénible et je vous répondrais oui... Si on travaille en pure CSS ! Mais si on utilise Less (et c'est surement aussi possible en Sass) il y a une fonction qui fait ça automatiquement !

Y a-t-il que des avantages ?

Malheureusement non.

  • Un fichier CSS avec du base64 pèse 25% (10% si on utilise compression gzip) de plus qu'un sprite.
  • Les data URI's ne sont pas supportées par IE6/7 (ça je m'en fous un peu :-)).
  • Les performances sont quelques peu en dessous d'un sprite.

En conclusion

J'ai déjà pas mal optimisé mon site, le fait de passer par data-uri me permet de :

  • Réduire le poids général de ma CSS (j'embarquais déjà quelques data-uri que je viens de délocaliser dans une CSS à part).
  • Pouvoir rapidement faire évoluer mon image sprites (retirer les images inutiles par exemple) et gagner en time to market.

Bref : adopté !

Data URIs VS CSS Sprites

Guillaume Kulakowski

J'ai profité de la sortie de la branche 2.7 de Dotclear et de l'arrivée de Twig comme moteur de template pour faire quelques modifications sur le blog :

  • Migration vers des templates utilisant la syntaxe Twig et utilisation au maximum de l'héritage (la killer feature de Twig !).
  • Utilisation encore plus massive de Less.
  • Mise à jour des librairies JS et CSS (merci Bower).
  • Suppression des sprites CSS pour utiliser des images en data URI au sein de mes CSS.

Et c'est sur ce dernier point que je souhaitais discourir...

Les sprites CSS c'est bien... mais c'est chi**t !!!

En effet, les sprites CSS c'est bien pour les performances web client side (notamment : Minimize HTTP Requests & Preload Components). Le problème, c'est que c'est chi**t à manipuler ! Il existe des générateurs de sprites, mais ce n'est pas la panacée. Bilan : j'y réfléchi à 2 fois avant de changer une image et dans certains cas il est même obligatoire de mixer plusieurs images sprites (sur des background-repeat bien complexes).

Data URI, L'alternative ?

La solution alternative c'est d'utiliser des data URIs, c'est à dire de mettre le code base64 de l'image directement dans la CSS (où une CSS séparée). Vous me direz que faire un sprite CSS ou faire un base64 c'est aussi pénible et je vous répondrais oui... Si on travaille en pure CSS ! Mais si on utilise Less (et c'est surement aussi possible en Sass) il y a une fonction qui fait ça automatiquement !

Y a-t-il que des avantages ?

Malheureusement non.

  • Un fichier CSS avec du base64 pèse 25% (10% si on utilise compression gzip) de plus qu'un sprite.
  • Les data URI's ne sont pas supportées par IE6/7 (ça je m'en fous un peu :-)).
  • Les performances sont quelques peu en dessous d'un sprite.

En conclusion

J'ai déjà pas mal optimisé mon site, le fait de passer par data-uri me permet de :

  • Réduire le poids général de ma CSS (j'embarquais déjà quelques data-uri que je viens de délocaliser dans une CSS à part).
  • Pouvoir rapidement faire évoluer mon image sprites (retirer les images inutiles par exemple) et gagner en time to market.

Bref : adopté !

Passage d'Eclipse à Netbeans

Guillaume Kulakowski

Après des années et des années de bons et loyaux services, j'ai décidé d'arrêter d'utiliser Eclipse.

Les raisons sont multiples :

  • Eclipse est de plus en plus lourd alors que la mode est aux IDE de plus en plus légers (SublimeText, Atom.io, etc...).
  • Je ne fais plus de Java depuis longtemps. J'ai besoin d'un IDE qui permette de faire du web (CSS, HTML, JS, Less, etc...), PHP, Node, Ruby, shell. Or Eclipse devient de plus en plus inadapté pour cela.
  • J'ai besoin d'un IDE avec une thème dark. Alors sous Linux pas de problème, mais au travail, sous Windows (sniff), je n'ai jamais pu avoir de thème dark sans un détourage désagréable des icônes.

C'est pour toutes ces raisons que j'ai souhaité changer de crèmerie. Mais je n'étais pas prêt pour passer à un truc trop light, je suis donc allé chercher la solution d'en face : Netbeans. Alors ne nous emballons pas, avec Netbeans tout n'est pas rose :

  • Netbeans permet d'avoir une syntaxe par langage, mais ça veut direz coder tous les frameworks PHP de la même façon... Du Drupal et du Symfony2, ça ne se code pas pareil (hélas).
  • Le plugin GIT est simple et efficace mais un peu trop simple quand même.

Mais il y a des trucs qui sont en train de me devenir indispensables :

  • Terminal intégré nativement.
  • Plugin Vagrant.
  • Stack d'outils PHP qui fonctionnent sans PEAR.
  • Support de Composer, NPM, Bower et du Less out-of-the-box.

Après avoir changé le moteur, j'ai également changé la carrosserie :

Et voici le résultat :

Netbeans.png

Passage d'Eclipse à Netbeans

Guillaume Kulakowski

Après des années et des années de bons et loyaux services, j'ai décidé d'arrêter d'utiliser Eclipse.

Les raisons sont multiples :

  • Eclipse est de plus en plus lourd alors que la mode est aux IDE de plus en plus légers (SublimeText, Atom.io, etc...).
  • Je ne fais plus de Java depuis longtemps. J'ai besoin d'un IDE qui permette de faire du web (CSS, HTML, JS, Less, etc...), PHP, Node, Ruby, shell. Or Eclipse devient de plus en plus inadapté pour cela.
  • J'ai besoin d'un IDE avec une thème dark. Alors sous Linux pas de problème, mais au travail, sous Windows (sniff), je n'ai jamais pu avoir de thème dark sans un détourage désagréable des icônes.

C'est pour toutes ces raisons que j'ai souhaité changer de crèmerie. Mais je n'étais pas prêt pour passer à un truc trop light, je suis donc allé chercher la solution d'en face : Netbeans. Alors ne nous emballons pas, avec Netbeans tout n'est pas rose :

  • Netbeans permet d'avoir une syntaxe par langage, mais ça veut direz coder tous les frameworks PHP de la même façon... Du Drupal et du Symfony2, ça ne se code pas pareil (hélas).
  • Le plugin GIT est simple et efficace mais un peu trop simple quand même.

Mais il y a des trucs qui sont en train de me devenir indispensables :

  • Terminal intégré nativement.
  • Plugin Vagrant.
  • Stack d'outils PHP qui fonctionnent sans PEAR.
  • Support de Composer, NPM, Bower et du Less out-of-the-box.

Après avoir changé le moteur, j'ai également changé la carrosserie :

Et voici le résultat :

Netbeans.png

Koschei : intégration continue de la pile PHP dans Fedora

Remi Collet

L'intégration Continue est une préoccupation naturelle de tous développeurs.

La plupart des projets utilisent des outils comme Travis (disponible sous github.com). PHP dispose de sa propre suite de tests. Cela permet de vérifier que les modifications apportées au code source n'apportent pas de régression.

Cela semble suffisant pour une projet, mais pour la "pile PHP" dans sont ensemble, il faut pouvoir :

  • vérifier que la mise à jour d'une dépendance ne casse pas les projets l'utilisant
  • vérifier que la mise à jour de PHP n'entraine pas de régression dans les projets, dès les Release Candidate (les versions de PHP disponibles dans travis/github sont souvent en retard)
  • vérifier que les modifications liés au packaging ne cassent rien

Le projet Koschei permet de répondre à ces besoins en supervisant les mises à jour dans rawhide (la branche développement de Fedora) et en déclenchant une construction à blanc de tous les paquets dépendants.

Comme de nombreux outils et bibliothèques sont disponibles dans Fedora, nous les avons ajoutés pour qu'ils soient surveillés, en particulier :

  • PHP (uniquement la version 5.6 pour l'instant)
  • PHPUnit et toutes ses dépendances
  • Symfony
  • Doctrine
  • Horde
  • etc

Adresse de l'instance : http://koschei.cloud.fedoraproject.org/

Ce nouvel outil aurait pu nous permettre de détecter au plus tôt les régressions introduites dans PHP version 5.4.29 et 5.5.13. Je prévois donc d'importer les prochaines Release Candidate au plus tôt afin de bénéficier de l'outil.

Par exemple, il vient de permettre de détecter un FTBFS de symfony suite à la mise à jour de PHPUnit 4.2.0 (il s'agissait d'un problème spécifique au paquet et déjà corrigé).

Évidement, il est donc nécessaire de disposer de suite de test pour chaque projet et de les exécuter lors de la construction des paquets, mais cela fait, depuis longtemps, partie des bonnes pratiques.

Nous verrons à l'avenir comment l'outil se comporte, mais je pense qu'il apporte un vrai progrès pour l'intégration continue de l'ensemble de la pile PHP.

Il s'agit aussi et surtout d'un outil dont les résultats doivent bénéficier à chaque projet, prouvant l'utilité de la collaboration inter-projets (upstream / downstream), et donnant encore plus d'intérêt, pour chaque projet, à être intégré dans une distribution comme Fedora.

Grunt, Bower, LESS & Bootstrap

Guillaume Kulakowski
Dans mon dernier billet, j'avais évoqué mon engouement pour Bower, une solution de gestion de dépendances web. J'avais alors regretté le fait de ne pas pouvoir exécuter des tâches post-installation afin de retravailler la version distribuée de Bootstrap pour lalléger (comme on peut le faire ici). Comme j'en avais émis l'idée dans ce même billet, j'ai entrepris de ne plus utiliser Bower directement mais de lutiliser au travers de Grunt.

grunt-bower.png

Si vous ne connaissez pas encore Grunt, il s'agit d'un ordonnanceur au même titre que Maven, ant, ou encore Phing, mais basé sur nodejs et utilisant une syntaxe JavaScript.

Installation de Bower & Grunt sous Fedora 20

Après avoir installé nodejs et npm via yum, il faudra installer Bower et Grunt avec npm. En effet, Bower est absent des dépôts Fedora et Grunt est bien disponible mais sans grunt-cli, ce qui en enlève tout lintérêt...

sudo yum install npm -y
sudo npm install bower grunt grunt-cli -g

Notez au passage le fait que l'on fasse l'installation avec l'option -g pour avoir Grunt et Bower installés au niveau du système (/usr/lib/node_modules). Les autres installations se feront toujours à l'aide de npm mais sans cette option. Ceci permet de tout avoir à la racine du projet web, dans un répertoire node_modules (qu'il faudra exclure de la gestion de sources).

Voila, maintenant on rajoute un fichier packages.json et on installe les dépendances avec un npm install :

{
  "name": "Boldy",
  "version": "1.1.2",
  "description": "The Boldy theme for Dotclear",
  "repository": {
    "type": "git",
  },
  "author": "Guillaume Kulakowski",
  "homepage": "http://www.llaumgui.com",
  "devDependencies": {
    "bower": "latest",
    "grunt": "~0.4.2",
    "grunt-contrib-uglify": "latest",
    "grunt-contrib-concat": "latest",
    "grunt-contrib-less": "latest",
    "grunt-bower-task": "latest",
    "grunt-contrib-watch": "latest"
    "grunt-contrib-cssmin": "latest"
  }
}

J'ai maintenant un environnement de travail avec Bower et Grunt ainsi que les plugins nécessaires à mon projet.

Le fichier Gruntfile.js

La configuration des tâches Grunt passe par le fichier Gruntfile.js, j'ai mis le mien et vais en détailler les tâches :

module.exports = function(grunt) {
  'use strict';
 
  // Force use of Unix newlines
  grunt.util.linefeed = '\n';
 
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    bowerrc: grunt.file.readJSON('.bowerrc'),
 
    // Path configuration from Gruntfile.js
    dirs: {
      'vendor': '<%= bowerrc.directory %>',
      'bootstrap': {
        'js': '<%= dirs.vendor %>/bootstrap/js',
        'less': '<%= dirs.vendor %>/bootstrap/less'
      },
      'css': 'css',
      'less': 'less',
      'js': 'js'
    },
 
    src: {
      output: {
        'bootstrap': {
          'js': '<%= dirs.vendor %>/bootstrap/my/js/bootstrap.js',
          'css': '<%= dirs.vendor %>/bootstrap/my/css/bootstrap.css'
        },
        'boldy': {
          css: {
            'screen': '<%= dirs.css %>/screen.css',
            'indefero': '<%= dirs.css %>/indefero.css'
          },
          js: '<%= dirs.js %>/global.js'
        }
      },
 
      input: {
        bootstrap: {
          'js': [
            '<%= dirs.bootstrap.js %>/dropdown.js',
            '<%= dirs.bootstrap.js %>/tooltip.js',
            '<%= dirs.bootstrap.js %>/collapse.js',
            '<%= dirs.bootstrap.js %>/transition.js',
            '<%= dirs.bootstrap.js %>/carousel.js'
          ],
          'less': [
            '<%= dirs.less %>/bootstrap.less'
          ]
        },
        indefero: '<%= dirs.css %>/indefero.src.css',
        less: '<%= dirs.less %>/boldy_boot.less',
        js: [
          '<%= dirs.vendor %>/jquery-cookie/jquery.cookie.js',
          '<%= src.output.bootstrap.js %>',
          '<%= dirs.vendor %>/scroll-to-top/jquery.scrollToTop.min.js',
          '<%= dirs.js %>/js/post.js',
          '<%= dirs.vendor %>/async-gravatars/async-gravatars.js',
          '<%= dirs.vendor %>/jquery-colorbox/jquery.colorbox-min.js',
          '<%= dirs.vendor %>/nwxforms/src/nwxforms.js',
          '<%= dirs.js %>/boldy.js'
        ]
      },
    },
 
    // Banner
    banner: '/*!\n' +
              ' * Boldy for Dotclear v<%= pkg.version %>\n' +
              ' * Original theme by Site5 (http://www.s5themes.com)\n' +
              ' * Under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt\n' +
              ' * Ported on Dotclear by <%= pkg.author %> (<%= pkg.homepage %>)\n' +
              ' * Copyright 2012-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
              ' */',
 
 
 
    /********************************** Task **********************************/
    // Bower
    bower: {
      install: {
        options: {
          targetDir: '<%= dirs.vendor %>',
          cleanTargetDir: true,
          layout: 'byComponent',
          install: true,
          copy: false,
          verbose: true
        }
      }
    },
 
 
    // Concatenation
    concat: {
      bootstrap: {
        src: '<%= src.input.bootstrap.js %>',
        dest: '<%= src.output.bootstrap.js %>'
      },
    },
 
 
    // LESS
    less: {
      boldy: {
        options: {
          compress: true,
          cleancss: true,
          report: 'gzip',
          strictImports: true,
        },
        files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      boldy_debug: {
          options: {
            compress: false,
            cleancss: false,
            report: 'none',
            strictImports: true,
          },
          files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      bootstrap: {
        files: { '<%= src.output.bootstrap.css %>': '<%= src.input.bootstrap.less %>'}
      }
    },
 
 
    // Watcher
    watch: {
      boldy: {
        files: ['less/*.less'],
        tasks: ['less:boldy_debug'],
        options: {
          spawn: false,
        },
      },
    },
 
 
    // CSSmin
    cssmin: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.css.indefero %>': '<%= src.input.indefero %>'
        }
      }
    },
 
 
    // Uglify
    uglify: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.js %>': '<%= src.input.js %>',
        }
      }
    }
 
  });
 
 
  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-bower-task');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
 
 
  // Callable tasks
  grunt.registerTask('default', ['boldy', 'cssmin', 'concat:bootstrap', 'uglify']);
  grunt.registerTask('w', ['watch:boldy']);
 
  grunt.registerTask('bootstrap', ['concat:bootstrap', 'less:bootstrap']);
  grunt.registerTask('boldy', ['less:boldy']);
 
  grunt.registerTask('full', ['bower', 'bootstrap', 'default']);
  grunt.registerTask('all', ['full']); // Alias
};

Tâche bower

Via le plugin grunt-bower-task.

Cette tâche est là pour exécuter Bower au travers de Grunt, notez que le plugin n'interprète pas le .bowerrc, je l'ai donc chargé moi même (bowerrc: grunt.file.readJSON('.bowerrc')) afin de passer les mêmes options à la tâche Grunt qu'à Bower.

Tâche Bootstrap

Via les plugins grunt-contrib-less & grunt-contrib-concat.

Le but de cette tâche est de ne pas utiliser la version distribuée de Bootstrap mais de reconstruire ma propre version. Pour celà,

  • je reconstruis le JavaScript à partir des sources et du plugin concat,
  • je recompile les CSS à partir d'une version allégée du bootstrap.less :
/* -- BEGIN LICENSE BLOCK ---------------------------------------
# This file is part of Boldy, a theme for Dotclear
#
# Theme by Site5 (http://www.s5themes.com)
# under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt
# Ported on Dotclear by Guillaume Kulakowski (http://www.llaumgui.com)
#
# -- END LICENSE BLOCK ----------------------------------------- */
 
// Core variables and mixins
@import "../vendor/bootstrap/less/variables.less";
@import "../vendor/bootstrap/less/mixins.less";
 
// My override
@import "bootstrap_variables.less";
//@import "bootstrap_mixins.less";
 
// Reset
@import "../vendor/bootstrap/less/normalize.less";
@import "../vendor/bootstrap/less/print.less";
 
// Core CSS
@import "../vendor/bootstrap/less/scaffolding.less";
@import "../vendor/bootstrap/less/type.less";
@import "../vendor/bootstrap/less/code.less";
@import "../vendor/bootstrap/less/grid.less";
//@import "../vendor/bootstrap/less/tables.less";
@import "../vendor/bootstrap/less/forms.less";
@import "../vendor/bootstrap/less/buttons.less";
 
// Components
@import "../vendor/bootstrap/less/component-animations.less";
//@import "../vendor/bootstrap/less/glyphicons.less";
@import "../vendor/bootstrap/less/dropdowns.less";
//@import "../vendor/bootstrap/less/button-groups.less";
//@import "../vendor/bootstrap/less/input-groups.less";
@import "../vendor/bootstrap/less/navs.less";
@import "../vendor/bootstrap/less/navbar.less";
//@import "../vendor/bootstrap/less/breadcrumbs.less";
@import "../vendor/bootstrap/less/pagination.less";
@import "../vendor/bootstrap/less/pager.less";
//@import "../vendor/bootstrap/less/labels.less";
//@import "../vendor/bootstrap/less/badges.less";
//@import "../vendor/bootstrap/less/jumbotron.less";
@import "../vendor/bootstrap/less/thumbnails.less";
@import "../vendor/bootstrap/less/alerts.less";
//@import "../vendor/bootstrap/less/progress-bars.less";
//@import "../vendor/bootstrap/less/media.less";
@import "../vendor/bootstrap/less/list-group.less";
@import "../vendor/bootstrap/less/panels.less";
@import "../vendor/bootstrap/less/wells.less";
//@import "../vendor/bootstrap/less/close.less";
 
// Components w/ JavaScript
//@import "../vendor/bootstrap/less/modals.less";
@import "../vendor/bootstrap/less/tooltip.less";
//@import "../vendor/bootstrap/less/popovers.less";
@import "../vendor/bootstrap/less/carousel.less";
 
// Utility classes
@import "../vendor/bootstrap/less/utilities.less";
@import "../vendor/bootstrap/less/responsive-utilities.less";

Remarquez que j'utilise mes propres variables après celles de Bootstrap afin de les écraser et de modifier Bootstrap directement à la source.

Tâche Boldy

Via les plugins grunt-contrib-uglify, grunt-contrib-less & grunt-contrib-cssmin.

Comme en recompilant Bootstrap à partir des sources LESS j'ai adhéré au concept de LESS, j'ai réécrit le thème de mon blog en LESS.

Cette tâche boldy sert donc :

  • à générer le CSS minifié à partir des sources LESS (grunt-contrib-less),
  • à minifier la CSS pour mon thème indefero qui reste en CSS (grunt-contrib-cssmin),
  • et à minifier les Javascripts (grunt-contrib-uglify).

Tâche watch

Via le plugin grunt-contrib-watch.

Pour compiler du LESS en CSS, il y a 2 méthodes.

  • soit passer par le less.js qui va parser en JS les fichiers LESS à chaque chargement de page (2 à 3 secondes pour mon blog),
  • soit passer par une tâche watch dans Grunt, qui va écouter les modifications sur les fichiers LESS et recompiler un CSS.

C'est cette dernière solution que j'ai retenu et donc c'est à cela que sert la tâche watch.

En conclusion

Pour conclure, je dirais que la mise en place de ces outils (surtout pour la première fois) est certes chronophage, mais une fois que ça tourne, pour mettre à jour les libs (Bootstrap, jQuery, etc.), une seule ligne de commande suffit (grunt full). De plus, une fois Grunt et Bower en place (et maîtrisés), passer de CSS à LESS a été super simple.

Pour finir, je regrette presque que nodejs n'ait pas été inclus dans Fedora plus tôt car il propose de formidables outils indispensables aux développeurs (front) web.

Et pour voir ce que ça donne, une petite vidéo:

Grunt, Bower, LESS & Bootstrap

Guillaume Kulakowski
Dans mon dernier billet, j'avais évoqué mon engouement pour Bower, une solution de gestion de dépendances web. J'avais alors regretté le fait de ne pas pouvoir exécuter des tâches post-installation afin de retravailler la version distribuée de Bootstrap pour lalléger (comme on peut le faire ici). Comme j'en avais émis l'idée dans ce même billet, j'ai entrepris de ne plus utiliser Bower directement mais de lutiliser au travers de Grunt.

grunt-bower.png

Si vous ne connaissez pas encore Grunt, il s'agit d'un ordonnanceur au même titre que Maven, ant, ou encore Phing, mais basé sur nodejs et utilisant une syntaxe JavaScript.

Installation de Bower & Grunt sous Fedora 20

Après avoir installé nodejs et npm via yum, il faudra installer Bower et Grunt avec npm. En effet, Bower est absent des dépôts Fedora et Grunt est bien disponible mais sans grunt-cli, ce qui en enlève tout lintérêt...

sudo yum install npm -y
sudo npm install bower grunt grunt-cli -g

Notez au passage le fait que l'on fasse l'installation avec l'option -g pour avoir Grunt et Bower installés au niveau du système (/usr/lib/node_modules). Les autres installations se feront toujours à l'aide de npm mais sans cette option. Ceci permet de tout avoir à la racine du projet web, dans un répertoire node_modules (qu'il faudra exclure de la gestion de sources).

Voila, maintenant on rajoute un fichier packages.json et on installe les dépendances avec un npm install :

{
  "name": "Boldy",
  "version": "1.1.2",
  "description": "The Boldy theme for Dotclear",
  "repository": {
    "type": "git",
  },
  "author": "Guillaume Kulakowski",
  "homepage": "http://www.llaumgui.com",
  "devDependencies": {
    "bower": "latest",
    "grunt": "~0.4.2",
    "grunt-contrib-uglify": "latest",
    "grunt-contrib-concat": "latest",
    "grunt-contrib-less": "latest",
    "grunt-bower-task": "latest",
    "grunt-contrib-watch": "latest"
    "grunt-contrib-cssmin": "latest"
  }
}

J'ai maintenant un environnement de travail avec Bower et Grunt ainsi que les plugins nécessaires à mon projet.

Le fichier Gruntfile.js

La configuration des tâches Grunt passe par le fichier Gruntfile.js, j'ai mis le mien et vais en détailler les tâches :

module.exports = function(grunt) {
  'use strict';
 
  // Force use of Unix newlines
  grunt.util.linefeed = '\n';
 
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    bowerrc: grunt.file.readJSON('.bowerrc'),
 
    // Path configuration from Gruntfile.js
    dirs: {
      'vendor': '<%= bowerrc.directory %>',
      'bootstrap': {
        'js': '<%= dirs.vendor %>/bootstrap/js',
        'less': '<%= dirs.vendor %>/bootstrap/less'
      },
      'css': 'css',
      'less': 'less',
      'js': 'js'
    },
 
    src: {
      output: {
        'bootstrap': {
          'js': '<%= dirs.vendor %>/bootstrap/my/js/bootstrap.js',
          'css': '<%= dirs.vendor %>/bootstrap/my/css/bootstrap.css'
        },
        'boldy': {
          css: {
            'screen': '<%= dirs.css %>/screen.css',
            'indefero': '<%= dirs.css %>/indefero.css'
          },
          js: '<%= dirs.js %>/global.js'
        }
      },
 
      input: {
        bootstrap: {
          'js': [
            '<%= dirs.bootstrap.js %>/dropdown.js',
            '<%= dirs.bootstrap.js %>/tooltip.js',
            '<%= dirs.bootstrap.js %>/collapse.js',
            '<%= dirs.bootstrap.js %>/transition.js',
            '<%= dirs.bootstrap.js %>/carousel.js'
          ],
          'less': [
            '<%= dirs.less %>/bootstrap.less'
          ]
        },
        indefero: '<%= dirs.css %>/indefero.src.css',
        less: '<%= dirs.less %>/boldy_boot.less',
        js: [
          '<%= dirs.vendor %>/jquery-cookie/jquery.cookie.js',
          '<%= src.output.bootstrap.js %>',
          '<%= dirs.vendor %>/scroll-to-top/jquery.scrollToTop.min.js',
          '<%= dirs.js %>/js/post.js',
          '<%= dirs.vendor %>/async-gravatars/async-gravatars.js',
          '<%= dirs.vendor %>/jquery-colorbox/jquery.colorbox-min.js',
          '<%= dirs.vendor %>/nwxforms/src/nwxforms.js',
          '<%= dirs.js %>/boldy.js'
        ]
      },
    },
 
    // Banner
    banner: '/*!\n' +
              ' * Boldy for Dotclear v<%= pkg.version %>\n' +
              ' * Original theme by Site5 (http://www.s5themes.com)\n' +
              ' * Under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt\n' +
              ' * Ported on Dotclear by <%= pkg.author %> (<%= pkg.homepage %>)\n' +
              ' * Copyright 2012-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
              ' */',
 
 
 
    /********************************** Task **********************************/
    // Bower
    bower: {
      install: {
        options: {
          targetDir: '<%= dirs.vendor %>',
          cleanTargetDir: true,
          layout: 'byComponent',
          install: true,
          copy: false,
          verbose: true
        }
      }
    },
 
 
    // Concatenation
    concat: {
      bootstrap: {
        src: '<%= src.input.bootstrap.js %>',
        dest: '<%= src.output.bootstrap.js %>'
      },
    },
 
 
    // LESS
    less: {
      boldy: {
        options: {
          compress: true,
          cleancss: true,
          report: 'gzip',
          strictImports: true,
        },
        files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      boldy_debug: {
          options: {
            compress: false,
            cleancss: false,
            report: 'none',
            strictImports: true,
          },
          files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      bootstrap: {
        files: { '<%= src.output.bootstrap.css %>': '<%= src.input.bootstrap.less %>'}
      }
    },
 
 
    // Watcher
    watch: {
      boldy: {
        files: ['less/*.less'],
        tasks: ['less:boldy_debug'],
        options: {
          spawn: false,
        },
      },
    },
 
 
    // CSSmin
    cssmin: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.css.indefero %>': '<%= src.input.indefero %>'
        }
      }
    },
 
 
    // Uglify
    uglify: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.js %>': '<%= src.input.js %>',
        }
      }
    }
 
  });
 
 
  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-bower-task');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
 
 
  // Callable tasks
  grunt.registerTask('default', ['boldy', 'cssmin', 'concat:bootstrap', 'uglify']);
  grunt.registerTask('w', ['watch:boldy']);
 
  grunt.registerTask('bootstrap', ['concat:bootstrap', 'less:bootstrap']);
  grunt.registerTask('boldy', ['less:boldy']);
 
  grunt.registerTask('full', ['bower', 'bootstrap', 'default']);
  grunt.registerTask('all', ['full']); // Alias
};

Tâche bower

Via le plugin grunt-bower-task.

Cette tâche est là pour exécuter Bower au travers de Grunt, notez que le plugin n'interprète pas le .bowerrc, je l'ai donc chargé moi même (bowerrc: grunt.file.readJSON('.bowerrc')) afin de passer les mêmes options à la tâche Grunt qu'à Bower.

Tâche Bootstrap

Via les plugins grunt-contrib-less & grunt-contrib-concat.

Le but de cette tâche est de ne pas utiliser la version distribuée de Bootstrap mais de reconstruire ma propre version. Pour celà,

  • je reconstruis le JavaScript à partir des sources et du plugin concat,
  • je recompile les CSS à partir d'une version allégée du bootstrap.less :
/* -- BEGIN LICENSE BLOCK ---------------------------------------
# This file is part of Boldy, a theme for Dotclear
#
# Theme by Site5 (http://www.s5themes.com)
# under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt
# Ported on Dotclear by Guillaume Kulakowski (http://www.llaumgui.com)
#
# -- END LICENSE BLOCK ----------------------------------------- */
 
// Core variables and mixins
@import "../vendor/bootstrap/less/variables.less";
@import "../vendor/bootstrap/less/mixins.less";
 
// My override
@import "bootstrap_variables.less";
//@import "bootstrap_mixins.less";
 
// Reset
@import "../vendor/bootstrap/less/normalize.less";
@import "../vendor/bootstrap/less/print.less";
 
// Core CSS
@import "../vendor/bootstrap/less/scaffolding.less";
@import "../vendor/bootstrap/less/type.less";
@import "../vendor/bootstrap/less/code.less";
@import "../vendor/bootstrap/less/grid.less";
//@import "../vendor/bootstrap/less/tables.less";
@import "../vendor/bootstrap/less/forms.less";
@import "../vendor/bootstrap/less/buttons.less";
 
// Components
@import "../vendor/bootstrap/less/component-animations.less";
//@import "../vendor/bootstrap/less/glyphicons.less";
@import "../vendor/bootstrap/less/dropdowns.less";
//@import "../vendor/bootstrap/less/button-groups.less";
//@import "../vendor/bootstrap/less/input-groups.less";
@import "../vendor/bootstrap/less/navs.less";
@import "../vendor/bootstrap/less/navbar.less";
//@import "../vendor/bootstrap/less/breadcrumbs.less";
@import "../vendor/bootstrap/less/pagination.less";
@import "../vendor/bootstrap/less/pager.less";
//@import "../vendor/bootstrap/less/labels.less";
//@import "../vendor/bootstrap/less/badges.less";
//@import "../vendor/bootstrap/less/jumbotron.less";
@import "../vendor/bootstrap/less/thumbnails.less";
@import "../vendor/bootstrap/less/alerts.less";
//@import "../vendor/bootstrap/less/progress-bars.less";
//@import "../vendor/bootstrap/less/media.less";
@import "../vendor/bootstrap/less/list-group.less";
@import "../vendor/bootstrap/less/panels.less";
@import "../vendor/bootstrap/less/wells.less";
//@import "../vendor/bootstrap/less/close.less";
 
// Components w/ JavaScript
//@import "../vendor/bootstrap/less/modals.less";
@import "../vendor/bootstrap/less/tooltip.less";
//@import "../vendor/bootstrap/less/popovers.less";
@import "../vendor/bootstrap/less/carousel.less";
 
// Utility classes
@import "../vendor/bootstrap/less/utilities.less";
@import "../vendor/bootstrap/less/responsive-utilities.less";

Remarquez que j'utilise mes propres variables après celles de Bootstrap afin de les écraser et de modifier Bootstrap directement à la source.

Tâche Boldy

Via les plugins grunt-contrib-uglify, grunt-contrib-less & grunt-contrib-cssmin.

Comme en recompilant Bootstrap à partir des sources LESS j'ai adhéré au concept de LESS, j'ai réécrit le thème de mon blog en LESS.

Cette tâche boldy sert donc :

  • à générer le CSS minifié à partir des sources LESS (grunt-contrib-less),
  • à minifier la CSS pour mon thème indefero qui reste en CSS (grunt-contrib-cssmin),
  • et à minifier les Javascripts (grunt-contrib-uglify).

Tâche watch

Via le plugin grunt-contrib-watch.

Pour compiler du LESS en CSS, il y a 2 méthodes.

  • soit passer par le less.js qui va parser en JS les fichiers LESS à chaque chargement de page (2 à 3 secondes pour mon blog),
  • soit passer par une tâche watch dans Grunt, qui va écouter les modifications sur les fichiers LESS et recompiler un CSS.

C'est cette dernière solution que j'ai retenu et donc c'est à cela que sert la tâche watch.

En conclusion

Pour conclure, je dirais que la mise en place de ces outils (surtout pour la première fois) est certes chronophage, mais une fois que ça tourne, pour mettre à jour les libs (Bootstrap, jQuery, etc.), une seule ligne de commande suffit (grunt full). De plus, une fois Grunt et Bower en place (et maîtrisés), passer de CSS à LESS a été super simple.

Pour finir, je regrette presque que nodejs n'ait pas été inclus dans Fedora plus tôt car il propose de formidables outils indispensables aux développeurs (front) web.

Et pour voir ce que ça donne, une petite vidéo:

Grunt, Bower, LESS & Bootstrap

Guillaume Kulakowski
Dans mon dernier billet, j'avais évoqué mon engouement pour Bower, une solution de gestion de dépendances web. J'avais alors regretté le fait de ne pas pouvoir exécuter des tâches post-installation afin de retravailler la version distribuée de Bootstrap pour lalléger (comme on peut le faire ici). Comme j'en avais émis l'idée dans ce même billet, j'ai entrepris de ne plus utiliser Bower directement mais de lutiliser au travers de Grunt.

grunt-bower.png

Si vous ne connaissez pas encore Grunt, il s'agit d'un ordonnanceur au même titre que Maven, ant, ou encore Phing, mais basé sur nodejs et utilisant une syntaxe JavaScript.

Installation de Bower & Grunt sous Fedora 20

Après avoir installé nodejs et npm via yum, il faudra installer Bower et Grunt avec npm. En effet, Bower est absent des dépôts Fedora et Grunt est bien disponible mais sans grunt-cli, ce qui en enlève tout lintérêt...

sudo yum install npm -y
sudo npm install bower grunt grunt-cli -g

Notez au passage le fait que l'on fasse l'installation avec l'option -g pour avoir Grunt et Bower installés au niveau du système (/usr/lib/node_modules). Les autres installations se feront toujours à l'aide de npm mais sans cette option. Ceci permet de tout avoir à la racine du projet web, dans un répertoire node_modules (qu'il faudra exclure de la gestion de sources).

Voila, maintenant on rajoute un fichier packages.json et on installe les dépendances avec un npm install :

{
  "name": "Boldy",
  "version": "1.1.2",
  "description": "The Boldy theme for Dotclear",
  "repository": {
    "type": "git",
  },
  "author": "Guillaume Kulakowski",
  "homepage": "http://www.llaumgui.com",
  "devDependencies": {
    "bower": "latest",
    "grunt": "~0.4.2",
    "grunt-contrib-uglify": "latest",
    "grunt-contrib-concat": "latest",
    "grunt-contrib-less": "latest",
    "grunt-bower-task": "latest",
    "grunt-contrib-watch": "latest"
    "grunt-contrib-cssmin": "latest"
  }
}

J'ai maintenant un environnement de travail avec Bower et Grunt ainsi que les plugins nécessaires à mon projet.

Le fichier Gruntfile.js

La configuration des tâches Grunt passe par le fichier Gruntfile.js, j'ai mis le mien et vais en détailler les tâches :

module.exports = function(grunt) {
  'use strict';
 
  // Force use of Unix newlines
  grunt.util.linefeed = '\n';
 
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    bowerrc: grunt.file.readJSON('.bowerrc'),
 
    // Path configuration from Gruntfile.js
    dirs: {
      'vendor': '<%= bowerrc.directory %>',
      'bootstrap': {
        'js': '<%= dirs.vendor %>/bootstrap/js',
        'less': '<%= dirs.vendor %>/bootstrap/less'
      },
      'css': 'css',
      'less': 'less',
      'js': 'js'
    },
 
    src: {
      output: {
        'bootstrap': {
          'js': '<%= dirs.vendor %>/bootstrap/my/js/bootstrap.js',
          'css': '<%= dirs.vendor %>/bootstrap/my/css/bootstrap.css'
        },
        'boldy': {
          css: {
            'screen': '<%= dirs.css %>/screen.css',
            'indefero': '<%= dirs.css %>/indefero.css'
          },
          js: '<%= dirs.js %>/global.js'
        }
      },
 
      input: {
        bootstrap: {
          'js': [
            '<%= dirs.bootstrap.js %>/dropdown.js',
            '<%= dirs.bootstrap.js %>/tooltip.js',
            '<%= dirs.bootstrap.js %>/collapse.js',
            '<%= dirs.bootstrap.js %>/transition.js',
            '<%= dirs.bootstrap.js %>/carousel.js'
          ],
          'less': [
            '<%= dirs.less %>/bootstrap.less'
          ]
        },
        indefero: '<%= dirs.css %>/indefero.src.css',
        less: '<%= dirs.less %>/boldy_boot.less',
        js: [
          '<%= dirs.vendor %>/jquery-cookie/jquery.cookie.js',
          '<%= src.output.bootstrap.js %>',
          '<%= dirs.vendor %>/scroll-to-top/jquery.scrollToTop.min.js',
          '<%= dirs.js %>/js/post.js',
          '<%= dirs.vendor %>/async-gravatars/async-gravatars.js',
          '<%= dirs.vendor %>/jquery-colorbox/jquery.colorbox-min.js',
          '<%= dirs.vendor %>/nwxforms/src/nwxforms.js',
          '<%= dirs.js %>/boldy.js'
        ]
      },
    },
 
    // Banner
    banner: '/*!\n' +
              ' * Boldy for Dotclear v<%= pkg.version %>\n' +
              ' * Original theme by Site5 (http://www.s5themes.com)\n' +
              ' * Under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt\n' +
              ' * Ported on Dotclear by <%= pkg.author %> (<%= pkg.homepage %>)\n' +
              ' * Copyright 2012-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
              ' */',
 
 
 
    /********************************** Task **********************************/
    // Bower
    bower: {
      install: {
        options: {
          targetDir: '<%= dirs.vendor %>',
          cleanTargetDir: true,
          layout: 'byComponent',
          install: true,
          copy: false,
          verbose: true
        }
      }
    },
 
 
    // Concatenation
    concat: {
      bootstrap: {
        src: '<%= src.input.bootstrap.js %>',
        dest: '<%= src.output.bootstrap.js %>'
      },
    },
 
 
    // LESS
    less: {
      boldy: {
        options: {
          compress: true,
          cleancss: true,
          report: 'gzip',
          strictImports: true,
        },
        files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      boldy_debug: {
          options: {
            compress: false,
            cleancss: false,
            report: 'none',
            strictImports: true,
          },
          files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      bootstrap: {
        files: { '<%= src.output.bootstrap.css %>': '<%= src.input.bootstrap.less %>'}
      }
    },
 
 
    // Watcher
    watch: {
      boldy: {
        files: ['less/*.less'],
        tasks: ['less:boldy_debug'],
        options: {
          spawn: false,
        },
      },
    },
 
 
    // CSSmin
    cssmin: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.css.indefero %>': '<%= src.input.indefero %>'
        }
      }
    },
 
 
    // Uglify
    uglify: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.js %>': '<%= src.input.js %>',
        }
      }
    }
 
  });
 
 
  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-bower-task');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
 
 
  // Callable tasks
  grunt.registerTask('default', ['boldy', 'cssmin', 'concat:bootstrap', 'uglify']);
  grunt.registerTask('w', ['watch:boldy']);
 
  grunt.registerTask('bootstrap', ['concat:bootstrap', 'less:bootstrap']);
  grunt.registerTask('boldy', ['less:boldy']);
 
  grunt.registerTask('full', ['bower', 'bootstrap', 'default']);
  grunt.registerTask('all', ['full']); // Alias
};

Tâche bower

Via le plugin grunt-bower-task.

Cette tâche est là pour exécuter Bower au travers de Grunt, notez que le plugin n'interprète pas le .bowerrc, je l'ai donc chargé moi même (bowerrc: grunt.file.readJSON('.bowerrc')) afin de passer les mêmes options à la tâche Grunt qu'à Bower.

Tâche Bootstrap

Via les plugins grunt-contrib-less & grunt-contrib-concat.

Le but de cette tâche est de ne pas utiliser la version distribuée de Bootstrap mais de reconstruire ma propre version. Pour celà,

  • je reconstruis le JavaScript à partir des sources et du plugin concat,
  • je recompile les CSS à partir d'une version allégée du bootstrap.less :
/* -- BEGIN LICENSE BLOCK ---------------------------------------
# This file is part of Boldy, a theme for Dotclear
#
# Theme by Site5 (http://www.s5themes.com)
# under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt
# Ported on Dotclear by Guillaume Kulakowski (http://www.llaumgui.com)
#
# -- END LICENSE BLOCK ----------------------------------------- */
 
// Core variables and mixins
@import "../vendor/bootstrap/less/variables.less";
@import "../vendor/bootstrap/less/mixins.less";
 
// My override
@import "bootstrap_variables.less";
//@import "bootstrap_mixins.less";
 
// Reset
@import "../vendor/bootstrap/less/normalize.less";
@import "../vendor/bootstrap/less/print.less";
 
// Core CSS
@import "../vendor/bootstrap/less/scaffolding.less";
@import "../vendor/bootstrap/less/type.less";
@import "../vendor/bootstrap/less/code.less";
@import "../vendor/bootstrap/less/grid.less";
//@import "../vendor/bootstrap/less/tables.less";
@import "../vendor/bootstrap/less/forms.less";
@import "../vendor/bootstrap/less/buttons.less";
 
// Components
@import "../vendor/bootstrap/less/component-animations.less";
//@import "../vendor/bootstrap/less/glyphicons.less";
@import "../vendor/bootstrap/less/dropdowns.less";
//@import "../vendor/bootstrap/less/button-groups.less";
//@import "../vendor/bootstrap/less/input-groups.less";
@import "../vendor/bootstrap/less/navs.less";
@import "../vendor/bootstrap/less/navbar.less";
//@import "../vendor/bootstrap/less/breadcrumbs.less";
@import "../vendor/bootstrap/less/pagination.less";
@import "../vendor/bootstrap/less/pager.less";
//@import "../vendor/bootstrap/less/labels.less";
//@import "../vendor/bootstrap/less/badges.less";
//@import "../vendor/bootstrap/less/jumbotron.less";
@import "../vendor/bootstrap/less/thumbnails.less";
@import "../vendor/bootstrap/less/alerts.less";
//@import "../vendor/bootstrap/less/progress-bars.less";
//@import "../vendor/bootstrap/less/media.less";
@import "../vendor/bootstrap/less/list-group.less";
@import "../vendor/bootstrap/less/panels.less";
@import "../vendor/bootstrap/less/wells.less";
//@import "../vendor/bootstrap/less/close.less";
 
// Components w/ JavaScript
//@import "../vendor/bootstrap/less/modals.less";
@import "../vendor/bootstrap/less/tooltip.less";
//@import "../vendor/bootstrap/less/popovers.less";
@import "../vendor/bootstrap/less/carousel.less";
 
// Utility classes
@import "../vendor/bootstrap/less/utilities.less";
@import "../vendor/bootstrap/less/responsive-utilities.less";

Remarquez que j'utilise mes propres variables après celles de Bootstrap afin de les écraser et de modifier Bootstrap directement à la source.

Tâche Boldy

Via les plugins grunt-contrib-uglify, grunt-contrib-less & grunt-contrib-cssmin.

Comme en recompilant Bootstrap à partir des sources LESS j'ai adhéré au concept de LESS, j'ai réécrit le thème de mon blog en LESS.

Cette tâche boldy sert donc :

  • à générer le CSS minifié à partir des sources LESS (grunt-contrib-less),
  • à minifier la CSS pour mon thème indefero qui reste en CSS (grunt-contrib-cssmin),
  • et à minifier les Javascripts (grunt-contrib-uglify).

Tâche watch

Via le plugin grunt-contrib-watch.

Pour compiler du LESS en CSS, il y a 2 méthodes.

  • soit passer par le less.js qui va parser en JS les fichiers LESS à chaque chargement de page (2 à 3 secondes pour mon blog),
  • soit passer par une tâche watch dans Grunt, qui va écouter les modifications sur les fichiers LESS et recompiler un CSS.

C'est cette dernière solution que j'ai retenu et donc c'est à cela que sert la tâche watch.

En conclusion

Pour conclure, je dirais que la mise en place de ces outils (surtout pour la première fois) est certes chronophage, mais une fois que ça tourne, pour mettre à jour les libs (Bootstrap, jQuery, etc.), une seule ligne de commande suffit (grunt full). De plus, une fois Grunt et Bower en place (et maîtrisés), passer de CSS à LESS a été super simple.

Pour finir, je regrette presque que nodejs n'ait pas été inclus dans Fedora plus tôt car il propose de formidables outils indispensables aux développeurs (front) web.

Et pour voir ce que ça donne, une petite vidéo:

Grunt, Bower, LESS & Bootstrap

Guillaume Kulakowski
Dans mon dernier billet, j'avais évoqué mon engouement pour Bower, une solution de gestion de dépendances web. J'avais alors regretté le fait de ne pas pouvoir exécuter des tâches post-installation afin de retravailler la version distribuée de Bootstrap pour lalléger (comme on peut le faire ici). Comme j'en avais émis l'idée dans ce même billet, j'ai entrepris de ne plus utiliser Bower directement mais de lutiliser au travers de Grunt.

grunt-bower.png

Si vous ne connaissez pas encore Grunt, il s'agit d'un ordonnanceur au même titre que Maven, ant, ou encore Phing, mais basé sur nodejs et utilisant une syntaxe JavaScript.

Installation de Bower & Grunt sous Fedora 20

Après avoir installé nodejs et npm via yum, il faudra installer Bower et Grunt avec npm. En effet, Bower est absent des dépôts Fedora et Grunt est bien disponible mais sans grunt-cli, ce qui en enlève tout lintérêt...

sudo yum install npm -y
sudo npm install bower grunt grunt-cli -g

Notez au passage le fait que l'on fasse l'installation avec l'option -g pour avoir Grunt et Bower installés au niveau du système (/usr/lib/node_modules). Les autres installations se feront toujours à l'aide de npm mais sans cette option. Ceci permet de tout avoir à la racine du projet web, dans un répertoire node_modules (qu'il faudra exclure de la gestion de sources).

Voila, maintenant on rajoute un fichier packages.json et on installe les dépendances avec un npm install :

{
  "name": "Boldy",
  "version": "1.1.2",
  "description": "The Boldy theme for Dotclear",
  "repository": {
    "type": "git",
  },
  "author": "Guillaume Kulakowski",
  "homepage": "http://www.llaumgui.com",
  "devDependencies": {
    "bower": "latest",
    "grunt": "~0.4.2",
    "grunt-contrib-uglify": "latest",
    "grunt-contrib-concat": "latest",
    "grunt-contrib-less": "latest",
    "grunt-bower-task": "latest",
    "grunt-contrib-watch": "latest"
    "grunt-contrib-cssmin": "latest"
  }
}

J'ai maintenant un environnement de travail avec Bower et Grunt ainsi que les plugins nécessaires à mon projet.

Le fichier Gruntfile.js

La configuration des tâches Grunt passe par le fichier Gruntfile.js, j'ai mis le mien et vais en détailler les tâches :

module.exports = function(grunt) {
  'use strict';
 
  // Force use of Unix newlines
  grunt.util.linefeed = '\n';
 
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    bowerrc: grunt.file.readJSON('.bowerrc'),
 
    // Path configuration from Gruntfile.js
    dirs: {
      'vendor': '<%= bowerrc.directory %>',
      'bootstrap': {
        'js': '<%= dirs.vendor %>/bootstrap/js',
        'less': '<%= dirs.vendor %>/bootstrap/less'
      },
      'css': 'css',
      'less': 'less',
      'js': 'js'
    },
 
    src: {
      output: {
        'bootstrap': {
          'js': '<%= dirs.vendor %>/bootstrap/my/js/bootstrap.js',
          'css': '<%= dirs.vendor %>/bootstrap/my/css/bootstrap.css'
        },
        'boldy': {
          css: {
            'screen': '<%= dirs.css %>/screen.css',
            'indefero': '<%= dirs.css %>/indefero.css'
          },
          js: '<%= dirs.js %>/global.js'
        }
      },
 
      input: {
        bootstrap: {
          'js': [
            '<%= dirs.bootstrap.js %>/dropdown.js',
            '<%= dirs.bootstrap.js %>/tooltip.js',
            '<%= dirs.bootstrap.js %>/collapse.js',
            '<%= dirs.bootstrap.js %>/transition.js',
            '<%= dirs.bootstrap.js %>/carousel.js'
          ],
          'less': [
            '<%= dirs.less %>/bootstrap.less'
          ]
        },
        indefero: '<%= dirs.css %>/indefero.src.css',
        less: '<%= dirs.less %>/boldy_boot.less',
        js: [
          '<%= dirs.vendor %>/jquery-cookie/jquery.cookie.js',
          '<%= src.output.bootstrap.js %>',
          '<%= dirs.vendor %>/scroll-to-top/jquery.scrollToTop.min.js',
          '<%= dirs.js %>/js/post.js',
          '<%= dirs.vendor %>/async-gravatars/async-gravatars.js',
          '<%= dirs.vendor %>/jquery-colorbox/jquery.colorbox-min.js',
          '<%= dirs.vendor %>/nwxforms/src/nwxforms.js',
          '<%= dirs.js %>/boldy.js'
        ]
      },
    },
 
    // Banner
    banner: '/*!\n' +
              ' * Boldy for Dotclear v<%= pkg.version %>\n' +
              ' * Original theme by Site5 (http://www.s5themes.com)\n' +
              ' * Under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt\n' +
              ' * Ported on Dotclear by <%= pkg.author %> (<%= pkg.homepage %>)\n' +
              ' * Copyright 2012-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
              ' */',
 
 
 
    /********************************** Task **********************************/
    // Bower
    bower: {
      install: {
        options: {
          targetDir: '<%= dirs.vendor %>',
          cleanTargetDir: true,
          layout: 'byComponent',
          install: true,
          copy: false,
          verbose: true
        }
      }
    },
 
 
    // Concatenation
    concat: {
      bootstrap: {
        src: '<%= src.input.bootstrap.js %>',
        dest: '<%= src.output.bootstrap.js %>'
      },
    },
 
 
    // LESS
    less: {
      boldy: {
        options: {
          compress: true,
          cleancss: true,
          report: 'gzip',
          strictImports: true,
        },
        files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      boldy_debug: {
          options: {
            compress: false,
            cleancss: false,
            report: 'none',
            strictImports: true,
          },
          files: { '<%= src.output.boldy.css.screen %>': '<%= src.input.less %>'},
      },
      bootstrap: {
        files: { '<%= src.output.bootstrap.css %>': '<%= src.input.bootstrap.less %>'}
      }
    },
 
 
    // Watcher
    watch: {
      boldy: {
        files: ['less/*.less'],
        tasks: ['less:boldy_debug'],
        options: {
          spawn: false,
        },
      },
    },
 
 
    // CSSmin
    cssmin: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.css.indefero %>': '<%= src.input.indefero %>'
        }
      }
    },
 
 
    // Uglify
    uglify: {
      boldy: {
        options: {
          report: 'gzip',
          banner: '<%= banner %>'
        },
        files: {
          '<%= src.output.boldy.js %>': '<%= src.input.js %>',
        }
      }
    }
 
  });
 
 
  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-bower-task');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
 
 
  // Callable tasks
  grunt.registerTask('default', ['boldy', 'cssmin', 'concat:bootstrap', 'uglify']);
  grunt.registerTask('w', ['watch:boldy']);
 
  grunt.registerTask('bootstrap', ['concat:bootstrap', 'less:bootstrap']);
  grunt.registerTask('boldy', ['less:boldy']);
 
  grunt.registerTask('full', ['bower', 'bootstrap', 'default']);
  grunt.registerTask('all', ['full']); // Alias
};

Tâche bower

Via le plugin grunt-bower-task.

Cette tâche est là pour exécuter Bower au travers de Grunt, notez que le plugin n'interprète pas le .bowerrc, je l'ai donc chargé moi même (bowerrc: grunt.file.readJSON('.bowerrc')) afin de passer les mêmes options à la tâche Grunt qu'à Bower.

Tâche Bootstrap

Via les plugins grunt-contrib-less & grunt-contrib-concat.

Le but de cette tâche est de ne pas utiliser la version distribuée de Bootstrap mais de reconstruire ma propre version. Pour celà,

  • je reconstruis le JavaScript à partir des sources et du plugin concat,
  • je recompile les CSS à partir d'une version allégée du bootstrap.less :
/* -- BEGIN LICENSE BLOCK ---------------------------------------
# This file is part of Boldy, a theme for Dotclear
#
# Theme by Site5 (http://www.s5themes.com)
# under a GPLv2 http://www.gnu.org/licenses/gpl-2.0.txt
# Ported on Dotclear by Guillaume Kulakowski (http://www.llaumgui.com)
#
# -- END LICENSE BLOCK ----------------------------------------- */
 
// Core variables and mixins
@import "../vendor/bootstrap/less/variables.less";
@import "../vendor/bootstrap/less/mixins.less";
 
// My override
@import "bootstrap_variables.less";
//@import "bootstrap_mixins.less";
 
// Reset
@import "../vendor/bootstrap/less/normalize.less";
@import "../vendor/bootstrap/less/print.less";
 
// Core CSS
@import "../vendor/bootstrap/less/scaffolding.less";
@import "../vendor/bootstrap/less/type.less";
@import "../vendor/bootstrap/less/code.less";
@import "../vendor/bootstrap/less/grid.less";
//@import "../vendor/bootstrap/less/tables.less";
@import "../vendor/bootstrap/less/forms.less";
@import "../vendor/bootstrap/less/buttons.less";
 
// Components
@import "../vendor/bootstrap/less/component-animations.less";
//@import "../vendor/bootstrap/less/glyphicons.less";
@import "../vendor/bootstrap/less/dropdowns.less";
//@import "../vendor/bootstrap/less/button-groups.less";
//@import "../vendor/bootstrap/less/input-groups.less";
@import "../vendor/bootstrap/less/navs.less";
@import "../vendor/bootstrap/less/navbar.less";
//@import "../vendor/bootstrap/less/breadcrumbs.less";
@import "../vendor/bootstrap/less/pagination.less";
@import "../vendor/bootstrap/less/pager.less";
//@import "../vendor/bootstrap/less/labels.less";
//@import "../vendor/bootstrap/less/badges.less";
//@import "../vendor/bootstrap/less/jumbotron.less";
@import "../vendor/bootstrap/less/thumbnails.less";
@import "../vendor/bootstrap/less/alerts.less";
//@import "../vendor/bootstrap/less/progress-bars.less";
//@import "../vendor/bootstrap/less/media.less";
@import "../vendor/bootstrap/less/list-group.less";
@import "../vendor/bootstrap/less/panels.less";
@import "../vendor/bootstrap/less/wells.less";
//@import "../vendor/bootstrap/less/close.less";
 
// Components w/ JavaScript
//@import "../vendor/bootstrap/less/modals.less";
@import "../vendor/bootstrap/less/tooltip.less";
//@import "../vendor/bootstrap/less/popovers.less";
@import "../vendor/bootstrap/less/carousel.less";
 
// Utility classes
@import "../vendor/bootstrap/less/utilities.less";
@import "../vendor/bootstrap/less/responsive-utilities.less";

Remarquez que j'utilise mes propres variables après celles de Bootstrap afin de les écraser et de modifier Bootstrap directement à la source.

Tâche Boldy

Via les plugins grunt-contrib-uglify, grunt-contrib-less & grunt-contrib-cssmin.

Comme en recompilant Bootstrap à partir des sources LESS j'ai adhéré au concept de LESS, j'ai réécrit le thème de mon blog en LESS.

Cette tâche boldy sert donc :

  • à générer le CSS minifié à partir des sources LESS (grunt-contrib-less),
  • à minifier la CSS pour mon thème indefero qui reste en CSS (grunt-contrib-cssmin),
  • et à minifier les Javascripts (grunt-contrib-uglify).

Tâche watch

Via le plugin grunt-contrib-watch.

Pour compiler du LESS en CSS, il y a 2 méthodes.

  • soit passer par le less.js qui va parser en JS les fichiers LESS à chaque chargement de page (2 à 3 secondes pour mon blog),
  • soit passer par une tâche watch dans Grunt, qui va écouter les modifications sur les fichiers LESS et recompiler un CSS.

C'est cette dernière solution que j'ai retenu et donc c'est à cela que sert la tâche watch.

En conclusion

Pour conclure, je dirais que la mise en place de ces outils (surtout pour la première fois) est certes chronophage, mais une fois que ça tourne, pour mettre à jour les libs (Bootstrap, jQuery, etc.), une seule ligne de commande suffit (grunt full). De plus, une fois Grunt et Bower en place (et maîtrisés), passer de CSS à LESS a été super simple.

Pour finir, je regrette presque que nodejs n'ait pas été inclus dans Fedora plus tôt car il propose de formidables outils indispensables aux développeurs (front) web.

Et pour voir ce que ça donne, une petite vidéo:

Bower pour gérer les dépendances du thème de mon blog

Guillaume Kulakowski

C'est à la lecture de cet excellent article de Raphaël Goetter (dont le livre "CSS avancées vers HTML5 et CSS3" fut un de mes livres de chevet) sur Alsacreations que j'ai décidé de mettre en place Bower pour gérer les dépendances du thème de mon blog.

Pour ceux qui ne connaissent pas (encore) Bower et qui n'ont pas eu la curiosité de cliquer sur les liens ci-dessus: sachez juste que Bower est un gestionnaire de paquets pour le web (JS, CSS, etc). On pourrait même faire le raccourci en disant que Bower est au web ce que Composer est à PHP.

Les avantages immédiats que j'y trouve :

  • Un simple bower update et je mets à jour toutes les librairies tierces.
  • Le cloisonnement entre mon code et le code tiers. En effet, ce dernier se trouve dans un répertoire séparé: vendor (les habitudes de symfony2 ;-)).
  • Comme au travail je fais beaucoup de Symfony2 et d'eZ Publish 5, j'utilise pas mal composer, avec Bower je ne suis donc pas perdu.

Les inconvénients que j'y trouve :

  • Bower ne fait que récupérer les dépendances. Il ne permet pas (encore), comme (entre autre) Composer, de lancer des scripts post-installation ou post-mise-à-jour.
  • Bower récupère Bootstrap dans son intégralité alors que moi, j'aimerais l'optimiser et en réduire le poids.

Avant de pouvoir passer en production cette nouvelle version de mon blog, qui se doit d'être ISO avec la précédente, il me reste donc à régler quelques problèmes :

  • Optimiser ou reconstruire Bootstrap après l'avoir récupéré.
  • Minifier tout mon code.
  • Réfléchir à comment le pousser en production.
    En effet, Bower tournant sous node.js, je n'ai pas spécialement envie de déployer node sur mon serveur. Jusquà présent jutilisais GIT pour déployer le code source et je lançais juste un script shell pour minifier CSS et JS via YUI Compressor. Là je pense que je vais, soit devoir héberger les scripts minifiés sur GIT (ce qui n'est pas une bonne pratique) soit mettre en place un Jenkins (pour la construction du paquet) et un Capistrano (pour le déploiement). Autant au boulot le couple Jenkins/Capistrano m'est indispensable autant là pour déployer une fois de temps en temps une nouvelle version de mon blog, je pense que je vais opter pour les fichiers dans GIT.

Pour ceux que ça intéressent, ma configuration Bower :

# cat .bowerrc
{
    "directory": "vendor"
}
# cat bower.json
{
    "name": "Boldy",
    "version": "1.1",
    "main": [
        "css/screen.css",
        "css/indefero.css",
        "js/global.js"
    ],
    "ignore": [
        ".jshintrc",
        "**/*.txt"
    ],
    "private": true,
    "dependencies": {
        "jquery": "~1.*",
        "bootstrap": "3.0.*",
        "jquery.browser": "latest",
        "jquery-colorbox": "latest",
        "jquery-cookie": "latest",
        "scroll-to-top": "latest",
        "headjs": "latest"
    }
}

Bower pour gérer les dépendances du thème de mon blog

Guillaume Kulakowski

C'est à la lecture de cet excellent article de Raphaël Goetter (dont le livre "CSS avancées vers HTML5 et CSS3" fut un de mes livres de chevet) sur Alsacreations que j'ai décidé de mettre en place Bower pour gérer les dépendances du thème de mon blog.

Pour ceux qui ne connaissent pas (encore) Bower et qui n'ont pas eu la curiosité de cliquer sur les liens ci-dessus: sachez juste que Bower est un gestionnaire de paquets pour le web (JS, CSS, etc). On pourrait même faire le raccourci en disant que Bower est au web ce que Composer est à PHP.

Les avantages immédiats que j'y trouve :

  • Un simple bower update et je mets à jour toutes les librairies tierces.
  • Le cloisonnement entre mon code et le code tiers. En effet, ce dernier se trouve dans un répertoire séparé: vendor (les habitudes de symfony2 ;-)).
  • Comme au travail je fais beaucoup de Symfony2 et d'eZ Publish 5, j'utilise pas mal composer, avec Bower je ne suis donc pas perdu.

Les inconvénients que j'y trouve :

  • Bower ne fait que récupérer les dépendances. Il ne permet pas (encore), comme (entre autre) Composer, de lancer des scripts post-installation ou post-mise-à-jour.
  • Bower récupère Bootstrap dans son intégralité alors que moi, j'aimerais l'optimiser et en réduire le poids.

Avant de pouvoir passer en production cette nouvelle version de mon blog, qui se doit d'être ISO avec la précédente, il me reste donc à régler quelques problèmes :

  • Optimiser ou reconstruire Bootstrap après l'avoir récupéré.
  • Minifier tout mon code.
  • Réfléchir à comment le pousser en production.
    En effet, Bower tournant sous node.js, je n'ai pas spécialement envie de déployer node sur mon serveur. Jusquà présent jutilisais GIT pour déployer le code source et je lançais juste un script shell pour minifier CSS et JS via YUI Compressor. Là je pense que je vais, soit devoir héberger les scripts minifiés sur GIT (ce qui n'est pas une bonne pratique) soit mettre en place un Jenkins (pour la construction du paquet) et un Capistrano (pour le déploiement). Autant au boulot le couple Jenkins/Capistrano m'est indispensable autant là pour déployer une fois de temps en temps une nouvelle version de mon blog, je pense que je vais opter pour les fichiers dans GIT.

Pour ceux que ça intéressent, ma configuration Bower :

# cat .bowerrc
{
    "directory": "vendor"
}
# cat bower.json
{
    "name": "Boldy",
    "version": "1.1",
    "main": [
        "css/screen.css",
        "css/indefero.css",
        "js/global.js"
    ],
    "ignore": [
        ".jshintrc",
        "**/*.txt"
    ],
    "private": true,
    "dependencies": {
        "jquery": "~1.*",
        "bootstrap": "3.0.*",
        "jquery.browser": "latest",
        "jquery-colorbox": "latest",
        "jquery-cookie": "latest",
        "scroll-to-top": "latest",
        "headjs": "latest"
    }
}

Bower pour gérer les dépendances du thème de mon blog

Guillaume Kulakowski

C'est à la lecture de cet excellent article de Raphaël Goetter (dont le livre "CSS avancées vers HTML5 et CSS3" fut un de mes livres de chevet) sur Alsacreations que j'ai décidé de mettre en place Bower pour gérer les dépendances du thème de mon blog.

Pour ceux qui ne connaissent pas (encore) Bower et qui n'ont pas eu la curiosité de cliquer sur les liens ci-dessus: sachez juste que Bower est un gestionnaire de paquets pour le web (JS, CSS, etc). On pourrait même faire le raccourci en disant que Bower est au web ce que Composer est à PHP.

Les avantages immédiats que j'y trouve :

  • Un simple bower update et je mets à jour toutes les librairies tierces.
  • Le cloisonnement entre mon code et le code tiers. En effet, ce dernier se trouve dans un répertoire séparé: vendor (les habitudes de symfony2 ;-)).
  • Comme au travail je fais beaucoup de Symfony2 et d'eZ Publish 5, j'utilise pas mal composer, avec Bower je ne suis donc pas perdu.

Les inconvénients que j'y trouve :

  • Bower ne fait que récupérer les dépendances. Il ne permet pas (encore), comme (entre autre) Composer, de lancer des scripts post-installation ou post-mise-à-jour.
  • Bower récupère Bootstrap dans son intégralité alors que moi, j'aimerais l'optimiser et en réduire le poids.

Avant de pouvoir passer en production cette nouvelle version de mon blog, qui se doit d'être ISO avec la précédente, il me reste donc à régler quelques problèmes :

  • Optimiser ou reconstruire Bootstrap après l'avoir récupéré.
  • Minifier tout mon code.
  • Réfléchir à comment le pousser en production.
    En effet, Bower tournant sous node.js, je n'ai pas spécialement envie de déployer node sur mon serveur. Jusquà présent jutilisais GIT pour déployer le code source et je lançais juste un script shell pour minifier CSS et JS via YUI Compressor. Là je pense que je vais, soit devoir héberger les scripts minifiés sur GIT (ce qui n'est pas une bonne pratique) soit mettre en place un Jenkins (pour la construction du paquet) et un Capistrano (pour le déploiement). Autant au boulot le couple Jenkins/Capistrano m'est indispensable autant là pour déployer une fois de temps en temps une nouvelle version de mon blog, je pense que je vais opter pour les fichiers dans GIT.

Pour ceux que ça intéressent, ma configuration Bower :

# cat .bowerrc
{
    "directory": "vendor"
}
# cat bower.json
{
    "name": "Boldy",
    "version": "1.1",
    "main": [
        "css/screen.css",
        "css/indefero.css",
        "js/global.js"
    ],
    "ignore": [
        ".jshintrc",
        "**/*.txt"
    ],
    "private": true,
    "dependencies": {
        "jquery": "~1.*",
        "bootstrap": "3.0.*",
        "jquery.browser": "latest",
        "jquery-colorbox": "latest",
        "jquery-cookie": "latest",
        "scroll-to-top": "latest",
        "headjs": "latest"
    }
}