Lorsque nous créons des sites qui dépendent davantage de JavaScript, nous payons parfois pour ce que nous envoyons d'une manière que nous ne pouvons pas toujours voir facilement. Dans cet article, nous allons vous expliquer pourquoi un peu de discipline peut vous aider si vous souhaitez que votre site se charge et soit interactif rapidement sur les appareils mobiles. Fournir moins de code JavaScript peut signifier moins de temps de transmission réseau, moins de temps passé à décompresser le code, et moins de temps d'analyse et de compilation de ce code JavaScript.
Réseau
Lorsque la plupart des développeurs pensent au coût de JavaScript, ils pensent au coût de téléchargement et d'exécution. L'envoi d'un plus grand nombre d'octets de JavaScript sur le réseau prend plus de temps si la connexion de l'utilisateur est lente.

Cela peut poser problème, car le type de connexion réseau effectif d'un utilisateur n'est pas forcément 3G, 4G ou Wi-Fi. Vous pouvez être connecté au Wi-Fi d'un café, mais aussi à un point d'accès mobile avec un débit 2G.
Vous pouvez réduire le coût de transfert réseau de JavaScript de différentes manières :
- Envoi du code dont l'utilisateur a besoin uniquement.
- Utilisez la division du code pour séparer le code JavaScript critique du code non critique. Les empaqueteurs de modules comme webpack sont compatibles avec la division du code.
- Chargement différé du code non critique.
- Minimisation
- Utilisez UglifyJS pour minimiser le code ES5.
- Utilisez babel-minify ou uglify-es pour minimiser ES2015+.
- Compression
- Supprimer le code inutilisé
- Identifiez les opportunités de code pouvant être supprimé ou chargé de manière différée avec la couverture de code des outils de développement.
- Utilisez babel-preset-env et browserlist pour éviter de transpiler des fonctionnalités déjà présentes dans les navigateurs récents. Les développeurs avancés peuvent trouver qu'une analyse minutieuse de leurs bundles webpack permet d'identifier les opportunités de supprimer les dépendances inutiles.
- Pour supprimer du code, consultez les sections tree-shaking, Closure Compiler (optimisations avancées) et les plug-ins de suppression de bibliothèque tels que lodash-babel-plugin ou ContextReplacementPlugin de webpack pour les bibliothèques telles que Moment.js.
- Mise en cache du code pour minimiser les trajets réseau
- Utilisez la mise en cache HTTP pour vous assurer que les navigateurs mettent en cache les réponses de manière efficace. Déterminez les durées de vie optimales des scripts (max-age) et fournissez des jetons de validation (ETag) pour éviter de transférer des octets inchangés.
- La mise en cache des service workers peut rendre votre application résiliente au réseau et vous donner un accès rapide à des fonctionnalités telles que le cache de code de V8.
- Utilisez la mise en cache à long terme pour éviter d'avoir à récupérer à nouveau des ressources qui n'ont pas changé. Si vous utilisez Webpack, consultez filename hashing (hachage de nom de fichier).
Analyser/compiler
Une fois téléchargé, l'un des coûts les plus lourds de JavaScript est le temps nécessaire à un moteur JS pour analyser/compiler ce code. Dans les outils pour les développeurs Chrome, l'analyse et la compilation font partie du temps de "scripting" jaune dans le panneau "Performances".

Les onglets "Bottom-Up" (Ascendant) et "Call Tree" (Arborescence des appels) indiquent les temps d'analyse/de compilation exacts :
Mais pourquoi est-ce important ?

