Glossaire
Modules Bundler

Dans le cadre de l'écosystème JavaScript un empaqueteur de code ou de module est un programme/outil qui prend des morceaux de JavaScript et leurs dépendances et les regroupent en un seul fichier exploitable, généralement pour une utilisation dans le navigateur. Vous avez peut-être utilisé des outils tels que Webpack, Rollup ou l'un des nombreux autres.

Il commence généralement par un fichier d'entrée et, à partir de là, il regroupe tout le code nécessaire à ce fichier d'entrée.

modules resolution

Pour cela il y a deux étapes :

  1. La résolution des dépendances
  2. L'empâquetage (packing en anglais)

À partir d'un point d'entrée (tel que app.js ci-dessus), l'objectif de la résolution des dépendances est de rechercher toutes les dépendances de votre code (autres éléments de code dont il a besoin pour fonctionner) et de construire un graphique (appelé graphique de dépendances).

Une fois cette étape franchie, on peut alors empaqueter ou convertir le graphe de dépendances en un seul fichier que l'application peut utiliser.

Résolution des dépendances

La première chose à faire est de réfléchir à la manière dont on veut représenter un module pendant la phase de résolution des dépendances.

Représenter un module

Pour cela nous allons avoir besoin de quatre éléments :

  1. Le nom et un identifiant du fichier.
  2. D'où vient le fichier (dans le système de fichiers)
  3. Le code du fichier
  4. Les dépendances dont ce fichier a besoin

La structure du graphe se construit en vérifiant récursivement les dépendances de chaque fichier. On obtient alors quelque chose de la sorte :

module representation

Résoudre les dépendances

Qu'est-ce qu'on entends par « résoudre les dépendances » ? Dans NodeJS, il existe une fonction require.resolve, qui lui permet de cette façon de déterminer où se trouve le fichier dont on a besoin. C'est parce que on peut faire une importation relative ou à partir du dossier node_modules que cette fonction est nécessaire.

En effet lorsque l'on importe des modules dans NodeJS, on peut faire des importations relatives, comme require('./utils'). Mais alors lorsque le code appelle ceci, comment le bundler sait-il quel est le bon fichier ./utils lorsque tout est empaqueté ?

C'est le problème que le mapping de module résout.

L'objet module possède une clé d'identification unique qui sera la "source de vérité". Ainsi, lorsque l'on effectue la résolution des dépendances, on conserve, pour chaque module, une liste des noms des éléments requis ainsi que leur identifiant. De cette façon, on peut obtenir le module correct au moment de l'exécution.

Cela signifie également que l'on peut stocker tous les modules dans un objet non imbriqué, en utilisant l'identifiant comme clé. Le fonction basique d'une carte de correspondance (Map, Flatlist, …).

modules map

Mais ce n'est pas tout !

Empâquetage (packing)

Dans le navigateur, les modules n'existent pas (en quelque sorte). En fait cela signifie qu'il n'y a pas de fonction require, ni de module.exports. Ainsi, même si nous avons toutes nos dépendances, nous n'avons actuellement aucun moyen de les utiliser comme modules de la même manière que l'ont pourrait dans NodeJS.

Ce qui veut dire que le code écrit n'est pas interprétable dans un contexte de navigateur web frontend.

Bah du coup on est un peu embêté… Mais pas de panique le bundler de code est là pour cela ! Ce qu'il se passe sous le capôt c'est que le bundler va utiliser une « factory function » soit une fonction (qui n'est pas un constructeur) qui renvoie un objet.

C'est un pattern de la programmation orientée objet et l'une de ses utilisations est l'encapsulation et l'injection de dépendances.

En utilisant une fonction d'usine, nous pouvons à la fois injecter notre propre fonction require et notre objet module.exports qui peuvent être utilisés et donner au module sa propre portée.

On créer donc du code exploitable regroupé dans un seul fichier qui nous permet de conserver nos modules et leur contexte d'encapsulation.

Bref voilà ! Cela explique à peu prêt comment fonctionne un module bundler.

À noter que pour webpack par exemple on peut enregistrer des loaders personnalisés qui permettent d'appliquer des transformations sur le code au moment où la résolution des dépendances et l'empâquetage s'effectue : pratique, cela permet par exemple l'utilisation de polyfills de code ou bien de convertir du Typescript en Javascript !