Waly

Talk: L'outillage JavaScript - Les bundler

Date 13 janvier 2025
Lieu Hôtel Sea Plaza, Dakar — DevFest DevFest Dakar

Présentation de l'outillage JavaScript au DevFest Dakar 2025

Quand on parle d'outillage JavaScript, on parle souvent de Webpack, Vite, Rollup... sans vraiment savoir ce qu'ils font sous le capot. Au DevFest Dakar, j'ai pris le parti de décortiquer les 4 mécanismes fondamentaux derrière ces outils.

Minification

Objectif : réduire le poids des fichiers à charger par le navigateur.

Le navigateur n'a pas besoin de code lisible. Il n'a besoin que de code fonctionnel. La minification exploite ça en appliquant plusieurs transformations successives :

  1. Suppression des commentaires, espaces et sauts de ligne inutiles
  2. Renommage des variables en noms courts (firstNumberf)
  3. Réécriture avec des opérateurs plus compacts (ternaires, etc.)

Résultat concret sur un fichier math.js : 228 caractères → 98 caractères, sans changer le comportement.

// Avant (math.js) — 228 chars
export function add(firstNumber, secondNumber) {
  return firstNumber + secondNumber;
}
// Recursivity
export function factorial(number) {
  if (number === 0) {
    return 1;
  }
  return number * factorial(number - 1);
}

// Après (math.min.js) — 98 chars
export function add(f,s){return f+s}export function factorial(n){return n===0?1:n*factorial(n-1)}

Les outils du marché, du plus lent au plus rapide : Terser (JS, 2018), SWC (Rust, 2019), Esbuild (Go, 2020).

"Don't minify what you don't need. Minification without tree-shaking is like compressing a trash can without taking out the garbage." — Philip Walton

Bundling

Objectif : concaténer plusieurs fichiers en un seul.

Un projet JS moderne est découpé en modules. Sans bundler, le navigateur doit faire une requête HTTP par fichier. Le bundling rassemble tout en un bundle.js unique.

// math.js
export const add = (a, b) => a + b

// index.js
import { add } from './math'
console.log(add(2, 3))

// → bundle.js
const add = (a, b) => a + b
console.log(add(2, 3))

Les import/export disparaissent, le code est inliné. Une seule requête réseau au lieu de deux.

Tree Shaking

Objectif : supprimer le code non utilisé.

Le tree shaking, c'est ce qui fait que si tu importes add depuis un fichier qui exporte aussi subtract, seul add se retrouve dans le bundle final. Mais ça ne fonctionne qu'avec des ES modules (exports nommés), pas avec CommonJS.

// ❌ Non tree shakable — exporte un objet entier
// math.js
const math = { add: (a, b) => a + b, subtract: (a, b) => a - b }
export default math
// index.js — importe tout l'objet même si on n'utilise que add
import math from './math'

// ✅ Tree shakable — exports nommés
// math.js
export const add = (a, b) => a + b
export const subtract = (a, b) => a - b
// index.js — seul add est inclus dans le bundle
import { add } from './math'

Un exemple qui parle : moment.js (~250 Ko) vs day.js (~7 Ko) pour le même usage. La différence vient en grande partie du fait que moment.js n'est pas tree shakable.

Code Splitting

Objectif : charger les fichiers à la demande, pas tous d'un coup.

Le bundling crée un seul fichier — mais si ce fichier fait 2 Mo, l'utilisateur le télécharge entier au premier chargement, même pour les parties qu'il ne verra jamais. Le code splitting inverse la logique : on découpe en plusieurs chunks, chargés uniquement quand on en a besoin.

La primitive native, c'est l'import dynamique :

document.querySelector('button').addEventListener("click", async () => {
  const { hello } = await import("./utils/hello.js");
  console.log(hello("Sir Kane"));
});

Le fichier hello.js n'est téléchargé qu'au clic. Avant ça, zéro requête.

Les frameworks ont leurs propres abstractions par-dessus : defineAsyncComponent en Vue, les client: directives en Astro (client:load, client:idle, client:visible...).

Pour aller plus loin