Si l'analyse/la compilation du code prend beaucoup de temps, cela peut retarder considérablement le moment où un utilisateur peut interagir avec votre site. Plus vous envoyez de code JavaScript, plus l'analyse et la compilation prendront du temps avant que votre site ne devienne interactif.
Octet par octet, le traitement du JavaScript est plus coûteux pour le navigateur que celui d'une image ou d'une police Web de taille équivalente – Tom Dale
Par rapport à JavaScript, le traitement d'images de taille équivalente implique de nombreux coûts (elles doivent toujours être décodées !), mais sur un matériel mobile moyen, JS est plus susceptible d'avoir un impact négatif sur l'interactivité d'une page.
Lorsque nous parlons de lenteur d'analyse et de compilation, le contexte est important : nous parlons ici de téléphones mobiles moyens. Les utilisateurs moyens peuvent avoir des téléphones équipés de processeurs et de GPU lents, sans cache L2/L3, et qui peuvent même être limités en mémoire.
Les capacités du réseau et celles de l'appareil ne correspondent pas toujours. Un utilisateur disposant d'une excellente connexion Fiber n'a pas nécessairement le meilleur processeur pour analyser et évaluer le code JavaScript envoyé à son appareil. L'inverse est également vrai : une connexion réseau terrible, mais un processeur ultra-rapide. — Kristofer Baxter, LinkedIn
Vous trouverez ci-dessous le coût de l'analyse d'environ 1 Mo de code JavaScript décompressé (simple) sur du matériel bas de gamme et haut de gamme. Le temps d'analyse/de compilation du code est deux à cinq fois plus long sur les téléphones moyens que sur les téléphones les plus rapides du marché.
Qu'en est-il d'un site réel, comme CNN.com ?
Sur l'iPhone 8 haut de gamme, l'analyse et la compilation du code JS de CNN ne prennent que 4 secondes environ, contre 13 secondes environ pour un téléphone moyen (Moto G4). Cela peut avoir un impact significatif sur la rapidité avec laquelle un utilisateur peut interagir pleinement avec ce site.
Cela souligne l'importance de tester sur du matériel moyen (comme le Moto G4) plutôt que sur le téléphone que vous avez dans votre poche. Toutefois, le contexte est important : optimisez votre site en fonction de l'appareil et des conditions réseau de vos utilisateurs.
Envoyons-nous vraiment trop de JavaScript ? Euh, peut-être :)
En utilisant HTTP Archive (les 500 000 sites les plus populaires) pour analyser l'état de JavaScript sur mobile, nous pouvons constater que 50 % des sites mettent plus de 14 secondes à devenir interactifs. Ces sites passent jusqu'à quatre secondes à analyser et compiler le code JS.

Si l'on tient compte du temps nécessaire pour récupérer et traiter le code JS et d'autres ressources, il n'est peut-être pas surprenant que les utilisateurs doivent attendre un certain temps avant de sentir que les pages sont prêtes à être utilisées. Nous pouvons certainement faire mieux ici.
Supprimer le code JavaScript non critique de vos pages peut réduire les temps de transmission, l'analyse et la compilation gourmandes en ressources du processeur, ainsi que la surcharge de mémoire potentielle. Cela permet également de rendre vos pages interactives plus rapidement.
Durée d'exécution
L'analyse et la compilation ne sont pas les seules à avoir un coût. L'exécution JavaScript (exécution du code une fois analysé/compilé) est l'une des opérations qui doivent se produire sur le thread principal. Des temps d'exécution longs peuvent également retarder le moment où un utilisateur peut interagir avec votre site.

Si l'exécution du script prend plus de 50 ms, le délai avant interactivité est retardé de l'intégralité du temps nécessaire au téléchargement, à la compilation et à l'exécution du code JS. – Alex Russell
Pour résoudre ce problème, JavaScript peut être divisé en petits fragments afin d'éviter de bloquer le thread principal. Déterminez si vous pouvez réduire la quantité de travail effectuée pendant l'exécution.
Autres frais
JavaScript peut avoir un impact sur les performances de la page d'autres manières :
- Mémoire. Les pages peuvent sembler saccadées ou s'interrompre fréquemment en raison de la récupération de mémoire. Lorsqu'un navigateur récupère de la mémoire, l'exécution de JS est mise en pause. Par conséquent, un navigateur qui récupère fréquemment de la mémoire peut mettre en pause l'exécution plus souvent que nous le souhaiterions. Évitez les fuites de mémoire et les pauses fréquentes de récupération de mémoire pour éviter les à-coups sur les pages.
- Pendant l'exécution, le code JavaScript de longue durée peut bloquer le thread principal, ce qui entraîne l'absence de réponse des pages. La division du travail en plus petits morceaux (à l'aide de
requestAnimationFrame()ou derequestIdleCallback()pour la planification) peut minimiser les problèmes de réactivité, ce qui peut contribuer à améliorer l'Interaction to Next Paint (INP).
Modèles pour réduire le coût de diffusion de JavaScript
Lorsque vous essayez de maintenir des temps d'analyse/de compilation et de transmission réseau lents pour JavaScript, certains modèles peuvent vous aider, comme le chunking basé sur les routes ou PRPL.
PRPL
PRPL (Push, Render, Pre-cache, Lazy-load) est un modèle qui optimise l'interactivité grâce à une division agressive du code et à une mise en cache préalable :

