Angular génère par défaut des bundles JavaScript volumineux. Même une application modeste peut dépasser 200 KB en gzip, ce qui impacte directement le First Contentful Paint et les métriques Core Web Vitals. Le tree-shaking et le bundle splitting sont les deux leviers principaux pour réduire ce poids. Le tree-shaking élimine le code non utilisé au moment de la construction, tandis que le bundle splitting divise votre application en chunks distincts chargés à la demande. Combinées, ces techniques peuvent réduire votre bundle principal de 40 à 60 % sur une application réaliste.
Comprendre le tree-shaking et ses conditions préalables
Le tree-shaking fonctionne en analysant le graphe de dépendances de votre code et en supprimant les exports non importés. Cela nécessite du code modulaire écrit en ES2015 (imports/exports nommés) et un bundler capable de statique analysis, comme Webpack ou esbuild. Angular CLI utilise Webpack par défaut, configuré avec les bons flags pour le production build. Cependant, le tree-shaking n'est efficace que si vos dépendances sont elles-mêmes bien structurées. De nombreux packages npm incluent un champ sideEffects dans leur package.json : si ce champ vaut false , le bundler supprimera les imports inutilisés sans crainte. Si vous maintenez une librairie Angular, déclarez explicitement "sideEffects": false pour permettre aux consommateurs de bénéficier du tree-shaking. Un piège courant : les imports wildcard ( import * as utils from './utils' ) empêchent le tree-shaking car le bundler ne sait pas ce qui est réellement utilisé. Préférez toujours les imports nommés ( import { helperFunction } from './utils' ).
Testez l'efficacité du tree-shaking en analysant votre bundle avec ng build --prod --stats-json , puis ouvrez le JSON généré avec webpack-bundle-analyzer. Vous verrez immédiatement quels modules sont inclus et leur taille. Si vous découvrez des dépendances inutilisées, supprimez-les ou réorganisez votre code. Par exemple, si vous importez un utilitaire complet d'une librairie de date mais n'utilisez que trois fonctions, envisagez une librairie plus légère ou écrivez vos propres helpers. Le coût de la maintenabilité doit être pesé contre le gain de performance.
Bundle splitting : du lazy loading au code splitting stratégique
Le bundle splitting consiste à diviser votre application en plusieurs chunks JavaScript, chargés selon les besoins de l'utilisateur. Angular supporte le lazy loading nativement via les routes : chaque feature module chargé via loadChildren génère son propre chunk. Cette approche est simple et très efficace. Pour une application avec cinq modules métier, vous pouvez réduire le bundle initial de 80 % en chargeant chacun à la demande. Cependant, le lazy loading par routes seul ne suffit pas toujours. Un module peut être très volumineux (ex. un tableau avec 50 colonnes et des filtres avancés) ou rarement utilisé (ex. une page d'administration). Dans ces cas, vous pouvez pousser plus loin le code splitting.
Considérez une application de gestion de contenu avec une page d'édition riche utilisant TinyMCE. TinyMCE pèse ~200 KB non compressé. Si vous l'importez au niveau du module principal ou d'un module chargé tôt, ce poids s'ajoute au bundle initial. La solution : charger TinyMCE dynamiquement, uniquement quand l'utilisateur accède à la page d'édition. Utilisez import() dynamique et @angular/common 's NgComponentOutlet ou un resolver personnalisé. Voici un pattern concret : créez un composant lazy wrapper qui charge le composant éditeur via import() dans son constructeur ou via ngOnInit . Le bundle principal reste léger, et seul les utilisateurs accédant à l'édition paient le coût.
import { Component, input } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-editor-lazy',
standalone: true,
imports: [CommonModule],
template: `
<div *ngIf="editorComponent$ | async as editor">
<ng-container *ngComponentOutlet="editor"></ng-container>
</div>
})
export class EditorLazyComponent {
editorComponent$ = this.loadEditor();
private loadEditor() {
return import('./editor.component').then(m => m.EditorComponent);
}
}
Combiné au lazy loading par routes, ce pattern double voire triple l'efficacité du splitting.
Stratégies de chunking avancées
Au-delà du lazy loading, vous pouvez affiner le splitting via la configuration Webpack d'Angular. Le fichier angular.json expose des options optimization et sourceMap . Pour le production, Angular compresse automatiquement et utilise la minification. Mais vous pouvez aussi configurer le commonChunk : diviser les dépendances communes en un chunk séparé ( vendor.js ). Cela permet aux navigateurs de cacher ce chunk plus longtemps entre les mises à jour, car les dépendances changent moins souvent que votre code métier. Une autre stratégie : les chunks de dépendances lourdes. Si votre application utilise RxJS, Lodash et Chart.js, créez des entrées séparées pour chacun. Cela permet un chargement parallèle et une meilleure utilisation du cache HTTP/2.
La configuration Webpack personnalisée se fait via @angular-builders/custom-webpack ou directement dans angular.json via l'option optimization . Exemple minimal : augmenter le budget de performance pour identifier les régression progressivement, plutôt que de les découvrir en production.
"budgets": [
{
"type": "bundle",
"name": "main",
"maximumWarningSize": "200kb",
"maximumErrorSize": "250kb"
}
]
Chaque déploiement qui dépasse 200 KB déclenche une alerte. Cela force l'équipe à justifier les ajouts de dépendances ou à optimiser le code.
Pièges courants et anti-patterns
Le piège le plus courant : importer entièrement une grosse librairie quand vous n'en utilisez qu'une petite partie. Par exemple, import * as moment from 'moment' pour une seule fonction de formatting. Utilisez plutôt date-fns avec ses modules spécialisés ou écrivez votre propre helper. Un autre piège : les dépendances peerDependencies mal déclarées. Si votre composant réutilisable dépend d'une librairie mais ne la liste pas en peerDependencies , les consommateurs téléchargeront deux copies. Cela double le poids du bundle. Vérifiez vos package.json et package-lock.json avec npm ls <package> pour détecter les doublons.
Un dernier piège subtil : les imports circulaires qui empêchent le tree-shaking optimal. Webpack génère une alerte, mais elle est souvent ignorée. Réorganisez vos modules pour éviter les dépendances circulaires. Un outil comme madge (analyse les dépendances) vous aide à visualiser et corriger ces patterns. Aussi, ne lazy-loadez pas trop : chaque chunk supplémentaire ajoute du HTTP overhead. Visez 5 à 10 chunks optimaux pour une application moyenne.
Mesurer et valider l'impact
Installez webpack-bundle-analyzer : npm install --save-dev webpack-bundle-analyzer . Générez un build de production avec ng build --prod --stats-json . Ensuite, analysez avec webpack-bundle-analyzer dist/your-app/stats.json . Vous verrez un graphique treemap montrant chaque module et sa taille réelle. Cela révèle immédiatement les dépendances cachées ou les imports accidentels. Comparez avant/après votre optimisation : visez au minimum 20 % de réduction sur le bundle principal. Pour valider l'impact réel sur les utilisateurs, mesurez avec Lighthouse ou WebPageTest. Un bundle réduit de 50 KB peut améliorer le FCP de 0,5 à 1 seconde sur 3G, ce qui est significatif pour la conversion.
Conclusion opérationnelle
Le tree-shaking et le bundle splitting ne sont pas des optimisations « fancy » : ce sont des pratiques essentielles pour une application Angular moderne. Commencez par auditer votre bundle actuel avec webpack-bundle-analyzer. Appliquez les quick wins : supprimez les imports wildcard, éliminez les dépendances inutilisées, déclarez sideEffects: false dans vos librairies. Ensuite, structurez vos routes avec lazy loading et fragmentez les modules volumineux via import() dynamique. Enfin, mettez en place des budgets de performance dans angular.json pour empêcher les régression futures. Ces étapes, appliquées progressivement, réduiront votre Time to Interactive de 30 à 50 % et amélioreront directement vos Core Web Vitals. L'investissement initial (quelques heures) se rentabilise rapidement en satisfaction utilisateur et en SEO.