Archives du mot-clé grunt

JavaScript : Utiliser ES6 maintenant avec Babel

Je vous ai parlé dans des articles précédent de la nouvelle version de JavaScript, ES6/ES2015, Harmony.
J’avais évoqué des outils afin de pouvoir utiliser ces nouveautés dés maintenant, malgré le support partiel dans les navigateurs ou dans de vieux navigateurs.

Babel est transpileur qui va transformer du code ES6 en code compatible ES5 (version compatible dans la plupart des navigateur (IE ….). Babel est la fusion de 2 projets : 6to5 et esnext.

Il supporte l’ensemble des nouveautés de ES6 et même certaines fonctionnalités de ES7. Il a l’avantage de produire un code compréhensible et ne nécessite pas l’inclusion d’un script additionnel dans votre page (comme traceur, un autre transpileur).

Babel est basé sur node et il existe des plugins pour la plupart des task runner JavaScript comme Grunt, Gulp, …

Voyons comment l’utiliser avec grunt.

Installation et utilisation

Tout d’abord, installons le package grunt pour babel. L’installation se fait via la commande :

npm install grunt-babel --save-dev

Nous définissons une tache nommée babel dans notre Gruntfile.js, qui va prendre nos fichiers écrit en ES6 et les transpiler en ES5.

J’ai configuré une tache avec 2 configurations, une pour le dev et une pour générer un package, dist.

 //transpilation to ES5
  babel: {
    options: {
      sourceMap: true,
      blacklist: ["strict"]
    },
    dev: {
      files: [{
        expand : true,
        cwd: '<%= yeoman.app %>/scripts/',
        src: ['**/*.js'],
        dest: '<%= yeoman.dist %>/scripts/',
        ext: '.js'
      }]
    },
    dist: {
      files: [{
        expand : true,
        cwd: '<%= yeoman.tmp %>/concat/scripts',
        src: '*.js',
        dest: '<%= yeoman.tmp %>/concat/scripts',
        ext: '.js'
      }]
    }
  }

Il est possible d’activer des options. Dans mon cas, j’ai activé les sourcesmaps et supprimé l’option strict qui rajoute les « use strict » en début de fichier (ils sont déjà présent dans mes fichiers).

Il nous faut maintenant l’inclure dans nos taches grunt. Je l’inclus dans ma tache serve qui permet d’avoir un serveur web.

  grunt.registerTask('serve', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'bowerInstall',
      'concurrent:server',
      'autoprefixer',
      'copy:dist',
      'babel:dev',
      'connect:livereload',
      'watch'
    ]);
  });

Bien entendu, nous pouvons configurer grunt pour transpiler notre code à la volée et recharger la page via watch et livereload pour un process de développement plus fluide :

 watch: {
  js: {
    files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
    tasks: ['newer:eslint:all', 'newer:babel:dev'],
    options: {
      livereload: true
    }
  },
  //others file types ...
}

Exemple de Code généré

Voici du code ES6 que nous allons transpiler avec babel (let, expression lamda, string templates, classes)

//Exemple avec une expression lambda
var double = x => x * 2;

//string template
var deux = 2;
let quatre = double(deux);
console.log(`${quatre} est le double de 
            ${deux}`);

//Exemple avec une classe
class Person {
  constructor(firstname, lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
  }
  fullname() {
    return this.firstname + ' ' + this.lastname; 
  }
}

let julien = new Person('Julien', 'Roy');
console.log(julien.fullname());

Et voici le code généré par Babel

//Exemple avec une expression lambda
'use strict';

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

var double = function double(x) {
  return x * 2;
};

//string template
var deux = 2;
var quatre = double(deux);
console.log('' + quatre + ' est le double de \n            ' + deux);

//Exemple avec une classe

var Person = (function () {
  function Person(firstname, lastname) {
    _classCallCheck(this, Person);

    this.firstname = firstname;
    this.lastname = lastname;
  }

  _createClass(Person, [{
    key: 'fullname',
    value: function fullname() {
      return this.firstname + ' ' + this.lastname;
    }
  }]);

  return Person;
})();

var julien = new Person('Julien', 'Roy');
console.log(julien.fullname());

Liens

  • REPL: une page qui permet de tester la conversion de votre code

Angular : Retour d’expérience

Comme je l’ai mentionné dans un article précédent, je m’intéresse de près à Angular JS.

J’ai ainsi réécrit mon application de gestion de score de Volley avec ce framework. Le code est disponible sur github et l’application est disponible à cette adresse.

Voici quelques astuces/retours sur des problèmes que j’ai pu rencontrer.