Voyons l'impact que cela peut avoir.
Nous analysons le temps de chargement des sites mobiles populaires et des progressive web apps à l'aide des statistiques d'appel d'exécution de V8. Comme nous pouvons le voir, le temps d'analyse (indiqué en orange) représente une part importante du temps passé par un grand nombre de ces sites :

Wego, un site qui utilise PRPL, parvient à maintenir un temps d'analyse faible pour ses routes, ce qui permet d'obtenir une interactivité très rapidement. De nombreux autres sites ci-dessus ont adopté la division du code et les budgets de performances pour essayer de réduire leurs coûts JS.
Bootstrapping progressif
De nombreux sites optimisent la visibilité du contenu au détriment de l'interactivité. Pour obtenir un premier rendu rapide lorsque vous disposez de bundles JavaScript volumineux, les développeurs utilisent parfois le rendu côté serveur, puis le "mettent à niveau" pour associer des gestionnaires d'événements lorsque le code JavaScript est enfin récupéré.
Attention, cela a un coût. 1) Vous envoyez généralement une réponse HTML plus volumineuse qui peut retarder l'interactivité, 2) vous pouvez laisser l'utilisateur dans une situation étrange où la moitié de l'expérience ne peut pas être interactive tant que le traitement JavaScript n'est pas terminé.
Le bootstrapping progressif peut être une meilleure approche. Envoyez une page fonctionnelle minimale (composée uniquement du code HTML/JS/CSS nécessaire pour l'itinéraire actuel). À mesure que d'autres ressources arrivent, l'application peut charger de manière différée et débloquer d'autres fonctionnalités.
Le chargement du code proportionnel à ce qui est affiché est le Saint Graal. PRPL et l'amorçage progressif sont des modèles qui peuvent vous aider à y parvenir.
Conclusions
La taille de transmission est essentielle pour les réseaux bas de gamme. Le temps d'analyse est important pour les appareils liés au processeur. Il est important de maintenir ces valeurs à un niveau faible.
Les équipes ont réussi à adopter des budgets de performances stricts pour maintenir des temps de transmission et d'analyse/compilation JavaScript faibles. Consultez l'article d'Alex Russell intitulé Can You Afford It?: Real-world Web Performance Budgets" (en anglais) pour obtenir des conseils sur les budgets pour mobile.
Si vous créez un site ciblant les appareils mobiles, faites de votre mieux pour développer sur du matériel représentatif, maintenez des temps d'analyse/de compilation JavaScript faibles et adoptez un budget de performances pour vous assurer que votre équipe peut garder un œil sur ses coûts JavaScript.
En savoir plus
- Chrome Dev Summit 2017 – Bonnes pratiques pour un chargement moderne
- Performances de démarrage JavaScript
- Solving the web performance crisis — Nolan Lawson
- Pouvez-vous vous le permettre ? Budgets de performances réels — Alex Russell
- Évaluation des frameworks et des bibliothèques Web – Kristofer Baxter
- Résultats de l'expérimentation de Brotli par Cloudflare pour la compression (notez que Brotli dynamique à une qualité supérieure peut retarder l'affichage initial de la page, alors évaluez-le attentivement. Vous préférerez probablement utiliser la compression statique.)
- Performance Futures — Sam Saccone