Advanced (Mis à jour: 02/06/2026)

Tree shaking avec Claude Code : réduire un bundle sans casser l'app

Améliorez le tree shaking avec Claude Code : ESM, sideEffects, mesures, pièges et exemples exécutables.

Tree shaking avec Claude Code : réduire un bundle sans casser l'app

Comprendre le tree shaking simplement

Le tree shaking est l’étape qui retire du bundle de production les exports JavaScript ou TypeScript qui ne sont pas utilisés. En pratique, cela veut dire : ne pas envoyer au navigateur du code dont la page actuelle n’a pas besoin. Le gain se voit sur le téléchargement, le parsing, l’exécution et parfois le temps avant interaction.

Un bundler ne devine pas l’intention du développeur. Il analyse import, export, le champ sideEffects de package.json, les transformations CommonJS et le code exécuté au niveau supérieur d’un module. C’est pour cela que l’on voit des surprises : une fonction inutilisée reste dans le bundle, ou bien sideEffects: false supprime du CSS nécessaire.

Claude Code devient utile quand la demande est mesurable. Il doit d’abord relever la taille actuelle, repérer les dépendances CommonJS, examiner les barrel files, identifier les fichiers à effet de bord, modifier une zone limitée puis revérifier le build. Voici le flux que Masa applique sur des projets Vite, React et Astro lorsqu’il faut réduire le poids sans casser les écrans.

flowchart LR
  A["source files"] --> B["ESM import/export graph"]
  B --> C["bundler tree shaking"]
  C --> D["minified production bundle"]
  B --> E["side effects kept"]
  E --> D
  D --> F["measure bytes and gzip"]

Partir des documents officiels

Le comportement exact dépend du bundler. Avant de modifier du code de production, utilisez les références officielles.

SujetLien officielÀ vérifier
webpackTree ShakingsideEffects, ESM, build production
option webpackoptimization.sideEffectslecture du champ sideEffects
Rollup/ViteRollup treeshakeéviter un réglage global trop agressif
détail Rolluptreeshake.moduleSideEffectsconserver les modules d’initialisation
esbuildTree shakinganalyse ESM et mesure par metafile

Le point important : le tree shaking ne supprime pas des morceaux de texte au hasard. Il suit un graphe ESM statique et garde du code si sa suppression peut changer le comportement à l’exécution. CommonJS, imports namespace, gros objets default et imports CSS/polyfill au niveau supérieur limitent souvent le résultat.

Prompt utile pour Claude Code

Commencez par une enquête, pas par une modification. Un sideEffects: false global peut masquer des régressions visuelles ou d’initialisation.

Analyse pourquoi le tree shaking est faible dans le bundle de production.
Donne d'abord un tableau avec taille actuelle, principaux chunks, dépendances lourdes,
dépendances CommonJS et barrel exports.
Pour chaque changement proposé, indique le risque, l'impact attendu et les commandes de vérification.
CSS, polyfills, analytics et global setup ne doivent pas être supprimés.

Pour la phase de correction, réduisez le périmètre.

Pour cette passe, travaille seulement sur src/utils et src/components/index.ts.
Transforme les default object exports en named exports et mets à jour les imports.
Ensuite, exécute npm run build et la mesure de taille du bundle.
Si une API publique change, garde un re-export compatible.

Cette demande force Claude Code à optimiser sans perdre le comportement existant.

Exemple minimal exécutable

Ce mini-projet compare un default object export à des named exports avec esbuild.

mkdir tree-shaking-lab
cd tree-shaking-lab
npm init -y
npm install --save-dev esbuild
mkdir src scripts

Utilisez ce package.json.

{
  "name": "tree-shaking-lab",
  "version": "1.0.0",
  "type": "module",
  "private": true,
  "sideEffects": false,
  "scripts": {
    "measure": "node scripts/measure-tree-shaking.mjs"
  },
  "devDependencies": {
    "esbuild": "^0.25.0"
  }
}

La version fragile regroupe les helpers dans un objet.

// src/bad-utils.ts
const utils = {
  formatEur(amount: number): string {
    return new Intl.NumberFormat("fr-FR", {
      style: "currency",
      currency: "EUR"
    }).format(amount);
  },
  heavyReport(rows: number[]): string {
    const body = rows.map((row) => `row:${row}`).join("\n");
    return `report\n${body}\n${"=".repeat(4000)}`;
  },
  debugOnly(): string {
    return "debug:" + "x".repeat(4000);
  }
};

export default utils;

La version plus lisible pour le bundler exporte chaque fonction.

// src/good-utils.ts
export function formatEur(amount: number): string {
  return new Intl.NumberFormat("fr-FR", {
    style: "currency",
    currency: "EUR"
  }).format(amount);
}

