28 mars 2019 Gaëtan Rouziès

Arrêtez d’utiliser Lodash/Underscore, ES6 est là

Underscore et Lodash, deux classiques

Dans le développement Javascript il y a deux librairies qui ont facilité la vie d’énormément de développeurs. Ces librairies sont Underscore et Lodash, Lodash étant un sur-ensemble d’Underscore apportant encore plus de fonctionnalités. 

Actuellement, Lodash c’est :

  • + de 38 000 stars sur le projet Github
  • + de 22 millions de téléchargement du module npm par semaine

Sorties respectivement en 2009 pour Underscore et 2012 pour Lodash, elles font partie des librairies les plus populaires que tout développeur Javascript a utilisé au moins une fois dans sa vie.

Elles sont encore énormément utilisées et suivies que soit directement par les développeurs ou par les autres librairies qui dépendent d’elles.

 

L’arrivée de ES2015 / ES6

ECMAScript est un ensemble de normes qui définissent les fonctionnalités des langages de scripting, dont le langage qui nous intéresse : Javascript.

En 2015, une nouvelle version de ECMAScript est publiée nommée ES2015 et plus souvent appelée ES6, apportant énormément de nouvelles fonctionnalités pour les développeurs.

Qui dit nouvelles fonctionnalités, dit problème de compatibilité avec les navigateurs ?

Effectivement, mais aujourd’hui ce n’est plus un soucis:

  • La plupart des navigateurs récents n’ont pas de problèmes de compatibilité.
  • Et pour les navigateurs anciens, nous utilisons des Transpilers comme Babel ou Webpack pour rendre notre code ES6 compatible.

Parmi les nouvelles fonctionnalités d’ES6 nous retrouvons des fonctions qui concurrencent directement Underscore et Lodash.

 

Comparaison ES6 et Lodash/Underscore

Regardons ensemble les fonctions les plus utilisées écrites d’un côté avec ES6 et de l’autre avec Lodash/Underscore.

Parcourir un tableau avec ForEach

var array = ['Orange', 'Citron', 'Pomme'];

// ES6
array.forEach(fruit => console.log(fruit));

// Lodash/Underscore
_.forEach(array, fruit => console.log(fruit));

Créer un nouveau tableau avec Map

var array = [
 {id: 1, firstName: 'David'},
 {id: 2, firstName: 'Gaëtan'},
 {id: 3, firstName: 'Thomas'},
 {id: 4, firstName: 'Jonathan'}
];

// ES6
var result = array.map(elem => elem.firstName);
// --> ['David', 'Gaetan', 'Thomas', 'Jonathan'];

// Lodash/Underscore
var result = _.map(array, elem => elem.firstName);
// --> ['David', 'Gaetan', 'Thomas', 'Jonathan'];

Trouver le premier élément correspondant avec Find

var array = [
 {id: 1, firstName: 'David'},
 {id: 2, firstName: 'Gaëtan'},
 {id: 3, firstName: 'Thomas'},
 {id: 4, firstName: 'Jonathan'}
];

// ES6
var result = array.find(elem => elem.id === 3);
// -> { id: 3, firstName: 'Thomas' }

// Lodash/Underscore
var result = _.find(array, elem => elem.id === 3);
// -> { id: 3, firstName: 'Thomas' }

Filtrer un tableau avec Filter

var array = [
 {pays: 'France', marque: 'Peugeot'},
 {pays: 'Allemagne', marque: 'Mercedes'},
 {pays: 'Allemagne', marque: 'Audi'},
 {pays: 'France', marque: 'Renault'}
];

// ES6
var result = array.filter(elem => elem.pays === 'France');
// -> [{pays: 'France', marque: 'Peugeot'}, {pays: 'France', marque: 'Renault'}]

// Lodash/Underscore
var result = _.filter(array, elem => elem.pays === 'France');
// -> [{pays: 'France', marque: 'Peugeot'}, {pays: 'France', marque: 'Renault'}]

Entre les librairies et ES6 nous remarquons que la syntaxe et les fonctions sont très similaires et qu’il est facile de prendre en main les fonctions ES6 si vous connaissez déjà Lodash/Underscore.

La seule différence est qu’en ES6, la fonction est appelée directement depuis la variable.

  • maVariable.forEach(function)

Alors que pour Lodash/Underscore la variable est mise en premier paramètre de la fonction.

  • _.forEach(maVariable, function)

Nous allons voir que cette petite différence aura un gros impact sur l’écriture et la lisibilité du code.

 

Pour faire un article court je vous ai présenté uniquement les fonctions ForEach, Map, Filter. Si la liste complète vous intéresse, voici une ressource très complète sur le sujet: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore

