Réduction des tailles de bundle avec Webpack Analyzer et React Lazy / Suspense

À mesure que la complexité des applications client augmente, la taille de leurs bundles devient de plus en plus. Dans cette situation, les personnes souffrent le plus, obligées, pour diverses raisons, d'utiliser des connexions Internet lentes. De plus, chaque jour, cela ne fait qu'empirer.



L'auteur de l'article, dont nous publions aujourd'hui la traduction, travaille sous Wix. Il veut expliquer comment il a pu réduire la taille d'un paquet d'environ 80%, en utilisant Webpack Analyzer et React Lazy / Suspense.

À quelle heure commence l'optimisation?


Si vous venez de commencer à travailler sur votre nouvelle application Web, vous essayez probablement de vous concentrer sur sa création, pour ainsi dire, «décoller», en essayant de la faire fonctionner. Vous ne prêtez probablement pas beaucoup d'attention aux performances ou à la taille du bundle. Je peux comprendre ça. Cependant, mon expérience suggère que les performances et la taille des bundles doivent être prises en compte dès le début. Une bonne architecture d'application et une «réflexion sur l'avenir du projet» en temps opportun vous permettront de gagner du temps et de ne pas accumuler de dettes techniques importantes. De toute évidence, il est difficile de «tout prévoir» à l'avance, mais vous devez vous efforcer de tout faire dès le premier jour de travail sur le projet.

Voici quelques excellents outils qui, je pense, devraient être utilisés dès le départ. Ces outils vous aideront à reconnaître les packages NPM «problématiques» avant même qu'ils n'occupent une place importante dans l'application.

UndBundlephobia


Bundlephobia est un site qui vous permet de savoir dans quelle mesure un certain package NPM augmentera la taille d'un bundle. Il s'agit d'un excellent outil pour aider le programmeur à prendre les bonnes décisions concernant le choix des packages tiers dont il peut avoir besoin. Bundlephobia aide à concevoir l'architecture de l'application afin que sa taille ne se révèle pas trop grande. La figure suivante montre les résultats de la vérification d'une bibliothèque populaire pour travailler avec le temps, appelé moment. Vous pouvez voir que cette bibliothèque est assez grande - près de 66 Ko sous forme compressée. Pour de nombreux utilisateurs travaillant sur Internet haut débit, ce n'est rien. Cependant, il convient de prêter attention à la durée de téléchargement de ce package dans les réseaux 2G / 3G. Elle est respectivement de 2,2 et 1,32 seconde. Et, faites attention, nous ne parlons que de ce seul paquet.


Résultats de l'analyse du paquet de moment Bundlephobia

▍ Coût d'importation