export function heavyReport(rows: number[]): string {
  const body = rows.map((row) => `row:${row}`).join("\n");
  return `report\n${body}\n${"=".repeat(4000)}`;
}

export function debugOnly(): string {
  return "debug:" + "x".repeat(4000);
}

Créez deux fichiers d’entrée.

// src/bad-entry.ts
import utils from "./bad-utils";

console.log(utils.formatEur(1200));
// src/good-entry.ts
import { formatEur } from "./good-utils";

console.log(formatEur(1200));

Ajoutez le script de mesure.

// scripts/measure-tree-shaking.mjs
import { gzipSync } from "node:zlib";
import { build } from "esbuild";

async function bundle(entryPoint) {
  const result = await build({
    entryPoints: [entryPoint],
    bundle: true,
    minify: true,
    format: "esm",
    treeShaking: true,
    write: false,
    metafile: true
  });

  const code = result.outputFiles[0].text;
  return {
    entryPoint,
    bytes: Buffer.byteLength(code),
    gzipBytes: gzipSync(code).byteLength,
    inputs: Object.keys(result.metafile.inputs)
  };
}

const rows = await Promise.all([
  bundle("src/bad-entry.ts"),
  bundle("src/good-entry.ts")
]);

console.table(rows);

Exécutez :

npm run measure

Dans une vraie application, ajoutez les noms de chunks, gzip, Brotli et le Total Blocking Time de Lighthouse. Pour savoir quelle dépendance reste dans le graphe, combinez cette mesure avec le guide d’analyse de bundle.

Cas d’usage 1 : nettoyer les utilitaires

Le premier gain se trouve souvent dans utils/index.ts ou helpers.ts. Si dates, devises, CSV, Markdown et debug helpers sont mélangés, une seule fonction peut rendre l’analyse plus difficile.

Demandez à Claude Code une découpe limitée.

Découpe src/utils par usage.
Remplace les imports par des named imports et ne re-exporte depuis index.ts que les helpers publics.
S'il existe des appels Date.now, console, localStorage ou fetch au niveau supérieur,
déplace-les dans des fonctions.

Une forme plus saine :

// src/utils/formatDate.ts
export function formatDate(date: Date, locale = "fr-FR"): string {
  return new Intl.DateTimeFormat(locale).format(date);
}
// src/utils/index.ts
export { formatDate } from "./formatDate";
export { formatEur } from "./formatEur";
// src/pages/invoice.ts
import { formatEur } from "../utils/formatEur";

export function invoiceLabel(total: number): string {
  return `Total : ${formatEur(total)}`;
}

Un barrel file n’est pas mauvais par nature. Il devient risqué s’il exécute du setup, enchaîne beaucoup de export * from ou attire des modules sans rapport. Dans le code applicatif, préférez les imports directs ; pour une librairie publique, gardez un barrel fin si la compatibilité l’exige.

Cas d’usage 2 : librairie UI interne

Dans une librairie UI interne, import { Button } from "@acme/ui" peut évaluer Modal, DatePicker, Chart, jeux d’icônes, CSS et setup de thème. Si tous les composants partagent une grosse entrée, les named exports ne suffisent pas.

Exposez des subpath entries.

{
  "name": "@acme/ui",
  "type": "module",
  "sideEffects": [
    "**/*.css",
    "./src/setup-theme.ts"
  ],
  "exports": {
    ".": "./dist/index.js",
    "./button": "./dist/button.js",
    "./modal": "./dist/modal.js"
  }
}

Le consommateur importe seulement l’entrée utile.

import { Button } from "@acme/ui/button";

Ne mettez pas sideEffects: false sans audit. Ce champ signifie qu’importer un module ne lance pas de travail extérieur nécessaire. CSS, polyfills, enregistrement de custom elements et setup de thème doivent rester dans le tableau sideEffects si leur exécution est requise.

Cas d’usage 3 : charger les dépendances admin plus tard

Markdown, PDF, graphiques et éditeurs riches sont rarement nécessaires sur la première page publique. Utilisez le tree shaking pour retirer les exports inutilisés, puis le code splitting pour déplacer les fonctions lourdes.

// src/features/admin/loadMarkdownPreview.ts
export async function renderMarkdown(markdown: string): Promise<string> {
  const [{ unified }, remarkParse, remarkHtml] = await Promise.all([
    import("unified"),
    import("remark-parse"),
    import("remark-html")
  ]);

  const file = await unified()
    .use(remarkParse.default)
    .use(remarkHtml.default)
    .process(markdown);

  return String(file);
}

Un import dynamique ne remplace pas le tree shaking. Il déplace du code vers un chunk tardif ; il ne garantit pas que ce chunk soit petit. Mesurez séparément ce qui quitte le bundle initial et ce qui disparaît dans le chunk lazy.