Vous verrez que dans certains cas, vous pourriez avoir besoin de plusieurs fonctions ES6 afin d’avoir l’équivalent d’une fonction Lodash.

Pourquoi vous devez arrêter d’utiliser Lodash/Underscore ?

Raison 1: Les dépendances

Ajouter une dépendance à votre projet a toujours un coût. 

  • Lors du développement le téléchargement de la librairie et son installation prennent du temps. 
  • Pour les clients, la librairie a un impact sur la taille du site web et celui-ci mettra plus de temps à charger.

Bundle Phobia est un site qui est très utile pour juger de l’impact d’une librairie sur votre projet. Voici ce que nous indique Bundle Phobia pour Lodash.

bundle phobia lodash size

Source: https://bundlephobia.com/[email protected]

Pour Lodash le résultat nous indique que son utilisation peut augmenter le temps de téléchargement de notre site d’environ 0.5sec suivant la connexion du client. Bien que léger, l’impact est réel, surtout pour une librairie qui est devenu facultative depuis ES6.

 

Raison 2: Des performances supérieures

Lorsque nous comparons les performances entre ES6 et Lodash/Underscore, nous remarquons une légère différence en la faveur des fonctions ES6.

Vous pouvez retrouvez le code présenté ci-dessous ici: https://codepen.io/gaetanrouzies/pen/MRWjMQ

Code testé

Voici les instructions testées sur un tableau de Integer d’une longueur de 300 000:

// Find
_.find(array, e => e === 200000);
array.find(e => e === 200000);
// Map
_.map(array, e => e + 5);
array.map(e => e + 5);
// Reduce
_.reduce(array, (sum, n) => sum + n, 0);
array.reduce((sum, n) => sum + n, 0);

Méthodologie utilisée

const totalLoop = 100;
let averageDuration = 0;
let timer = undefined;
let result = undefined;

for(let i = 0; i < totalLoop; i++) {
 timer = new Date();
 // <---- Instruction
 averageDuration += (new Date() - timer);
}

averageDuration = averageDuration / totalLoop;
console.log(`Lodash/ES6 : ${averageDuration} ms`);

Résultat

Voici les résultats obtenus en mesurant la durée d’exécution de chaque instruction.

"------ FIND"
"Lodash : 3.92 ms"
"ES6 : 3.26 ms"
"------ MAP"
"Lodash : 7.94 ms"
"ES6 : 6.28 ms"
"------ REDUCE"
"Lodash : 6.92 ms"
"ES6 : 5.37 ms"

C’est une raison supplémentaire de choisir ES6, mais vous allez être d’accord avec moi si je vous dit que la performance ne fait pas tout. Rien ne sert de gratter quelques millisecondes d’exécution si notre code devient illisible ou impossible à maintenir et vous allez voir que justement ES6 va nous aider à augmenter la qualité de notre code.

 

Raison 3: Uniformisation du code

Dans tout projet informatique il est important de définir des règles de codage (Convention de nommage, architecture, etc…). Le but étant d’avoir une même logique de développement au sein d’un même projet.

Avoir un code uniformisé permet à tous les développeurs de prendre en main facilement les développements des autres sans être perdu par le caprice d’un autre.

J’avais rejoint il y’a quelques temps un projet AngularJS avec du Typescript (Donc avec toutes les fonctionnalités ES6), cela m’a surpris quand j’ai vu dans la même équipe:

  • Des développeurs utiliser uniquement Lodash
  • Des développeurs utiliser uniquement les fonctions d’ES6
  • Des développeurs utiliser un mélange des deux selon leur bon vouloir

Cela donne un code non uniformisé qui va faire perdre du temps et de l’énergie aux développeurs du projet.

En effet, un habitué d’ES6 pourra avoir du mal à prendre en main rapidement du code Lodash et inversement.

Pour s’adapter le développeur va devoir apprendre les deux syntaxes et switcher entre les deux, ce qui ajoute une gymnastique mentale qui va naturellement réduire la qualité du code.

 

Raison 4: Écriture et Lisibilité du code

Les fonctions ES6 s’appelant directement depuis les variables, la syntaxe est plus claire et aura un impact sur le développement et la lisibilité du code. Même si nous allons voir que Lodash ne s’en sort pas si mal grâce à un wrapper qui nous permet d’enchaîner les fonctions de manière naturelle comme en ES6.

var array = [
 { team: 'A', playerName: 'Alice Neptune', point: 24 },
 { team: 'A', playerName: 'Jonathan Neptune', point: 12.5 },
 { team: 'B', playerName: 'Camille Neptune', point: 16.2 },
 { team: 'B', playerName: 'Nicolas Neptune', point: 22.9 },
];

