Développement JavaScript responsable Partie 2

En avril de cette année, nous avons publié une traduction du premier matériel d'une série consacrée à une approche responsable du développement JavaScript. Là, l'auteur a réfléchi sur les technologies Web modernes et leur utilisation rationnelle. Nous vous proposons maintenant une traduction du deuxième article de cette série. Il est consacré à quelques détails techniques concernant la conception de projets web.



Avoir une idée


Vous et votre équipe avez promu avec enthousiasme l'idée d'une refonte complète du site Web vieillissant de l'entreprise. Vos demandes ont atteint la direction, sont même venues aux yeux des plus hauts dirigeants. On vous a donné un feu vert. Votre équipe s'est mise à travailler avec enthousiasme, attirant des designers, des rédacteurs et d'autres spécialistes. Bientôt, vous avez déployé un nouveau code.

Les travaux ont commencé innocemment. La commande d' npm install est ici, la commande d' npm install est là. Dès que vous avez regardé autour de vous, les dépendances de production s'étaient déjà établies comme si le développement du projet était un alcool sauvage, et c'est vous qui ne vous souciez pas du tout de ce qui sera demain matin.

Alors tu as commencé.

Mais, contrairement aux conséquences de la fête la plus folle, la chose terrible n'a pas commencé le lendemain matin. Malheureusement - pas le lendemain matin. Le calcul est venu en quelques mois. Elle a pris la forme désagréable de nausées légères et de maux de tête pour les propriétaires d'entreprise et les cadres intermédiaires qui se demandaient pourquoi, après le lancement du nouveau site, les conversions et les revenus avaient chuté. Puis la catastrophe a pris de l'ampleur. Cela s'est produit lorsque le directeur technique est revenu du week-end, qu'il a passé quelque part en dehors de la ville. Il se demandait pourquoi le site Web de l'entreprise se chargeait si lentement (voire pas du tout) sur son téléphone.

Avant, c'était bon pour tout le monde. Maintenant, d'autres temps sombres sont venus. Rencontrez votre première gueule de bois après avoir consommé une grande dose de JavaScript.

Ce n'est pas de ta faute.


Pendant que vous tentiez de faire face à une gueule de bois infernale, des mots comme «Je vous l'ai dit» vous sembleraient une réprimande bien méritée. Et si vous pouviez vous battre à ce moment-là, ils pourraient servir d’occasion à un combat.

