Notes sur l'écosystème front-end avec Webpack en 2016

Ce billet date de plusieurs années, ses informations peuvent être devenues obsolètes.

J'ai récemment travaillé sur une application web monopage développée avec Vue.js. J'ai choisi ce framework parce qu'il est facile à comprendre, non opiniâtre et qu'il dispose d'une bonne documentation. Ce dernier point fut décisif dans mon choix car je trouve que c'est encore un gros point faible dans la communauté JavaScript, par comparaison avec la culture de la documentation du monde Python.

J'ai aussi aimé sa façon d'implémenter le two-way binding par le truchement des descripteurs de propriété disponibles depuis ECMAScript 5, qui permettent de faire des trucs vraiment cool comme les computed properties.

Je pense que John Resig ne s'y était pas trompé :

Property descriptors (and their associated methods) is probably the most important new feature of ECMAScript 5. It gives developers the ability to have fine-grained control of their objects, prevent undesired tinkering, and maintaining a unified web-compatible API.

Pour travailler sur cette application, j'ai utilisé une chaîne de compilation basée sur Webpack qui a le vent en poupe afin d'automatiser la création d'un environnement de travail. Et dire que l'année dernière j'écrivais sur Browserify ! Je ne vais pas écrire une satire sur l'écosystème JavaScript mais il y aurait de quoi :)

Afin de mieux comprendre comment tout cela fonctionne, je me suis inspiré de vue-cli et de son Webpack template pour configurer Webpack de zéro dans le cadre du développement d'un plugin. Le choix de mon outillage est donc largement inspiré de ce boilerplate. Mais vous pouvez consulter The State of Front-End Tooling 2016 pour mesurer la diversité des outils disponibles.

Au final il m'a fallu beaucoup plus de temps pour comprendre et mettre en place un environnement avec Webpack que pour comprendre Vue.js. Il n'est pas étonnant de voir qu'il existe un si grand nombre de chaînes de compilation et de boilerplates pour automatiser le processus : on a pas envie de tout refaire à chaque fois.

Voici donc mes notes sur Webpack 1 et l'écosystème front-end alors même que Webpack 2 est sur le point d'arriver… Je sens venir la rechute de JS fatigue :D

Webpack et le Hot Module Replacement (HMR)

Webpack est un assembleur de modules. Il prend divers fichiers en entrée (JS, CSS, Images, HTML etc.) par l'entremise de loaders, traite chacun d'entre eux comme un module, réussi à déterminer les dépendances entre eux, et les assemble en ressources statiques prêtes pour le déploiement.

Le "Hot Module Replacement" (HMR) est une fonctionnalité de Webpack (encore qualifiée d'experimental feature dans la documentation de Webpack 1) utilisée pendant la phase de développement et qui permet d'injecter les modules mis à jour dans l'environnement d'exécution actif. En moins pompeux, le HMR remplace la partie de votre code modifiée et met à jour le navigateur sans rafraîchissement, c'est à dire que l'état est conservé (dans la mesure du possible) : effet whouhaou garanti.

Il y a 3 manières de mettre en place le HMR.

J'ai choisi d'utiliser webpack-hot-middleware qui permet d'utiliser le HMR en se reposant uniquement sur webpack-dev-middleware branché sur un serveur existant, en l'occurrence express.

Le principe des middlewares d'Express est un peu le même qu'avec Django. Une fonction reçoit les objets request et response d'une requête HTTP et peut les modifier avant de les passer au middleware suivant dans la chaîne. Elle peut aussi écrire dans la response ou la terminer sans poursuivre la chaîne.

Webpack Hot Middleware s'installe comme un plug-in de Webpack, puis écoute les événements du compilateur. Chaque client connecté obtient une connexion Server-Sent Events : à chaque événement du compilateur le serveur publie des notifications aux clients connectés. Quand un client reçoit une notification, il vérifie que le code local est à jour. S'il ne l'est pas, il déclenchera le HMR.

Une fois la configuration du HMR terminée, il faut aussi savoir que ce dernier est opt-in. Cela signifie qu'il faut ajouter du code à certains endroits de notre application en utilisant l'API du HMR pour conserver l'état. Bref, ça n'est pas magique ni gratuit et c'est ce que permettent par exemple react-hot-loader ou vue-hot-reload-api.

Découper la configuration de Webpack pour de multiples environnements

Il existe plusieurs façons de faire :

  • conserver un fichier unique et effectuer des branchements conditionnels en fonction de l'environnement courant
  • créer un fichier de configuration par environnement (ce qui entraîne de la duplication de code)
  • créer des configurations partielles qui peuvent être fusionnées entre elles à l'aide d'outils spécialisés tels que webpack-merge afin de factoriser les parties communes

La pratique la plus courante pour identifier l'environnement courant est d'utiliser une variable d'environnement. Pour cela Node.js fournit la propriété process.env à laquelle on ajoute, par convention dans le monde Node.js, une variable NODE_ENV (rendue populaire par le framework Express).

Cela veut dire que si NODE_ENV n'est pas définie, sa valeur sera undefined.

Parfois on a aussi besoin d'utiliser NODE_ENV (ou d'autres constantes) dans le code client. Pour cela webpack.DefinePlugin permet d'exposer des constantes globales dans le bundle Webpack au prix d'une syntaxe quelque peu déroutante

Analyse de code source (linting)

Les outils d'analyse de code sont désormais quasiment inclus de facto dans le flux de travail JavaScript.

J'utilise actuellement ESLint en duo avec la convention préétablie Standard. Et pourtant j'ai longtemps était partisan de l'insertion explicite de points-virgules. Mais la convention Standard donne un petit goût agréable de Python au JavaScript :D

J'utilise aussi un bundle spécial pour TextMate : JavaScript ESLint TextMate Bundle.