Import Cost est une extension très intéressante pour de nombreux éditeurs de code populaires (il compte plus d'un million de téléchargements pour VS Code ). Il peut montrer le "coût" des colis importés. Je l'aime particulièrement car cela aide à identifier les zones problématiques de l'application tout en y travaillant. La figure suivante (tirée de la page Import Cost GitHub) montre un excellent exemple de l'impact sur les dimensions du projet d'une approche différente de l'importation d'entités. Ainsi, l'importation de la seule propriété uniqueId partir de Lodash entraîne l'importation de la bibliothèque entière dans le projet (70 Ko). Et si vous importez directement la fonction uniqueId , alors seulement 2 Ko seront ajoutés à la taille du projet. En savoir plus sur le coût d'importation ici .


Le "coût" de l'importation de la bibliothèque Lodash entière dans le projet et d'une seule fonction spécifique de cette bibliothèque

Le cas des paquets déraisonnablement grands


Ainsi, vous avez créé votre merveilleuse application. Il fonctionne très bien sur votre Internet haut débit et sur votre ordinateur le plus puissant, plein de RAM. Vous l'avez sorti dans le monde réel. Après un certain temps, des plaintes ont commencé à émaner de vous ou de vos propres analystes. Ces plaintes concernaient les temps de chargement des applications. Quelque chose de similaire m'est arrivé récemment lorsque nous, chez Wix, avons dévoilé une nouvelle fonctionnalité sur laquelle je travaillais.

Afin de vous mettre un peu au courant, parlons de cette nouvelle opportunité. Il s'agit d'une nouvelle barre de progression située en haut du panneau latéral de l'interface des paramètres du site utilisateur. Le but de ce mécanisme est d'attirer l'attention de l'utilisateur sur les différentes étapes qu'il doit effectuer pour que son projet d'entreprise ait de meilleures chances de réussite (connexion SEO, ajout de régions dans lesquelles les marchandises sont livrées, ajout du premier produit , etc.).

La barre de progression est mise à jour automatiquement en se connectant au serveur à l'aide de sockets Web. Lorsque l'utilisateur a terminé toutes les étapes recommandées, une fenêtre contextuelle s'affiche avec des félicitations. Une fois cette fenêtre fermée, la barre de progression est masquée et ne s'affiche plus jamais lorsque vous travaillez avec le site dans lequel vous l'avez utilisé. C'est ce dont nous venons de parler.


Barre de progression et fenêtre de félicitations

Que s'est-il passé? Pourquoi nos analystes se plaignent-ils que le temps de chargement des pages a augmenté? Lorsque j'ai examiné la situation à l'aide de l'onglet Réseau des outils de développement Chrome, il est immédiatement devenu clair pour moi que mon ensemble était assez gros. A savoir, sa taille était de 190 Kb.


Taille de l'ensemble trouvée à l'aide des outils de développement Chrome

"Pourquoi cette petite chose a-t-elle besoin d'un paquet relativement grand?" J'ai alors pensé. Mais la vérité - pourquoi?

▍Rechercher les points problématiques dans le bundle


Après avoir réalisé que la taille du paquet est trop grande, il est temps de découvrir la raison de cela. C'est là que Webpack Bundle Analyzer a été utile - un excellent outil pour identifier les zones problématiques des bundles. Il ouvre un nouvel onglet de navigateur et affiche des informations de dépendance.

C'est ce qui s'est passé après avoir analysé le bundle avec cet outil.


Résultats de Webpack Bundle Analyzer

Avec l'aide de l'analyseur, j'ai pu détecter le "criminel". Le package lottie-web a été utilisé ici, ce qui a ajouté 61,45 Ko à la taille du bundle. Lottie est une très belle bibliothèque JavaScript qui permet d'utiliser des outils de navigation standard pour produire des animations créées dans Adobe After Effect. Dans mon cas, c'était pour que notre designer ait besoin d'une belle animation, qui a été réalisée lorsque la fenêtre de félicitations est apparue. Il a créé cette animation et me l'a donnée sous la forme d'un fichier JSON, que j'ai transféré dans le package Lottie et obtenu une belle animation. En plus du package lottie-web, j'avais également un fichier JSON avec des descriptions d'animation. La taille de ce fichier était de 26 Kb. En conséquence, la bibliothèque Lottie, le fichier JSON, ainsi que certaines petites dépendances auxiliaires me "coûtent" environ 94 Ko. Et ce n'est qu'une animation de la fenêtre avec des félicitations à l'utilisateur. L'utilisateur, quand il a vu ces félicitations, aurait dû être content. Tout cela m'a rendu triste.

La technologie React Lazy / Suspense vient à la rescousse


Après avoir découvert la cause du problème, il est temps de résoudre ce problème. Il était clair qu'il n'était pas nécessaire de charger au tout début du travail tout ce qui était nécessaire pour l'animation. En fait, il y avait une chance considérable qu'au cours de la session en cours de l'utilisateur, il n'ait pas à afficher une fenêtre de félicitations. Ensuite, je me suis familiarisé avec les technologies React Lazy / Suspense récemment apparues et j'ai pensé que maintenant j'avais probablement une bonne occasion de les tester.

Si vous n'êtes pas familier avec le concept de composants "paresseux" (paresseux), sachez que leur signification est de diviser l'application en petits morceaux de code. Le téléchargement de ces fragments n'est effectué que lorsqu'ils sont nécessaires. Dans mon cas, cela s'est exprimé dans le fait que je devais sélectionner dans la fonction principale de la barre de progression le composant qui était responsable de montrer les félicitations. Il était nécessaire de charger ce composant uniquement lorsque l'utilisateur a terminé la séquence d'étapes recommandée.

React 16.6.0 (et les versions plus récentes) possède une API simple conçue pour rendre les composants paresseux. Ce sont React.lazy et React.Suspense . Prenons un exemple:

 const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() {  return (    <div>      <React.Suspense fallback={<div>Loading...</div>}>        <OtherComponent />      </React.Suspense>    </div>  ); } 

Nous avons un composant qui affiche l'élément <div> , et il OtherComponent un composant Suspense qui enveloppe le composant OtherComponent . Si vous regardez la première ligne de cet exemple, vous pouvez voir que OtherComponent pas importé directement dans le code. En règle générale, une importation comme celle-ci ressemble à l' import OtherComponent from './OtherComponent'; .

Au lieu de cela, la commande d'importation est encadrée comme une fonction qui prend un chemin de fichier. Ce mécanisme fonctionne car Webpack dispose d'outils de séparation de code intégrés. Lorsqu'une construction similaire est présente dans le code, une promesse est retournée, qui, après avoir téléchargé le fichier, est résolue avec le contenu de ce fichier. Notre équipe d'importation est enveloppée dans une fonction React.lazy .

Dans les matériaux de rendu renvoyés par MyComponent , OtherComponent dans un composant React.Suspense qui a une propriété de fallback . Dans notre cas, il s'avère que lorsque le rendu atteint OtherComponent , le chargement du composant correspondant commence. En attendant, ce qui est écrit dans la propriété de fallback est rendu. Dans cet exemple, il s'agit du texte Loading… En fait, c'est tout. Ces mécanismes font tout simplement leur travail.

Il est vrai que vous devez prendre en compte quelques fonctionnalités lorsque vous travaillez avec Lazy / Suspense.

  1. Un composant importé de manière "paresseuse" doit contenir l'exportation par défaut, qui sera le point d'entrée du composant. Les exportations nommées ne peuvent pas être utilisées ici.
  2. Vous devez React.lazy le composant importé à l'aide de la fonction React.lazy dans le composant React.Suspense . Le composant React.Suspense fournir la propriété de fallback . Sinon, une erreur se produira. Cependant, si vous ne voulez tout simplement pas rendre quoi que ce soit tant que le composant n'a pas terminé le chargement paresseux, vous pouvez simplement écrire null en fallback sans essayer de contourner la nécessité d'écrire quelque chose dans cette propriété de manière délicate.

L'utilisation de React Lazy / Suspense m'a-t-elle aidé?


Oui, ça a aidé! Le fractionnement de code a fonctionné de manière incroyable. Jetons un coup d'œil aux résultats de l'analyse du nouveau bundle à l'aide de Webpack.


Résultats de Webpack Bundle Analyzer après la division du code

Comme vous pouvez le voir, la taille de mon bundle a diminué d'environ 50% - à 96 Ko. Super!

Alors quoi, maintenant le problème est résolu? Non, malheureusement. Quand j'ai regardé la page, il s'est avéré que la fenêtre contextuelle avec félicitations a perdu son positionnement.


La fenêtre de félicitations n'apparaît pas là où vous en avez besoin

Le problème était que j'ai «demandé» à la fenêtre de s'ouvrir en changeant l'état du composant React. Pendant ce temps, j'ai déjà rendu null (c'est-à-dire que je n'ai rien rendu) en utilisant le composant React.Suspense . Après le chargement paresseux des données nécessaires, les documents pertinents ont été ajoutés au DOM. Cependant, le positionnement du popup a déjà été fait. Par conséquent, du fait que les propriétés du composant correspondant n'ont pas changé, ce composant «ne savait pas» qu'il devait résoudre le problème de positionnement. Si je modifiais la taille de la fenêtre du navigateur, la fenêtre contextuelle apparaissait au bon endroit car le composant correspondant observait les changements de propriétés et les événements de redimensionnement de la fenêtre, initiant, si nécessaire, un repositionnement.

Comment résoudre ce problème? La solution était d'éliminer «l'intermédiaire».

J'ai dû d'abord charger le composant "paresseux", et ensuite seulement écrire à l'état qui dirait à la fenêtre avec des félicitations qu'elle doit s'ouvrir. J'ai pu le faire en utilisant les mêmes mécanismes de partage de code Webpack, mais maintenant - sans implémenter les importations en utilisant React.lazy :

 async loadAndSetHappyMoment() {  const component = await import(    '../SidebarHappyMoment/SidebarHappyMoment.component'  );  this.SidebarHappyMoment = component.SidebarHappyMoment;  this.setState({    tooltipLevel: TooltipLevel.happyMoment,  }); } 

Cette fonction est appelée après que mon composant, via le mécanisme des sockets Web, soit informé qu'il doit afficher une fenêtre avec félicitations. J'ai utilisé la fonction d' import Webpack (deuxième ligne de code). Si vous vous souvenez, j'ai dit ci-dessus que cette fonction renvoie une promesse, en conséquence j'ai pu utiliser la construction async/await .

Une fois le chargement du composant terminé, je l'écris dans l'instance de mon composant (avec la commande this.SidebarHappyMoment = component.SidebarHappyMoment; ). Cela me donne l'opportunité de l'utiliser plus tard lors du rendu. Veuillez noter que je peux maintenant utiliser des exportations nommées. Dans mon cas, j'ai utilisé, dans la ligne ci-dessus, le nom SidebarHappyMoment . Et enfin, et ce n'est pas moins important que tout le reste, j'informe la fenêtre qu'elle doit s'ouvrir, en changeant son état en conséquence une fois que je sais que le composant est prêt à fonctionner.

Par conséquent, le code de rendu ressemble maintenant à ceci:

 renderTooltip() {  if (this.state.tooltipLevel === TooltipLevel.happyMoment) {    return <this.SidebarHappyMoment />;  }  // ... } 

Notez que la commande return <this.SidebarHappyMoment />; renvoie this.SidebarHappyMoment - ce que j'ai écrit précédemment à l'instance de mon composant. Il s'agit maintenant d'une fonction de rendu synchrone normale, identique à celles que vous avez utilisées un million de fois. Et maintenant, la fenêtre de félicitations s'affiche exactement à l'endroit où elle doit être affichée. Et le fait est qu'il ne s'ouvre que lorsque son contenu est prêt à l'emploi.

Le produit définit l'architecture


Si l'idée dans le titre de cette section a soulevé un point d'interrogation important dans votre imagination, sachez que c'est le cas. Le produit définit l'architecture.

Il s'agit du fait que le produit définit ce qui doit être visible et interactif lors du premier rendu du composant. Cela aide le développeur à comprendre exactement ce qu'il peut séparer du code principal et charger plus tard, si nécessaire. J'ai réfléchi à la situation et je me suis souvenu qu'après que l'utilisateur a terminé les étapes recommandées pour configurer le site, ou s'il n'est pas l'administrateur du site, je n'ai pas besoin de lui montrer la barre de progression et le contenu connecté. Maintenant, en utilisant ces informations, j'ai pu continuer à partager le bundle. Voilà ce que j'ai.


La séparation continue du paquet

Après cela, la taille du paquet n'était plus que de 38 Ko. Et je vous rappelle que nous avons commencé avec 190 Ko. Il y a une réduction de 80% de la taille du paquet. Et en passant, je vois déjà d'autres possibilités de séparation de code. J'ai hâte de continuer à optimiser le bundle.

Résumé


Les développeurs ont la possibilité de s'efforcer de rester dans leur «zone de confort» et de ne pas se plonger dans autre chose que l'appareil du code lui-même et sa fonctionnalité. Cependant, un programmeur qui utilise les outils décrits ci-dessus, réfléchit de manière créative et travaille en étroite collaboration avec d'autres spécialistes, peut être en mesure d'améliorer les performances de son application en réduisant considérablement la taille du bundle contenant ce qui est nécessaire pour commencer à travailler avec cette application.

Chers lecteurs! Utilisez-vous le fractionnement de code pour accélérer le chargement de vos applications Web?


Source: https://habr.com/ru/post/fr468225/


All Articles