En ce qui concerne les conséquences d'une utilisation imprudente de JavaScript, vous pouvez blâmer tout et tout le monde. Mais rechercher les coupables est une perte de temps. L'appareil Web moderne lui-même oblige les entreprises à résoudre les problèmes plus rapidement que leurs concurrents. Une telle pression signifie que nous, cherchant à augmenter notre productivité autant que possible, sommes susceptibles de saisir quoi que ce soit. Cela signifie que nous, avec un degré de probabilité élevé (bien que cela ne puisse pas être qualifié d'inévitable), créerons des applications dans lesquelles il y aura de nombreux excès et, très probablement, nous utiliserons des modèles qui nuisent aux performances et à la disponibilité des applications.

Le développement Web n'est pas une tâche facile. C'est un long travail. Il est rarement bien exécuté du premier coup. La meilleure chose à propos de ce travail, cependant, est que nous ne devons pas tout faire parfaitement au tout début. Nous pouvons apporter des améliorations aux projets après leur lancement, et, en fait, ce matériel y est consacré, le deuxième d'une série d'articles sur une approche responsable du développement JS. La perfection est un objectif très éloigné. En attendant, traitons de la gueule de bois JavaScript en améliorant, pour ainsi dire, les scripts
sur le site dans un avenir proche.

Nous traitons des problèmes communs


Cela peut sembler une approche mécanique pour résoudre des problèmes, mais commencez par parcourir une liste de problèmes typiques et les moyens de les résoudre. Dans les grandes équipes de développement, ces choses sont souvent oubliées. Cela est particulièrement vrai pour les équipes qui travaillent avec plusieurs référentiels ou n'utilisent pas de modèle optimisé pour leurs projets.

▍ Appliquer l'algorithme de tremblement d'arbre


Tout d'abord, vérifiez si vos outils sont configurés pour implémenter l'algorithme d' agitation d'arbre . Si vous n'avez jamais rencontré ce concept auparavant, jetez un œil à mon matériel écrit l'an dernier. Si nous expliquons le fonctionnement de cet algorithme en un mot, nous pouvons dire qu'en raison de son utilisation dans les assemblages de production de l'application, n'incluent pas les packages qui, bien qu'importés dans le projet, n'y sont pas utilisés.

La mise en œuvre de l'algorithme d'agitation d'arbre est une fonctionnalité standard des bundlers modernes tels que webpack , Rollup ou Parcel . Grunt ou gulp sont des gestionnaires de tâches. Ils ne font pas ça. Contrairement au bundler, le gestionnaire de tâches ne crée pas de graphique de dépendance . Le gestionnaire de tâches est engagé, à l'aide des plug-ins nécessaires, effectuant des manipulations distinctes sur les fichiers qui lui sont transférés. Les fonctionnalités des gestionnaires de tâches peuvent être étendues à l'aide de plugins, ce qui leur permet de traiter JavaScript à l'aide de bundlers. Si l'extension des capacités du gestionnaire de tâches dans cette direction semble être un problème, vous devez probablement vérifier manuellement la base de code et en supprimer le code inutilisé.

Pour que l'algorithme d'agitation d'arbre fonctionne efficacement, les conditions suivantes doivent être remplies:

  1. Le code d'application et les packages installés doivent être présentés sous forme de modules ES6 . L'utilisation de l'algorithme d'agitation d'arbre pour les modules CommonJS est presque impossible.
  2. Votre bundler ne doit pas transformer les modules ES6 en modules d'un autre format lors de la construction du projet. Si cela se produit dans la chaîne d'outils qui utilise Babel, alors @ Babel / present-env doit avoir les modules: false setting. Cela entraînera la conversion du code ES6 en code qui utilise CommonJS.

Si soudainement, lors de la construction de votre projet, l'algorithme de tremblement d'arbre n'est pas appliqué, l'inclusion de ce mécanisme peut améliorer la situation. Bien entendu, l'efficacité de cet algorithme varie d'un projet à l'autre. De plus, la possibilité de son utilisation dépend si les modules importés ont des effets secondaires . Cela peut affecter la capacité du bundler à se débarrasser de l'inclusion de modules importés inutiles dans l'assemblage.

▍Divisez le code en parties


Il est très probable que vous utilisez déjà une forme de séparation de code . Cependant, vous devez vérifier comment cela se fait. Quelle que soit la façon dont vous séparez le code, je veux vous proposer de vous poser les deux questions très précieuses suivantes:

  1. Supprimez-vous le code en double des points d'entrée ?
  2. Chargez-vous paresseusement tout ce qui peut être chargé de cette manière avec des importations dynamiques ?

Ces problèmes sont importants car la réduction de la quantité de code redondant est un élément de performance crucial. Le chargement tardif du code améliore également les performances en réduisant la quantité de code JavaScript qui fait partie de la page et se charge lors du chargement. Si nous parlons de l'analyse du projet pour la présence de code redondant, alors pour cela, vous pouvez utiliser une sorte d'outil comme le Bundle Buddy. Si votre projet a un problème avec cela, cet outil vous en informera.


L'outil Bundle Buddy peut vérifier les informations de compilation du webpack et découvrir combien du même code est utilisé dans vos bundles

Si nous parlons de chargement paresseux de matériaux, il peut être difficile de trouver où chercher des opportunités pour appliquer cette optimisation. Lorsque je recherche un projet existant pour la possibilité d'utiliser le chargement paresseux, je recherche dans la base de code les endroits qui impliquent des interactions des utilisateurs avec le code. Il peut s'agir, par exemple, de gestionnaires d'événements de souris ou de clavier, ainsi que d'autres éléments similaires. Tout code qui nécessite une action utilisateur pour s'exécuter est un bon candidat pour lui appliquer la commande import() dynamique import() .

Bien sûr, le chargement de scripts à la demande comporte un risque de retards notables dans le passage du système en mode interactif. En effet, avant que le programme puisse interagir avec l'utilisateur de manière interactive, vous devez télécharger le script approprié. Si la quantité de données transférées ne vous dérange pas, envisagez d'utiliser l'indicateur de ressource rel = prefetch pour charger ces scripts de faible priorité. Ces ressources ne rivaliseront pas pour la bande passante avec les ressources critiques. Si le navigateur de l'utilisateur prend en charge rel=prefetch , l'utilisation de cette info-bulle ne sera que bénéfique. Sinon, rien de mauvais ne se produira, car les navigateurs ignorent simplement le balisage qu'ils ne comprennent pas.

▍Utilisez l'option webpack externals pour marquer les ressources situées sur des serveurs étrangers


Idéalement, vous devez héberger autant de dépendances que possible de votre site sur vos propres serveurs. Si, pour une raison quelconque, vous devez, sans options, télécharger des dépendances à partir des serveurs d'autres personnes - placez-les dans le bloc externe dans les paramètres du webpack. Si cela n'est pas fait, cela peut signifier que les visiteurs de votre site téléchargeront à la fois le code que vous hébergez et le même code à partir des serveurs d'autres personnes.

Jetez un œil à une situation hypothétique dans laquelle quelque chose comme cela pourrait nuire à votre ressource. Supposons que votre site télécharge la bibliothèque Lodash à partir d'une ressource CDN publique. Vous avez également installé Lodash dans le projet à des fins de développement local. Cependant, si vous ne spécifiez pas dans les paramètres du webpack que Lodash est une dépendance externe, votre code de production chargera la bibliothèque à partir du CDN, mais en même temps, il sera inclus dans le bundle hébergé sur votre serveur.

Si vous connaissez bien les bundlers, alors tout cela peut vous sembler des vérités banales. Mais j'ai vu comment ces choses ne font pas attention. Par conséquent, ne prenez pas le temps de revérifier votre projet pour les problèmes ci-dessus.

Si vous ne jugez pas nécessaire d'héberger vous-même vos dépendances créées par des développeurs tiers, alors envisagez d'utiliser dns-prefetch , preconnect , ou peut-être même de précharger des indices avec eux. Cela peut réduire le score TTI (Time To Interactive, temps du site à la première interactivité). Et si des capacités JavaScript sont requises pour afficher le contenu du site, alors l' indice de vitesse du site est également nécessaire.

Bibliothèques alternatives plus petites et frais généraux réduits sur les systèmes utilisateur


Ce qu'on appelle « Userland JavaScript » (bibliothèques JS développées par l'utilisateur) semble être un énorme magasin de bonbons obscène. Toute cette magnificence et cette variété open source nous inspirent, les développeurs, avec admiration. Les cadres et les bibliothèques nous permettent d'étendre nos applications, en les équipant rapidement de fonctionnalités qui aident à résoudre une variété de problèmes. Si nous devions implémenter nous-mêmes la même fonctionnalité, cela prendrait beaucoup de temps et d'énergie.

