La façon dont la taille du code dépend du minificateur, du collecteur et de la langue. Mise à jour inattendue du webpack

Je m'appelle Ilya Goldfarb, je suis développeur d'interfaces Yandex. Je suis intéressé à suivre l'évolution des outils de construction du frontend, donc j'essaie d'étudier les changements dans chaque version de solutions populaires.

En prévision de la sortie de la cinquième version de webpack, je veux parler de sa version apparemment mineure 4.26.0 du 19 novembre 2018, où, de manière inattendue et sans déclarer la guerre, la version par défaut du minifier a été modifiée. Auparavant, il s'agissait d'un package UglifyJS, maintenant il utilise Terser, un fork d'UglifyES, une branche d'UglifyJS qui peut compresser le code ES5 et ES6. Terser est apparu lorsque le principal responsable a refusé de soutenir et de développer UglifyES. Cependant, UglifyJS a également arrêté son développement en août 2018, lorsque la dernière version a été publiée. Dans un nouveau fork, nous avons corrigé quelques bugs et refactorisé un peu le code.

L'API de ces minificateurs est compatible, mais ils produisent des résultats de compression différents. Habituellement, les modifications de ce niveau se produisent uniquement dans les mises à jour majeures et non mineures. Pour cette raison, de nombreux développeurs peuvent ne pas prêter attention à l'innovation. Bien sûr, dans la plupart des cas, tout fonctionnera, mais personne ne veut devenir celui qui obtient des bugs sur la production de son projet en raison du système de construction et de minification.

Toute cette histoire m'a incité à faire une petite recherche personnelle sur la compression. Voici les questions que j'ai posées:

  • Qu'est-ce qui comprime mieux ES5, TerSer ou UglifyJS?
  • Qu'est-ce qui se charge plus rapidement: une version compressée d'ES5 de Terser ou d'UglifyJS?
  • Quelle version pèse le plus: ES5 ou ES6? Et comment TypeScript affecte-t-il cela?
  • Y a-t-il une grande différence entre les paramètres par défaut et les paramètres manuels?
  • Et sinon webpack? Qui produit un assemblage, un rollup ou un webpack plus petit?

Pour la recherche, j'ai fait une petite application React 16 qui rend une application Vue 2 qui rend une application Angular 7 qui a un seul bouton.

Au total, 3 529 695 octets de code non minifié (720 393 octets gzip) ont été publiés.

Qu'est-ce qui comprime mieux ES5, TerSer ou UglifyJS?


J'ai pris le dernier UglifyJS disponible et je suis allé avec le webpack Terser avec l'option ES5 et j'ai utilisé les mêmes paramètres de compression.

Taille en octets
Taille en octets (gzip)
UglifyJS
1 050 376
285,290
Terser
1 089 282
292 678
Conclusion: UglifyJS compresse mieux de 3,5% (2,5% gzip).

Qu'est-ce qui se charge plus rapidement: une version compressée d'ES5 de Terser ou d'UglifyJS?


J'ai mesuré les performances à l'aide du navigateur DevTools Yandex standard. J'ai chargé la page 12 fois et pris la valeur Scripting (exécution du script), en ignorant les trois premières dimensions.
UglifyJS - 221 ms (erreur 2,8%).
Terser - 226 ms (erreur 2,7%).
Conclusion: les valeurs sont trop petites pour une telle erreur, on peut les considérer comme les mêmes. Nous concluons également que cette méthode ne convient pas pour mesurer le temps de charge.
Je n'ai pas mesuré et comparé la vitesse du code, car un code différent fonctionne différemment. Les développeurs de chaque projet doivent enquêter indépendamment sur ce problème.

Quelle version pèse le plus: ES6 ou ES5? Et comment TypeScript affecte-t-il cela?


Pour comparer les deux versions et me concentrer uniquement sur la technologie, j'ai pris des plugins Babel et réalisé quatre assemblages:

  • ES5: tous les plugins marqués comme es2016, + plugin pour Object.assign + plugins pour les versions ultérieures + plugins expérimentaux, la cible dans tsconfig est installée dans ES5;
  • ES5 (ts esnext): tous les plugins marqués comme es2016, + plugin pour Object.assign + tous les plugins pour les versions ultérieures + plugins expérimentaux, la cible dans tsconfig est définie sur esnext;
  • ES6: uniquement les plug-ins pour es2017 et versions ultérieures + plug-ins expérimentaux, la cible dans tsconfig est définie sur ES6;
  • ES6 (ts esnext): uniquement les plugins pour es2017 et versions ultérieures + plugins expérimentaux, la cible dans tsconfig est définie sur esnext.


Taille en octets
Taille en octets (gzip)
ES5
1 186 520
322 071
ES5 (ts esnext)
1 089 282
292 678
ES6
1.087.220
292 232
ES6 (ts esnext)
1.087.220
292 232
Conclusion: la version compressée par Babel avec le code temporel de compilation sous esnext pèse 97 238 octets (8,2%) de moins. Cela s'est produit de manière inattendue, car l' angulaire est écrit en TypeScript, et Vue et React en JavaScript Terser, comme Uglify, ne peut pas compiler un morceau de code inutilisé fourni par l'angulaire avec un script Web lors de la construction avec un webpack. Il s'agit d'un bogue de compilation pour cet exemple. Dans l'assemblage d'un autre projet, il se peut que ce ne soit pas le cas, et la différence sera beaucoup plus petite.

