Buildez vos applications avec WebPack

Aujourd'hui, les sites web évoluent de plus en plus vers des applications web. Cela se traduit par une utilisation de plus en plus intensive de JavaScript et souvent l'utilisation d'une ou plusieurs librairies. Bien souvent, le code source des applications ne suffit pas pour pouvoir être exécuté. Il est nécessaire de passer par une étape de construction, "build", pour assembler les librairies et le code source en un seul bundle.

Il existe de nombreux "builder" pour accomplir cette tâche : Maven, Gradle, Grunt, ...

Aujourd'hui nous parlerons de : WebPack.

Imports et dépendances

Avant de parler de WebPack, il est important de rappeler les manières d'importer le code source et les librairies au sein d'une application web.

La balise  <script>

C'est la première manière d'importer du code JavaScript. Interprété nativement par les navigateurs:

<script src="http://www.lycos.fr/va-chercher.js" />
<script src="http://www.lycos.fr/cherche-encore.js" />

Ce tag rencontre toutefois de nombreux problèmes, comme les conflits des objets importés dans l'objet global, l'importance de l'ordre de chargement des librairies et la résolution des dépendances entre les librairies.

 

CommonJS (require)

La méthode require de CommonJS permet de charger des dépendances de manière synchrone. Un module peut également spécifier ses exportations en ajoutant des propriétés aux exports.

require("module");
require("../file.js");
...module.exports = something;

Ce mode d'importation simple permet d'utiliser et de réutiliser de nombreux modules, même ceux de la communauté (node.js & npm). Par contre son côté synchrone est bloquant, il ne permet pas de charger plusieurs modules en parallèle. De plus, il ne permet pas d'importer des librairies sur des réseaux distants. Les modules doivent être accessibles en local.

 

Asynchronous Module Definition (AMD)

Il existe d'autres systèmes de gestion de modules qui ont éprouvé l'import synchrone de CommonJS et ont implémenté une version asynchrone, comme AMD :