Cas d’usage 4 : publier un package npm

Si vous publiez une librairie, exposez des entrées ESM faciles à analyser. Un simple main CommonJS limite les optimisations dans les apps frontend.

{
  "name": "@masa/formatters",
  "type": "module",
  "sideEffects": false,
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "./currency": {
      "types": "./dist/currency.d.ts",
      "import": "./dist/currency.js"
    }
  }
}

N’utilisez sideEffects: false que si le package est réellement sans effet à l’import. S’il importe du CSS, installe des polyfills, enregistre des globals ou démarre analytics, listez ces fichiers dans sideEffects.

Pièges et échecs courants

PiègeSymptômeCorrectif
Babel ou TypeScript produit CommonJS trop tôtexports inutilisés conservésgarder ESM jusqu’au bundler
sideEffects: false trop largeCSS ou polyfills suppriméslister les fichiers à effet
default object exporthelpers inutilisés conservéspasser aux named exports
barrel avec setup au niveau supérieurun seul composant coûte cherbarrel limité aux re-exports
mesure en dev buildchiffres trompeurscomparer production, minify, gzip
moduleSideEffects: false globalinitialisation suppriméevalider par package ou fichier
namespace importanalyse plus conservatriceutiliser des named imports ciblés

Les régressions visuelles discrètes sont les plus dangereuses. Un test qui vérifie seulement la présence du DOM peut passer alors que le CSS manque. Comme pour l’optimisation des performances, vérifiez build, écrans clés et comportement visible.

Ajouter un budget de bundle en CI

Sans contrôle continu, le bundle regrossit à la prochaine dépendance. Voici un budget gzip simple.

// scripts/check-bundle-budget.mjs
import { statSync } from "node:fs";
import { gzipSync } from "node:zlib";
import { readFileSync } from "node:fs";

const file = "dist/assets/index.js";
const maxGzipBytes = 160 * 1024;
const raw = readFileSync(file);
const gzipBytes = gzipSync(raw).byteLength;

if (gzipBytes > maxGzipBytes) {
  console.error(`Bundle budget exceeded: ${gzipBytes} > ${maxGzipBytes}`);
  process.exit(1);
}

console.log({
  file,
  bytes: statSync(file).size,
  gzipBytes
});

Exécutez-le après le build.

npm run build
node scripts/check-bundle-budget.mjs

Le premier budget doit être réaliste. Partez du gzip actuel avec un peu de marge, puis demandez une explication quand une PR l’augmente. Si l’application reste lente, inspectez aussi images, polices, latence API et hydratation avec le guide speed optimization.

Checklist de revue Claude Code

Relis cette PR de tree shaking.
1. Les exports inutilisés ont-ils disparu du bundle de production ?
2. CSS, polyfills et fichiers d'enregistrement sont-ils conservés ?
3. ESM est-il préservé jusqu'à l'analyse du bundler ?
4. Les imports directs cassent-ils une API publique ?
5. Quels sont les résultats build, tests, écrans clés et budget bundle ?
Ajoute les fichiers et les preuves de commandes pour chaque point.

Cette checklist transforme un refactoring en contrôle qualité publiable. Dans les missions de Masa, une modification sideEffects n’est terminée qu’après ouverture des écrans login, facturation et admin pour vérifier styles et initialisation.

Angle monétisation

Le tree shaking n’est pas seulement une propreté technique. Une première charge plus légère réduit la friction avant lecture d’article, page produit, inscription ou formulaire de conseil. Sur un média technique comme ClaudeCodeLab, une page d’exemples ou une landing trop lourde affaiblit le chemin vers revenus publicitaires et demandes entrantes.

ClaudeCodeLab peut auditer des bundles Vite, Next.js, Astro et librairies UI internes, puis transformer le diagnostic en tree shaking, code splitting et budgets CI. Pour une consultation efficace, préparez package.json, configuration de build, routes clés et rapport de bundle récent.

Résumé

Le tree shaking fonctionne quand ESM, sideEffects précis, effets maîtrisés et mesure continue sont alignés. Claude Code est efficace si la tâche est limitée et vérifiable : inspecter, découper, corriger les imports, mesurer et relire les cas d’échec.

J’ai exécuté l’exemple minimal de cet article avec npm run measure et confirmé que les entrées bad et good produisent des tailles différentes. Dans un vrai projet, les chiffres dépendent des dépendances et de la configuration ; mesurez toujours votre production build et documentez les effets de bord à conserver.

#Claude Code #tree shaking #bundle size #ES Modules #frontend optimization
Gratuit

PDF gratuit: cheatsheet Claude Code

Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.

Nous protégeons vos données et n'envoyons pas de spam.

Masa

À propos de l'auteur

Masa

Ingénieur spécialisé dans les workflows pratiques avec Claude Code.