Retour sur l’utilisation de Angular JS

Angular est un framework MVW (Model View Whatever comme aime le décrire Google). Je suis maintenant fan de ce framework. En voici quelques raisons :

  • Projet architecturé : angular définit un certains nombre de concept/objects (modules, controllers, services, directives, …) qui permettent de très bien structurer son code et de le rendre modulaire
  • Binding bi-directionnel : Un des plus de Angular est la gestion du binding bi-directionnel (la vue se met à jour automatiquement suite à une modification de la donnée sur laquelle est est bindée et inversement). Ce mécanisme permet de développer de manière très rapide.
  • POJO : On utilise des objets JavaScript standards et on n’hérite pas d’objets liés à un framework comme ça peut être le cas avec d’autres comme Backbone, Ember, knockout, …)
  • Injection de dépendances : Angular met en place de l’injection de dépendance ce qui va facilité le découplage de vos composants et les tests
  • Directives : Une des forces de Angular est l’utilisation de directives. Elles permettent de créer de nouveaux élément/attribut HTML que vous allez pouvoir utiliser directement dans votre vue. Cela facilite la réutilisation des composants et permet d’avoir une vue en full html. Cela va dans le sens de l’évolution du HTML avec les web components
  • Testabilité : Angular a été développé pour être facilement testable … et il l’est.

Angular est vraiment un super framework qui permet de développer rapidement et très bien pensé. N’hésitez pas, testez le !!! Vous allez rapidement l’adopter. Il y a un fort engouement autour du projet avec une grosse communauté, un écosystème de module et des outils adaptés.

Utilisation du générateur yeoman

J’ai parlé dans un article précédent de yeoman, un outil qui permet d’améliorer le worklow front end. J’avais déjà commencé à travailler sur mon application avec angular avant de décider d’utiliser yeoman. Pour cela, j’ai créé un nouveau projet avec le générateur angular et je l’ai intégré dans mon projet existant. Avec le recul, j’aurai du faire l’inverse 🙂 .

Les commentaires dans le fichier index.html sont très important, ils permettent de gérer l’inclusion aux bons endroits des fichiers js, la concaténation lors du build, …

Si, comme moi, vous changez le dossier par défaut de le bower_components, il ne faut pas le modifier seulement le fichier .bowerrc mais dans tout le projet (.gitignore, Gruntfile.js, …).

L’utilisation de yeoman s’est révélée très  pratique. A la création d’un fichier (controller, directives, services, ….), un fichier de test est automatiquement généré. Grunt et Karma sont déjà configurés afin de vous facilité la vie (live relead, tests, ….).

Publication

Le générateur inclus un tache de build qui va optimiser votre application (concaténation, minification, …) pour le déploiement.

grunt build

Si vous voulez tester votre site optimisé il faut utiliser la commande

grunt serve:dist

Dans le processus d’optimisation, il y a une tache concernant les images qui sont renommés afin d’éviter d’utiliser une ancienne image du cache du navigateur (ex : volley.svg en 56103f21.volley.svg). La tache va ainsi renommer vos fichiers images et les liens vers celles ci dans vos fichiers HTML et CSS. J’ai rencontré un problème avec une image au format SVG. Il est possible d’inclure une image au format SVG de plusieurs manière dans votre code HTML : embed, object, img ou svg (article sur alsacreations). J’utilisais la balise object et le lien vers l’image n’était pas mis à jour dans le code HTML. L’utilisation d’une balise img a résolu le problème.

Controller et ngRoute

Dans mon application, j’utilise le module ngRoute fourni de base par Angular. J’ai une container ngView qui affiche la vue en fonction de la route courante.

Ce qu’il faut savoir, c’est que à chaque changement de vue, un nouveau controller est instancié. Ainsi si on revient sur une page, ce n’est pas le précédent controller qui nous est injecté mais un nouveay. Les données du scope du controller sont donc perdues. Dans mon cas, si on changait de page en cours d’un match, les données du match en cours étaient perdues et il fallait recommencer de zéro.

La solution à ce problème consiste à utiliser un service car celui ci est un singleton. C’est lui qui va garder l’état de vos données. L’utilisation des services est une des bonnes pratiques d’angular. Le controller doit permettre le lien entre vos données et la vue. Ne mettez pas de code métier dans votre controller. Utiliser des services !!

Voici un exemple de code d’un service (sous la forme d’une factory) qui stocke les données

angular.
module('myApp', []).
factory('myData', function() {    
  return {
    shareData : {
      foo : 'foo',
      bar : 'bar'
    }
  }
});

