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.
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.
| Sujet | Lien officiel | À vérifier |
|---|---|---|
| webpack | Tree Shaking | sideEffects, ESM, build production |
| option webpack | optimization.sideEffects | lecture du champ sideEffects |
| Rollup/Vite | Rollup treeshake | éviter un réglage global trop agressif |
| détail Rollup | treeshake.moduleSideEffects | conserver les modules d’initialisation |
| esbuild | Tree shaking | analyse 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ège | Symptôme | Correctif |
|---|---|---|
| Babel ou TypeScript produit CommonJS trop tôt | exports inutilisés conservés | garder ESM jusqu’au bundler |
sideEffects: false trop large | CSS ou polyfills supprimés | lister les fichiers à effet |
| default object export | helpers inutilisés conservés | passer aux named exports |
| barrel avec setup au niveau supérieur | un seul composant coûte cher | barrel limité aux re-exports |
| mesure en dev build | chiffres trompeurs | comparer production, minify, gzip |
moduleSideEffects: false global | initialisation supprimée | valider par package ou fichier |
| namespace import | analyse plus conservatrice | utiliser 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.
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.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Permission receipt Claude Code : portée, preuves et rollback
Modèle de permission receipt pour Claude Code : actions autorisées, limites d'approbation, commandes de preuve, rollback et CTAs revenus.
Agent Harness securise pour Claude Code et Codex : permissions, verification et rollback
Construisez un Agent Harness pratique pour Claude Code et Codex avec politiques, plan, verification et recuperation.
Sous-agents Claude Code : guide pratique pour déléguer sans perdre le contrôle
Guide pratique des sous-agents Claude Code pour répartir articles et code : règles, prompts, pièges et checklist.