Sur ce tableau de joueur je veux calculer le total des points des joueurs de l’équipe A. Pour cela je vais utiliser les 3 fonctions: filter, map et reduce.

A savoir qu’avec Lodash vous avez la possibilité d’utiliser un wrapper vous permettant d’enchaîner les fonctions.

// Lodash/Underscore (sans wrapper)
var result = _.reduce(_.map(_.filter(array, player => player.team === 'A'), player => Math.round(player.point)), (total, point) => total += point);
// Lodash/Underscore (avec wrapper)
var result = _(array)
                    .filter(player => player.team === 'A')
                    .map(player => Math.round(player.point))
                    .reduce((total, point) => total += point);

Sans le wrapper l’enchaînement de fonctions est vraiment illisible et très dur à coder pour le développeur.
Néanmoins avec le wrapper l’enchaînement se fait de manière naturelle et rend un code beaucoup plus lisible.

// ES6
var result = array
                .filter(player => player.team === 'A')
                .map(player => Math.round(player.point))
                .reduce((total, point) => total += point);

Avec ES6 on se retrouve avec un équivalent par rapport à Lodash avec le wrapper. Avec l’avantage de pouvoir appeler les fonctions directement depuis la variable.

Conclusion

Pertinentes pendant longtemps, les librairies Lodash/Underscore doivent maintenant tirer leur révérence pour les futurs projets Javascript à venir.

Si vous vous apprêtez à démarrer un nouveau projet, ou si vous avez la possibilité de ne plus utiliser Lodash/Underscore à faible coût dans un projet existant, alors c’est le moment de passer à du 100% Javascript Natif avec ES6 😉.

Tagged:
  • Frédéric Camblor

    La raison 4 est un peu tirée par les cheveux quand on connaît les fonctions de chaining de lodash/underscore

    Typiquement :

    var result = _(array).filter(player => player.team === 'A')
    .map(player => Math.round(player.point)
    .reduce((total, point) => total += point)
    .value();

    … qui reste très similaire à la solution native

    Mon principal frein à dropper lodash sur mes projets, personnellement, c’est plutôt :
    – Que certaines fonctionnalités de lodash n’existent aujourd’hui pas en natif, particulièrement les fonction qui permettent de « muter » le tableau d’origine
    – Que je n’ai pas trop confiance (sûrement à tort) des polyfills fournis par les langages qui transpilent en ES5

    • Gaëtan Rouziès

      Salut Frédéric, merci pour ton commentaire.

      1. Je pensais que les fonctions .map/.reduce de ton exemple étaient des fonctions ES6. 🤔
      2. Il est vrai que certaines fonctions n’existent pas en natif ce serait intéressant de voir lesquelles tu utilises. Je viens de mettre une ressource que j’ai trouvé très intéressante sur Github.

      A bientôt 😉

      Gaëtan

      • Frédéric Camblor

        Euh .. non, mon code fonctionne sous IE 7 sans problèmes (et sans utiliser de polyfill)

        Le point important c’est le _(array) qui transforme l’objet array en un « objet fluent » (chain) sur lequel tu peux chainer des méthodes de l’API lodash.
        Typiquement, la fonction terminal value() n’existe pas en ES6 et c’est une méthode de lodash permettant « d’unwrapper » la structure sur laquelle tu travaille pour en obtenir le résultat final.

        • Gaëtan Rouziès

          Effectivement je viens d’éditer un peu mon message pour ne pas induire en erreur les autres lecteurs. 😁
          Merci pour les explications j’ai appris des trucs. Je vais analyser ça à tête reposée 👍

          Je vais surement mettre à jour l’article avec ta solution du chaining que je ne connaissais pas avec Lodash.

        • Gaëtan Rouziès

          Bonjour Frédéric, je viens de mettre à jour la Raison 4 avec tes retours !
          J’ai aussi vu qu’avec la version de Lodash 4.0.0 (https://github.com/lodash/lodash/wiki/Changelog#v400), plus besoin de mettre .value(). Donc encore plus lisible 👍

          A bientôt 😉

  • Mister Z

    On peut aussi utiliser lodash-es, qui permet grâce à une optimisation des imports de ne pas importer toute la librairie mais d’autres helpers que es6 n’a pas du tout comme par exemple get, set, pluck etc …, ce qui permet au moment du treeshaking suite à la modularisation de considérablement réduire le poids des dépendances

    https://www.npmjs.com/package/lodash-es

    • Rwan Plmn

      Comment tu fais pour les tests du coups ?

  • Dylan Boré

    Il y a quand même quelques exceptions où Lodash permet de gérer des situations plus facilement. Je pense par exemple à la méthode Object.assign native ES qui ne gère pas la récursivité d’un objet, alors que Lodash le fait très bien avec la méthode _.merge :)