On voit également que le volume de code ES6 est inférieur à ES5 de seulement 2062 octets. Sur le projet pour animaux de compagnie, j'ai obtenu un résultat complètement différent: le code ES6 est 3-6% de plus que ES5. Cela est dû à plusieurs facteurs, dont deux principaux:
1. L'assistant Babel pour l'héritage de classe est inséré une fois, puis coûte quatre octets (e (a, b)), et ES6 utilise l'héritage natif au coût de 15 octets (la classe a étend b).
2. La méthode de déclaration des variables. Dans ES5, ce sont des vars, et ils se compressent parfaitement. Mais dans ES6, ce sont let et const, qui préservent l'ordre d'initialisation et ne sont pas combinés les uns avec les autres.

Une minification agressive dangereuse comme les fonctions de flèche forcée ou l'utilisation du paramètre lâche aidera à réduire la taille du code ES6. Soyez prudent et considérez les subtilités. Par exemple, dans Firefox, les fonctions fléchées sont quatre fois plus lentes que d'habitude, mais dans Chromium il n'y a pas de différence.

Par conséquent, il est impossible de répondre sans équivoque à la question: le résultat dépend fortement du code et de l'exécution cible.

Y a-t-il une grande différence entre les paramètres par défaut et les paramètres manuels?


Comparez s'il est possible d'obtenir une taille de fichier plus petite si vous ajustez un peu les paramètres. Par exemple, nous indiquons que la minification doit être répétée cinq fois. Par défaut, il ne passe qu'une seule fois.

Taille en octets
Taille en octets (gzip)
Terser (par défaut) ES5
1 097 141
294 306
Terser (passe 5) ES5
1 089 312
292 408
Uglify (par défaut) ES5
1 091 350
294 845
Uglify (passe 5) ES5
1 050 363
284 618
Bottom line: Uglify avec une minification quintuple est inférieur à Uglify par défaut de 3,7% (3,4% gzip). Par conséquent, vous devez toujours resserrer les paramètres de compression. Soit dit en passant, une minification quintuple ne signifie pas que l'assemblage durera cinq fois plus longtemps. Par exemple, dans ce projet de test, une minification unique prend 18 secondes, cinq fois - 38 et dix fois - 49. Je recommande de rechercher expérimentalement la valeur idéale pour votre projet, après quoi la minification s'arrêtera et le code ne changera pas. Habituellement, c'est de 5 à 10. Il existe également un tas d'autres options: commentaires: faux supprime tous les commentaires sur les licences (bien que ce soit un problème juridique), et hoist_funs: true regroupe les fonctions en un seul endroit, ce qui permet une meilleure optimisation des variables. Idéalement, vous devez passer en revue tous les paramètres .

Qui produit un assemblage, un rollup ou un webpack plus petit?


Rollup est un collecteur alternatif avec un mécanisme d'agitation d'arbre intégré. Pour le test, j'ai fait une compilation sur Rollup 0.67.4 avec les mêmes paramètres que le webpack.

Taille en octets
Taille en octets (gzip)
Rollup ES5 (Uglify)
990 497
274 105
Rollup ES5 (Terser)
995 318
272 532
webpack ES5 (Uglify)
1 050 363
284 618
webpack ES5 (Terser)
1 089 312
292 408
Conclusion: le résultat de Rollup et Uglify est de 5,6% (3,6% gzip) de moins.

Cela s'est produit pour plusieurs raisons:

1. Webpack contient des béquilles pour les cas limites. Par exemple, ce code encapsule chaque appel de fonction d'un autre module dans Object (). Ceci est fait pour empêcher le transfert de contexte pour les modules sans utilisation stricte vers les modules avec utilisation stricte. Les projets bien écrits sans dépendances tierces n'ont pas besoin d'un wrapper, mais parfois non seulement du code bien écrit est impliqué dans l'assembly. Et à cet égard, webpack semble plus fiable. Rollap, à son tour, estime que tous les modules sont des modules ES6, et ils sont toujours exécutés en utilisation stricte, donc ce problème n'existe tout simplement pas pour lui.

Une question importante est de savoir comment ces béquilles Webpack affectent les performances. Imaginez que nous ayons écrit le code parfait qui n'a pas besoin d'encapsuleurs supplémentaires, mais quand même, chaque appel de fonction les traversera. Cela ajoute une petite surcharge de performances: environ un centième de microseconde par appel de fonction dans Chromium (un dixième dans Firefox).

2. Le webpack a un petit bootstrap qui contrôle l'initialisation et le chargement des modules. Le cumul n'utilise pas de wrappers, mais place simplement le code de tous les modules dans une seule étendue. Le webpack a une optimisation similaire, mais il ne fonctionne pas avec tous les modules.

Résumé de l'étude


J'espère que beaucoup, après avoir lu l'article, vérifieront leurs systèmes de construction et s'assureront qu'ils utilisent toutes les astuces possibles pour la meilleure compression. C'est simple et rapide.

Tout d'abord, configurez correctement un tas de TypeScript et Babel. Laissez chaque composant de l'assemblage faire sa propre chose: l'un vérifie les types et le second est responsable de la conversion en normes obsolètes.

Deuxièmement, lorsque vous utilisez ES5, vous pouvez redéfinir le minifieur sur UglifyJS, mais n'oubliez pas qu'il n'est plus pris en charge.

Troisièmement, il est préférable de choisir Rollup pour l'assemblage. Cependant, ce n'est pas toujours possible en raison du manque de certains plugins. Après assemblage, n'oubliez pas de vérifier la fonctionnalité par des tests fonctionnels. Si vous ne les avez pas - il est temps de commencer à les écrire.

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


All Articles