Le code du controller qui expose les données à la vue

angular.
module('myApp', []).
controller('dataCtrl', ['$scope', 'myData', function($scope, myData) {    
   $scope.data = myData.shareData;
  }
]);

Un exemple complet et fonctionnel sur JsFiddle.

Utilisation de module externes

J’ai utilisé des modules externes (angular-charts et angular-bootstrap) dans mon application. Le plus simple est de les ajouter via bower

bower install angular-bootstrap --save

Il faut ensuite ajouter les liens vers les fichiers des modules dans :

  • index.html

    • JavaScript dans la partie <!– build:js scripts/vendor.js –>
    • CSS dans la partie <!– build:css styles/vendor.css –>

  • karma.conf
    • JavaScript dans le tableau files

Edit du 02/06/2014 :
Les scripts sont intégrés automatiquement par grunt dans le fichier index.html via la tache bower-intall configurée par le générateur yeoman (merci Gilles 🙂 )

Si vous n’incluez pas les fichiers dans le karma.conf, vous aurez des erreurs de ce type lors de vos tests :

Error: [$injector:modulerr] Failed to instantiate module volleyApp due to:
Error: [$injector:modulerr] Failed to instantiate module ui.bootstrap due to:
Error: [$injector:nomod] Module 'ui.bootstrap' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.

Directives

Les directives sont vraiment très puissante. J’ai eu, dans le cadre de mon application, à créer mes propres directives. Je me suis heurté à un problème de rafraîchissement de ma vue dans une de mes directives. Cette directive permet de créer un input select avec des options numérique. La directive prend en paramètres la valeur max des options à créer. Il est possible d’associer une fonction à appeler au changement de valeur. La solution de mon problème est l’utilisation de scope.$apply qui permet de forcer un refresh.

Voici le code de la partie liée à l’appel de fonction au changement de valeur :

element.bind('change', function() {
  scope.$apply(function() {
    scope.onChange();
  });
});

Le code de ma directive sous github

Jasmine Matcher

Comme préciser plus haut, j’utilise le générateur yeoman pour angular et celui-ci utilise Jasmine comme framework de test. Jasmine inclut un petit nombre de matcher par défaut. Jasmine matcher permet d’en rajouter pour faciliter l’écriture de vos tests.

L’installation se fait via la commande suivante :

npm install jasmine-expect --save-dev

Il n’existe pas encore de plugins karma pour l’intégrer. Il faut rajouter l’inclusion du fichier js dans le fichier de configuration de karma (karma.conf.js)

files: [
            'node_modules/jasmine-expect/dist/jasmine-matchers.js',
            'src/main/webapp/js/angular/angular.js',
            'src/main/webapp/js/angular/angular-*.js',
            ... etc ...

Batarang

Batarang est une extension Chrome spécifique à Angular. Cela permet de rajouter un onglet dans la  barre d’outils développeur.  Cela permet de voir le scope, l’héritage de scope, consulter les méthodes les plus coûteuses en temps…

Liens

Présentation de Grunt

Le développement nécessite d’avoir de bons outils afin de gagner en productivité. L’automatisation des taches est un bon moyen de gagner du temps.

Grunt est un lanceur de taches. Il est basé sur NodeJS. Grunt permet de facilement automatiser vos taches liés au développement web. L’écosystème autour de nodeJs étant très dynamique, il existe une pléthore de taches grunt existantes

Je n’aborderai pas dans cet article l’installation et la configuration de grunt, vous trouverez dans la partie liens de très bons articles traitant ce sujet.

Voici une liste de quelques plugins utiles :

  • Less : Permet de compiler du des fichier LESS en CSS (il existe bien évidemment des plugins pour les autres préprocesseurs comme SASS ou Stylus
  • Cssmin : Permet de minifier vos CSS
  • Jshint : Permet de valider via JsHint votre code JavaScript
  • Concat : Permet la concaténation de fichier afin d’améliorer les performance de chargement de votre site
  • Uglify : Permet de minifier vos fichier js (permet également la concaténation avec possibilité d’utiliser Source map afin de debugger facilement vos scripts)
  • imageoptim / responsive-images : Permet d’optimiser et de générer des images adaptées à différentes résolution (responsive web design)
  • Watch : Permet de surveiller vos fichier et de lancer d’autres taches à chaque modification
  • HTMLHint : Validation de votre code HTML
  • jsdoc : Permet de générer la documentation de votre code js

Liens

Alternatives

Utilisez-vous Grunt ? Si oui, quelles taches utilisez-vous ?