Babel

Je n'ai plus grand chose à dire sur Babel qui n'ait pas déjà été dit ailleurs. Ce compilateur source à source a été massivement adopté par la communauté JavaScript, comme le souligne l'ami @revolunet.

Du coup je vous propose seulement un florilège de liens :

Tests unitaires

Si Jasmine a eu son heure de gloire, il semble que le battage médiatique jette désormais son dévolu sur Mocha. Moi je m'en fous j'aime les deux ;)

Mocha est un framework permettant d'écrire des tests JavaScript. À la différence de Jasmine il n'embarque pas d'outils d'assertion ni d'outils pour créer des spies, des stubs ou des mocks. C'est pourquoi on utilise Chai et Sinon. Et si on aime la syntaxe de Chai, il existe un plugin Sinon-Chai permettant d'utiliser les assertions de Sinon via les interfaces expect ou should de Chai.

Karma est un outil JavaScript en ligne de commande pouvant être utilisé pour démarrer un serveur web qui va charger le code source de notre application, exécuter les tests et afficher les résultats. Karma tourne sur Node.js.

Karma a besoin de :

  • karma-mocha pour utiliser Mocha
  • de karma-sinon-chai pour rendre les interfaces de Chai (should, expect, assert) et de Sinon globalement disponible dans les fichiers de tests (il faut aussi l'indiquer dans .eslintrc.js)
  • de karma-phantomjs-launcher pour utiliser PhantomJS et faire tourner les tests dans un navigateur web sans interface graphique
  • de karma-webpack afin d'utiliser Webpack avec une configuration spécifique pour pré-traiter les fichiers de tests

Et un petit best of des antisèches pour finir :

HTML Webpack Plugin

HTML Webpack Plugin génère un point d'entrée index.html pour notre application, et y injecte automatiquement les bundles Webpack.

C'est particulièrement utile avec des environnements de compilation multiples afin d'empêcher le HTML d'être désynchronisé d'un environnement à l'autre.

Utile également pour éviter les chemins vers les bundles écrits en dur et pour simplifier la mise en place du cache busting.

Techniquement le point d'entrée est automatiquement ajouté par l'intermédiaire d'un hook make, voir ça, ça et ça.

Utilisation de rollup pour créer un paquetage

Pourquoi ? Parce que Rollup tabasse ! Hype Driven Development FTW ;)

CSS

Depuis quelques années maintenant, CSS est vivement critiqué par une partie de la communauté. Remarquez, ça a toujours été le cas ;)

Le vent de fronde vient notamment de la communauté React. Une présentation d'un employé de Facebook identifiant les 7 deadly sins of CSS a conduit le monde React a écrire le code CSS directement en JavaScript, et le culte du cargo a emboîté le pas jusque dans la communauté française.

Pourtant quelques indices dans cette présentation auraient pu vous mettre la puce à l'oreille, l'emphase est de moi :

When I’m saying at scale, it means in a codebase with hundreds of developers that are committing code everyday and where most of them are not front-end developers

Ah ouais ? Et si je demandais à mon dentiste de venir poser le carrelage, ça devrait être putain de bien fait ;)

If you look at w3schools, my favorite website to learn JS, the first point of the best practice guide clearly says “Avoid Global Variables”.

Ah bin si c'est marqué sur W3Schools, ça doit être putain de vrai tout le temps ;)

Bref, arrêtons de considérer la portée globale (a.k.a. la cascade et l'héritage CSS) comme satanique. Le dogmatisme n'est jamais une solution.

Le problème est ailleurs et il est le même depuis le début : la majorité des développeurs ne fait pas l'effort d'apprendre CSS correctement.

Vous ne pouvez pas vous passer d'un scope global en CSS. Vous avez besoin qu'une partie de vos styles au moins soient propagés dans tous vos composants pour obtenir un design cohérent avec un minimum d'efforts.

Attention, ça ne veut pas dire que la portée globale ne pose pas de problèmes. Et c'est là qu'entrent en jeu des méthodes à la mode telles que BEM ou les CSS modules.

À titre personnel, j'ai choisi de faire un mix entre la portée globale et des outils permettant d'en limiter les inconvénients. Pour cela j'ai choisi de contraindre la portée localement grâce au local scope de css-loader dont le résultat pourrait être comparé à du BEM automatisé.

Notons pour finir qu'il existe aujourd'hui et qu'il existera demain des techniques natives en CSS pour modifier la portée !

Conclusion

Je n'ai pas abordé la configuration de la couverture de code, ni les tests End-to-End, ni les source maps, ni Flow, ni Vuex etc.

Pfff, l'écosystème front-end est devenu vraiment balaize. J'espère que tout cela va maintenant un peu se stabiliser et qu'il ne faudra pas tout modifier dans 6 mois ;)

#1 lionelb

07/12/2016 09:17

Hello,

Je suis curieux de l'intégration de rollup, dans ta toolchain.
Comment tu l'inseres avec webpack... Si tu pouvait détailler un peu
Merci pour ton article

#2 Kemar

07/12/2016 09:56

@lionelb Rollup est complètement séparé de Webpack en fait, c'est un peu une alternative.

Tu peux voir un exemple de script qui déclenche un build avec Rollup ici et sa configuration par là.

#3 lionelb

07/12/2016 22:00

@Kemar en lisant ton post, on a l'impression que tu utilises webpack et rollup
dans le même projet, de ce que je comprend tu utilises webpack / babel pour la partie dev et rollup pour builder le dist.

#4 Kemar

08/12/2016 09:34

@lionelb oui c'est exactement ça, l'idée était surtout d'évaluer Rollup :)

Avant Notes sur les types et la grammaire de JavaScript Après Gestion de la couleur

Tag Kemar Joint