Bien que je préconise personnellement une minimisation agressive de l'utilisation des frameworks clients et des bibliothèques dans mes projets, je ne peux que reconnaître leur énorme valeur et utilité. Mais, malgré cela, lorsqu'il s'agit d'installer de nouvelles dépendances dans le projet, nous devons traiter chacune d'elles avec beaucoup de suspicion. Si nous avons déjà créé et lancé quelque chose, dont le fonctionnement dépend de nombreuses dépendances installées, cela signifie que nous supportons la charge supplémentaire sur le système que tout cela crée. Il est probable que seuls les développeurs de packages peuvent résoudre ce problème en optimisant leur développement. En est-il ainsi?

Peut-être en est-il ainsi, mais peut-être pas. Cela dépend des dépendances utilisées. Par exemple, React est une bibliothèque extrêmement populaire. Mais Preact est une très petite alternative à React, qui donne au développeur presque les mêmes API et reste compatible avec de nombreux modules complémentaires React. Luxon et date-fns sont des alternatives à moment.js , beaucoup plus compact que cette bibliothèque, qui n'est pas si petite .

Dans des bibliothèques comme Lodash, vous pouvez trouver de nombreuses méthodes utiles. Mais certains d'entre eux sont faciles à remplacer par des méthodes ES6 standard. Par exemple, la méthode compacte Lodash peut être remplacée par la méthode des matrices de filtres standard. De nombreuses autres méthodes Lodash peuvent également être remplacées en toute sécurité par des méthodes standard. L'avantage de ce remplacement est que nous obtenons les mêmes fonctionnalités que l'utilisation de la bibliothèque, mais que nous nous débarrassons d'une dépendance assez importante.