require(["module", "../file.js", function(module, file) { /* ... */ }):
...define("monModule", ["dep1", "dep2"], function(d1, d2) {
return someExportValue;
}); 

Cette implémentation convient parfaitement pour le chargement de plusieurs modules en parallèle et le chargement de modules sur un réseau. Les inconvénients sont : la lecture et l'écriture du code qui est plus compliquée et n'est utilisable que côté client.

 

Modules ES6

EcmaScript 6 (ES6) ajoute quelques fonctionnalités à JavaScript, notamment sur la partie gestion de modules. Il est possible avec ES6 d'importer des librairies et d'exporter son code sous la forme d'une librairie :

import "module";
import { tmp } from "../monModule.js";...
export encoreUneNouvelleFacon = () => { /* ... */ };
export default monModule;

L'avantage d'ES6, c'est la facilité d'implémenter ce genre de module et de les utiliser. L'analyse de code est facile ainsi que le suivi des dépendances. La prise en compte par les navigateurs n'est pas encore assurée avant un certain temps. Il est donc nécessaire de traduire le code, "transpiler", vers ES5 pour qu'il puisse être interprété par les navigateurs.

Il existe de nombreuses manières d'importer les librairies JS et modules. Toutes ont leurs avantages et inconvénients. Pour pallier ce problème de multi systèmes d'importation, il existe une solution: WebPack.

 

WebPack comme module de gestion de dépendances

WebPack s'intègre comme un gestionnaire de modules permettant de les importer indépendamment du mode d'importation choisi lors de la réalisation.

WebPack se base sur les modules et les dépendances pour générer des actifs statiques. Toutes les dépendances peuvent ainsi devenir des modules statiques : comme des fichiers CSS, LESS, des images, des librairies JS et Node.js. Les dépendances sont organisées à l'intérieur d'un arbre et séparées en portion (chunks), pour être chargées à la demande. L’avantage de la décomposition en chunks est palpable lors du lancement de l’application. Seuls les éléments nécessaires seront chargés, les autres le seront lorsqu’il y en aura besoin. Cela permet à WebPack d'intégrer dans ses dépendances des librairies au chargement asynchrone.

WebPack a été conçu initialement pour ne travailler qu'avec du JavaScript. Pour utiliser d'autres types de ressources, comme les images ou des templates Jade par exemple. Il peut s'appuyer sur des loaders afin de les convertir en JavaScript ou en JSON.

Il ne s'arrête pas là, son système de gestion de modules, lui permet même de gérer les dépendances importées via des expressions calculées, de la forme :

require("../templates/" + name + ".jade");

Pour arriver à réaliser cet exploit, il s'appuie sur la gestion des modules mise en place dans CommonJS et AMD.

 

WebPack dans une application JavaScript

Vous l'avez deviné, WebPack est prévu pour être utilisé avec un langage précis : JavaScript. La contrainte avec JavaScript va encore plus loin, puisque WebPack est à utiliser côté serveur et nécessite donc Node.js. Hormis cette contrainte, il peut être utilisé à l'intérieur de n'importe quel écosystème même si vous utilisez Grunt ou Gulp pour la phase de "build".

Si nous prenons l'exemple d'une application finale développée en ES6, utilisant des librairies comme React.js et autres joyeusetés, il est nécessaire de passer par une étape de "build" pour obtenir un code compréhensible pour le navigateur. A cause de l’ES6, nous sommes obligés d’avoir une phase de transpilation (avec Babel par exemple) pour le traduire en ES5. Il est possible d'intégrer WebPack dans la phase de "build" pour réunir toutes les dépendances et produire un unique bundle.

La configuration de WebPack sera des plus facile pour les développeurs de projets JavaScript. La raison est simple : toute la configuration de WebPack est à faire en JavaScript également.

Exemple d'utilisation de WebPack

L'utilisation de WebPack passe par 3 étapes :

Installation du package Node module

npm install -g webpack

L'installation en global (--g) de WebPack est intéressante, puisqu'elle permet d'avoir accès à la commande webpack dans le prompt. Vous le constaterez par vous-même :  une fois qu’on a mis le pied dedans, on ne peut s’empêcher de l’utiliser sur tous les projets possibles.

Configuration de la compilation 

Pour la configuration de WebPack et les besoins associés, la création d'un fichier de configuration est nécessaire. Si vous nommez votre fichier webpack.config.js il sera automatiquement pris en compte par WebPack. Donc pour le nom du fichier, je vous laisse choisir, mais pour moi c'est tout vu. 

Dans l'exemple qui suit, je vais utiliser le module string-replace-webpack-plugin pour modifier des valeurs et chaînes de caractères dans le code en fonction de la configuration voulue (DEV, PROD, PRE PROD, …).

Passons au cœur du sujet, voilà un exemple de fichier de configuration pour WebPack :

Lancement de la compilation

Après avoir configuré WebPack, il ne reste plus qu'à procéder à la compilation du code. Pour cela, pas de miracle, mais un peu de magie, une simple commande suffit :

webpack –optimize-occurrence-order –progress --colors

Et voilà le tour est joué. A la fin de l'exécution, le fichier "buildé" sera disponible dans le répertoire défini dans output.path.

Afin d'optimiser la construction des chuncks dans le fichier "buildé", il est possible de demander à WebPack d'optimiser l'ordre d'import des chuncks. Pour cela, il faut ajouter le paramètre --optimize-occurrence-order.

Le processus de "build" peut s'avérer plus ou moins long en fonction de l'arborescence et la volumétrie du code source. Pour avoir des informations sur l'avancée du "build", cela se fera avec le paramètre --process.

Pour avoir une sortie dans le prompt un peu plus glamour, rien de plus simple, le paramètre  --color sera votre meilleur ami.

 

Aller encore plus loin avec WebPack

Build différentiel

Il est possible de pousser le vice et de rendre WebPack encore plus utile. Il est possible de demander à WebPack d'écouter les modifications faites sur des fichiers et de builder uniquement la différence. Il conservera ainsi les informations acquises durant le premier build.

Pour cela il faut utiliser l'option watch :

webpack --watch --optimize-occurence-order

Sources map

Par défaut, WebPack ne fournira que le résultat final du build. Il est compliqué de débugger du code dans le bundle. D'autant plus si le code est structuré selon une certaine logique. Pour pallier ce problème, l'option devtool permet de préciser des options de build comme le source map. Cette option permet de produire un bundle mais également de conserver l'arborescence du code source. Ce qui facilitera grandement la phase d'investigation et l'utilisation des points d'arrêt dans les inspecteurs web.

Exemple de commande utilisant le source map :

webpack --optimize-occurence-order --devtool source-map

Live reload plugin

Le plugin de live reload à utiliser dans le fichier de configuration de WebPack, permet de recharger l'application en cours d'exécution et de redéployer les codes source après chaque modification. Très pratique lorsque l'on débugge du code source. Pour l'utiliser il faut importer le plugin et l'ajouter dans le champ plugins comme ceci :

const LiveReloadPlugin = require("webpack-livereload-plugin");
module.exports = {

plugins: [
new LiveReloadPlugin({ hostname: "localhost:3000", appendScriptTag: true }),
 …
    ],
    };…

Conclusion

Pourquoi choisir WebPack ? Il est tout à fait possible d'utiliser Grunt, Gulp ou même Browserify, mais WebPack a un plus. Si je le conseille pour les projets à base de Javascript c'est avant tout pour sa configuration : simple et rapide. Mais également pour ses évolutions avec les loaders et les plugins qui lui permettent de répondre à presque tous les besoins. Alors pour ceux qui hésitent encore sur le moteur de build à utiliser, je n'ai qu'un mot à dire : WebPack

Mickaël D

Commentaires

Patrice
Merci pour cet article très clair !
Morguy
Super !

Ajouter un commentaire