React.js : réduire son bundle JavaScript avec du code splitting
28 mai 2019
Les utilisateurs sont de plus en plus exigeants en termes de performances. On construit des applications riches, et au fur et à mesure qu’une application grossit, son bundle JavaScript grossit également.
Quand on développe une single page application (SPA), on crée beaucoup de composants. Chacun de ces composants a ses propres dépendances, utilitaires, librairies, etc. À un certain moment, on réalise que notre site web est lent 🙀. En investiguant, on se rend compte que notre bundle JavaScript est bien trop gros. Il y a plusieurs façons de s’en rendre compte :
- Le site met trop de temps à se charger avec une mauvaise connexion. On peut facilement tester la vitesse de son site dans différentes conditions en utilisant les chrome dev tools.
- Webpack lève une alerte disant que notre bundle est trop gros :
- Les outils d’analyse (comme lighthouse) nous demandent de réduire la taille du bundle :
Quand on se retrouve dans une de ces situations, la question que l’on se pose est : « Ok, j’ai un problème de performance à cause de la taille de mon bundle. Comment je règle ce problème ? »
Mesure. Analyse. Passe à l’action.
Utilisons une application React assez simple comme exemple. Dans cette application, il y a deux routes, Home et Heavy. La route Home utilise un composant très léger, cependant la route Heavy utilise un composant lourd car il utilise (volontairement) les librairies lodash et moment.
Mesure
La première chose à faire est de mesurer l’impact d’un problème avant de se lancer dans sa résolution. On peut utiliser source-map-explorer pour inspecter nos bundles JavaScript et agir en fonction.
Note : J’utilise source-map-explorer parce que c’est l’outil recommandé par React et que l’on peut l’utiliser sans éjecter une application create-react-app. Pour ceux qui trouvent ces analyses difficiles à lire (comme moi), vous pouvez utiliser webpack-bundle-analyzer, mais vous serez peut-être obligé d’éjecter.
Il faut ajouter un nouveau script dans le package.json.
On peut ensuite lancer les commandes :
Analyse
En regardant la sortie de l’analyse, on peut voir que lodash et moment prennent une grande place dans notre bundle. Quand on charge la page Home, on envoie lodash et moment pour rien. La page Home n’a pas besoin de ces librairies.
Si on sépare le composant Heavy du reste du bundle JavaScript, cela permettrait de supprimer presque la moitié du bundle pour assurer un chargement plus rapide de la page Home.
Ce type de changement peut drastiquement réduire la taille de vos bundles JavaScript. Il faut garder à l’esprit que l’objectif est d’avoir le plus d’impact avec le moins d’effort possible. Il ne faut pas s’attarder sur des morceaux de code qui sont légers, car ils auront peu ou pas d’impact sur la taille finale du bundle.
Passe à l’action
Le but est de séparer le composant Heavy (et ses dépendances) du reste du bundle JavaScript. Webpack met à disposition une fonctionnalité de code splitting. Tout d’abord, regardons comment fonctionne le code splitting de webpack en dehors du contexte de react.
Note : Quand on utilise create-react-app, webpack est déjà configuré pour supporter le code splitting avec les imports dynamiques.
Webpack supporte la syntaxe d’import dynamique. Il utilise des promises en interne. Voici la différence entre un import statique et un import dynamique :
- L’impact statique renvoie ce qui est exporté par le module en tant que default export.
- L’import dynamique renvoie une promise. Cette promise met à disposition un objet avec une propriété default. La valeur de cette propriété est ce qui est exporté par le module en tant que default export. Quand webpack voit un import dynamique, il regroupe ce qui n’est pas importé statiquement dans le bundle à l’intérieur d’un chunk (un morceau de code mis dans un fichier à part). Il s’occupe également du code qui ira récupérer ce chunk quand on en aura besoin.
Retour dans notre application React
Regardons à quoi ressemble le network avant de séparer le composant :
On peut voir que le chunk 2.xxxx pèse 236kb (comme vu dans le rapport d’analyse) et prend 7.36s à se charger avec une connexion lente (stimulé avec les chromes dev tools). Maintenant, enlevons le composant Heavy et regardons ce qu’il se passe.
On peut utiliser lazy et suspense. Lazy permet de rendre un composant importé dynamiquement comme un composant normal et suspense permet de définir un fallback pendant que le composant se charge.
Ensuite nous pouvons utiliser ce composant dans notre router :
Maintenant relançons les commandes de build et d’analyse :
Cette analyse est un peu plus détaillée au niveau du chunk principal car le zoom est un peu différent mais ce n’est pas ce qui est important.
La chose la plus importante à regarder est qu’il y a maintenant deux chunks différents (2.xxxx et 3.xxxx). 3.xxxx a été créé parce qu’on a utilisé un import dynamique, comme vu dans la section précédente. Le chunk 3.xxxx contient le composant Heavy (et ses dépendances : lodash, moment) et ne sera pas chargé si on n’en a pas besoin. Grâce à cette séparation, notre chunk 2.xxxx pèse seulement 148kb, ce qui est bien moins que le précédent (263kb).
Voici ce qu’il se passe quand on charge la page Home :
La page se charge plus rapidement (2 secondes) car le chunk est plus léger. Si on navigue sur la route Heavy, le chunk du composant est automatiquement téléchargé :
Récap
- Le code splitting est utile pour réduire la taille de notre bundle JavaScript en séparant les grosses parties et les charger quand on en a besoin.
- Lazy et Suspense nous aident à séparer nos composants dans des chunks.
- Le code splitting est une feature de webpack et peut être utilisé sur n’importe quel projet qui utilise webpack, sans React.
- La première étape est toujours d’analyser avant de se lancer dans de l’optimisation de performance. Il faut se focaliser sur ce qui a le plus d’impact pour le moins d’effort.
Pour aller plus loin :
Vous souhaitez booster la performance de vos projets avec JavaScript ?
Visionner le webinarExpert technique