Quoi que vous utilisiez, l'idée générale reste la même: demandez si votre choix a des alternatives plus compactes. Découvrez si vous pouvez résoudre les mêmes problèmes avec des outils de langage standard. Peut-être serez-vous agréablement surpris par le peu que vous avez à faire des efforts pour réduire sérieusement la taille de l'application et la quantité de charge inutile qu'elle exerce sur les systèmes utilisateur.

Utiliser des technologies de chargement différentiel de scripts


Il y a de fortes chances que Babel soit dans votre chaîne d'outils. Cet outil est utilisé pour transformer le code source compatible ES6 en code que les navigateurs hérités peuvent exécuter. Cela signifie-t-il que nous sommes condamnés à offrir d'énormes offres, même aux navigateurs qui n'en ont pas besoin, jusqu'à ce que tous les anciens navigateurs disparaissent? Bien sûr que non ! Le chargement différentiel des ressources permet de contourner ce problème en créant deux assemblys différents basés sur le code ES6:

  • Le premier assemblage comprend toutes les conversions de code et le polyfill requis pour que votre site fonctionne dans les navigateurs hérités. Vous offrez probablement maintenant aux clients cet assemblage particulier.
  • Le deuxième assemblage contient un minimum de conversions de code et de polyfill, ou s'en passe du tout. Il est conçu pour les navigateurs modernes. Il s'agit d'un assemblage que vous pourriez ne pas avoir. Du moins pas encore.

Pour utiliser la technologie de chargement différentiel des assemblages, il faut travailler un peu. Je n'entrerai pas dans les détails ici - je donnerai un meilleur lien vers mon matériel, qui discute d'une des façons de mettre en œuvre cette technologie. L'essence de tout cela est que vous pouvez modifier la configuration de votre build afin que lors de la construction du projet, une version supplémentaire du bundle JS de votre site soit créée. Ce pack supplémentaire sera plus petit que le pack principal. Il sera destiné uniquement aux navigateurs modernes. La meilleure partie est que cette approche vous permet d’optimiser la taille du bundle et en même temps de ne rien sacrifier aux capacités du projet. Selon le code de l'application, les économies sur la taille du bundle peuvent être assez importantes.


Analyse des bundles pour les anciens navigateurs (à gauche) et des bundles pour les nouveaux navigateurs (à droite). L'étude de faisceaux a été réalisée à l'aide de webpack-bundle-analyzer. Voici la version pleine grandeur de cette image.

Il est plus facile de donner différents packs à différents navigateurs en utilisant l'astuce suivante. Cela fonctionne bien dans les navigateurs modernes:

 <!--     : --> <script type="module" src="/js/app.mjs"></script> <!--     : --> <script defer nomodule src="/js/app.js"></script> 

