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
.
- Webpack concepts and configuration (documentation de Webpack 2)
- Webpack: The Missing Tutorial ™
- Webpack — The Confusing Parts
- The simplest Webpack and Express setup
- Understanding Webpack HMR
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…
- NodeJs Best Practices: Environment-Specific Configuration
- Simple production environment with Webpack and Express
- How to load different .env.json files into the app depending on environment
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 :
- The State of Babel
- Setting up ES6
- Clearing up the Babel 6 Ecosystem
- Using ES6 and ES7 in the Browser, with Babel 6 and Webpack
- The Six Things You Need To Know About Babel 6
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 :
- Mocha.js cheatsheet
- Chai.js cheatsheet
- Sinon cheatsheet
- Sinon-chai cheatsheet
- The Ultimate Unit Testing Cheat-sheet
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 ;)
- rollup.js The next-generation JavaScript module bundler
- Rollup plugins change the behaviour of Rollup at key points in the bundling process.
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 !
- Local Scope in CSS
- CSS Inheritance, The Cascade And Global Scope: Your New Old Worst Best Friends
- The End of Global CSS
- When to use which CSS methodology
- Back to the :roots
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 ;)
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