Malheureusement, cette approche présente des inconvénients. Des navigateurs obsolètes comme IE11, et même des navigateurs relativement modernes tels que les versions Edge 15-18, chargeront les deux bundles. Si vous êtes prêt à le supporter, utilisez cette technique et ne vous inquiétez de rien.

D'autre part, vous devez trouver quelque chose au cas où vous seriez préoccupé par l' impact sur les performances de votre application du fait que les anciens navigateurs doivent télécharger les deux bundles. Voici une solution potentielle à ce problème qui utilise l'injection de script (au lieu de la <script> nous avons utilisée ci-dessus). Il évite le double chargement des bundles par les navigateurs appropriés. Voici de quoi nous parlons:

 var scriptEl = document.createElement("script"); if ("noModule" in scriptEl) {  //     scriptEl.src = "/js/app.mjs";  scriptEl.type = "module"; } else {  //     scriptEl.src = "/js/app.js";  scriptEl.defer = true; // type="module"   ,    . } //  ! document.body.appendChild(scriptEl); 

Ce script suppose que si le navigateur prend en charge l'attribut nomodule dans l'élément de script , il comprend la construction type="module" . Cela garantit que les navigateurs hérités ne recevront que des scripts conçus pour eux, et les navigateurs modernes recevront des scripts conçus pour eux. Cependant, gardez à l'esprit que les scripts intégrés dynamiquement sont chargés de manière asynchrone par défaut. Par conséquent, si l'ordre de chargement des dépendances est important pour vous, définissez l'attribut async sur false .

Transmettre moins


Je ne vais pas attaquer Babel ici. Cet outil est nécessaire dans le développement Web moderne, mais c'est une entité très capricieuse. Babel ajoute beaucoup de choses au code qu'il génère, que le développeur peut ne pas connaître. Par conséquent, vous ne regretterez pas si vous regardez dans les entrailles de Babel et découvrez exactement ce qu'il fait. En particulier, la connaissance des mécanismes internes de Babel montre clairement que de petits changements dans la façon dont quelqu'un écrit du code peuvent avoir un effet positif sur ce que Babel génère.


Transmettre moins

À savoir - c'est ce dont nous parlons. Par exemple, les options par défaut sont une fonctionnalité très pratique d'ES6 que vous utilisez peut-être déjà:

 function logger(message, level = "log") {  console[level](message); } 

Ici, il convient de prêter attention au paramètre de level , dont la valeur par défaut est le log chaîne. Cela signifie que si nous voulons appeler console.log à l'aide de la fonction wrapper de l' logger , nous n'avons pas besoin de passer le level à cette fonction. Pratique, non? Tout cela est bien - sauf pour le code que Babel obtient lors de la transformation de cette fonction:

 function logger(message) {  var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "log";  console[level](message); } 

Ceci est un exemple de la façon dont, malgré le fait que nous soyons dirigés par de bonnes intentions, le confort que Babel donne peut avoir des conséquences négatives. Ce qui n'était que quelques caractères dans le code source s'est transformé en une construction beaucoup plus longue dans la version de production du programme. - , , arguments .

, , ? , Babel :

 //   function logger(...args) {  const [level, message] = args;  console[level](message); } //   Babel function logger() {  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {    args[_key] = arguments[_key];  }  const level = args[0],        message = args[1];  console[level](message); } 

, Babel @babel/preset-env , . , , , , , ! — «» ( true loose ). — , , , , , . «» , Babel , .

, «» , , Babel :

 // Babel      function logger(message, level) {  console[level || "log"](message); } 

, — JavaScript, , . spread , , .

— :

  1. @babel/runtime @babel/plugin-transform-runtime , , Babel .
  2. , . @babel/polyfill . , babel /preset-env useBuiltins usage .

, , , , , . , JSX , , , . , , . , , . , Babel — . , Babel. , .

: —


, . , JavaScript-, , . , , . - . . , , , , , , , .

, , , , , , , . - — . , , , . . , , , , . , , , .

Chers lecteurs! JS-?

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


All Articles