Octet par octet, JavaScript reste la ressource la plus chère que nous envoyions aux téléphones mobiles, car il peut retarder considérablement l’interactivité.
Selon Cloudfare, si nous examinons la taille totale de JavaScript demandée sur les appareils mobiles par HTTPArchive, nous constatons qu'une page moyenne charge 350 Ko de JavaScript, alors que 10% des pages dépassent le seuil de 1 Mo. La montée d'applications plus complexes peut pousser ces chiffres encore plus haut.
Bien que la mise en cache aide, les sites Web populaires publient régulièrement un nouveau code, ce qui rend les temps de démarrage à froid (premier chargement) particulièrement importants. Tandis que les navigateurs séparent les caches de différents domaines afin de prévenir les fuites entre sites, l'importance des démarrages à froid augmente, même pour les sous-ressources populaires servies à partir de CDN, car elles ne peuvent plus être partagées en toute sécurité.
Une brève enquête de juillet 2017 sur la taille de la charge utile JS non compressée pour certaines applications Web populaires sur les ordinateurs de bureau et mobiles
Habituellement, lorsque l’on parle de la performance de démarrage à froid, le facteur principal considéré est une vitesse de téléchargement brute. Cependant, sur les pages interactives modernes, l'un des autres contributeurs importants aux démarrages à froid est le temps d'analyse JavaScript. Cela peut paraître surprenant au début, mais cela a du sens - avant de commencer à exécuter le code, le moteur doit d’abord analyser le code JavaScript récupéré, s’assurer qu’il ne contient aucune erreur de syntaxe, puis le compiler dans le bytecode initial. À mesure que les réseaux deviennent plus rapides, l'analyse et la compilation de JavaScript pourraient devenir le facteur dominant.
La capacité du périphérique (performances de la CPU ou de la mémoire) est le facteur le plus important dans la variation des temps d'analyse JavaScript et, conséquemment, du temps nécessaire au démarrage de l'application. Un fichier JavaScript de 1 Mo nécessite un temps de l'ordre de 100 ms pour une analyse sur un ordinateur de bureau moderne ou un appareil mobile haut de gamme, mais peut prendre plus d'une seconde sur un téléphone moyen (Moto G4 par exemple).
Alors que les moteurs améliorent continuellement les performances d'analyse brute, qui ont été doublé l’année dernière avec V8, ainsi que le déplacement de tâches plus importantes du thread principal, les analyseurs syntaxiques doivent encore effectuer beaucoup de travail potentiellement inutile qui consomme de la mémoire, de la batterie et peut retarder le traitement des ressources utiles.
Goulots d'étranglement d'analyse repérés par le groupe de travail
Information non disponible en cas de besoin
Un problème fondamental qui ralentit l'analyse syntaxique est que les informations nécessaires pour prendre des décisions lors de l'analyse syntaxique ne sont souvent pas encore disponibles au point du flux d'entrée auquel elles sont nécessaires. Cela se produit lorsque l’analyseur a besoin d’informations provenant de code qu’il n’a pas encore analysé (hoisting de variable par exemple) ou de code qu’il ne souhaite pas analyser (comme les fonctions internes).
Un exemple concret est le problème de la représentation efficace des liaisons. Afin de prendre des décisions sur la manière de représenter ou d'allouer une variable, l'analyseur doit savoir si la variable est fermée, il devient donc nécessaire d'analyser les fonctions imbriquées. La spécification indique uniquement où une déclaration telle que var x doit être accessible, et non comment l'allouer - les informations nécessaires à la décision d'allocation ne sont pas codées là où la variable est déclarée, ni à l'endroit où elle doit être allouée.
Dans l'exemple suivant, à cause du hoisting de variable, use_x se ferme sur x, qui n'a pas été déclaré dans f au moment de l'analyse syntaxique de la fonction interne. Un moteur ne peut pas émettre des accès optimisés à x dans use_x jusqu'à ce que l'intégralité de f soit analysée.
Code JavaScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | var x; function f() { function use_x() { use(x); // Closes over hoisted var x. } var x; } |
Dans l'exemple suivant, l'interface d'un moteur souhaite savoir comment allouer efficacement de l'espace aux déclarations var au fur et à mesure de leur analyse. Ce n'est pas possible sans analyser le reste de la fonction.
Code JavaScript : | Sélectionner tout |
1 2 3 4 5 6 | function g() { var x; // Not closed over, should go on function frame. var y; // Closed over, should go on activation object. return (function() { use(y); }); } |
Dans l'exemple suivant, la présence de eval au bas de la fonction est inconnue au moment où le moteur doit décider si l'accès à var x peut être effectué de manière dynamique.
Code JavaScript : | Sélectionner tout |
1 2 3 4 5 6 7 | function h(input) { var x; (function() { eval(input); })(); } |
Sémantique précoce des erreurs
La sémantique précoce des erreurs de JavaScript nécessite l’analyse complète de tous les fichiers. Les moteurs utilisent le lazy parsing (également appelée pré-analyse) qui évite de créer un AST complet en ignorant la génération de code initiale des fonctions internes jusqu'au moment de la première invocation. C'est en moyenne 50% plus rapide, et l'effort d'analyse est toujours proportionnel à la taille du fichier. Toutefois, il y a un revers à la médaille : si une fonction interne dont la génération de code a été ignorée est appelée lors du démarrage de l'application, le moteur doit alors analyser à nouveau la totalité de la fonction.
Considérez ce qui suit. Actuellement, étant donné que innerWithSyntaxError a une erreur précoce, outer doit également renvoyer l'erreur précoce.
Code JavaScript : | Sélectionner tout |
1 2 3 4 5 | function outer() { function innerWithSyntaxError() { var; } } |
Le changement de comportement proposé est analogue au wrapping du corps de fonction avec eval, comme indiqué ci-dessous.
Code JavaScript : | Sélectionner tout |
1 2 3 4 5 | function outer() { function innerWithSyntaxError() { eval("var"); } } |
La proposition "BinaryAST"
C'est ici qu'intervient BinaryAST. BinaryAST est un nouveau format over-the-wire pour JavaScript proposé et développé activement par Mozilla, qui vise à accélérer l'analyse tout en préservant la sémantique du code JavaScript d'origine. Pour ce faire, il utilise une représentation binaire efficace pour le code et les structures de données, ainsi que le stockage et la fourniture d'informations supplémentaires pour guider l'analyseur à l'avance.
Le nom vient du fait que le format stocke la source JavaScript au format AST encodé dans un fichier binaire.
Le groupe de travail propose à ECMAScript un codage binaire basé sur une représentation efficace par arborescence de syntaxe abstraite de la syntaxe JavaScript. Il s'agit d'un nouveau codage alternatif de la syntaxe de surface, avec un mappage bidirectionnel assez proche de la représentation textuelle.
N'oubliez pas que cette proposition en est à ses débuts et que les tests de performance et les démonstrations actuels ne sont pas représentatifs du résultat final (qui est susceptible de s’améliorer dans le temps).
Voici une vidéo qui vous donnera une idée de l'amélioration vue par un utilisateur de Firefox sur mobile (dans ce cas, affichant le temps de démarrage complet de la page):
« S'assurer que les applications Web démarrent rapidement est l'un des aspects les plus importants, mais aussi l'un des plus difficiles du développement Web. Nous savons que BinaryAST peut réduire radicalement le temps de démarrage, mais nous devons collecter des données réelles pour démontrer leur impact. Les travaux de Cloudflare sur l'activation de l'utilisation de BinaryAST avec Cloudflare Workers constituent une étape importante dans la collecte de ces données à grande échelle », a déclaré Till Schneidereit, Senior Engineering Manager, Developer Technologies chez Mozilla.
Sources : vue d'ensemble de la proposition Binary AST, Cloudfare, ingénieur Google, Mozilla (hoisting)
Et vous ?
Que pensez-vous de cette proposition ?
En temps que développeur web, comment optimisez-vous votre code JavaScript ?
Voir aussi :
jQuery 3.4.0 est disponible, la bibliothèque JavaScript est-elle toujours aussi utile et incontournable comme à ses débuts ?
WebStorm 2019.1 disponible : tour d'horizon des nouveautés de l'EDI de JetBrains pour les développeurs JavaScript
La Linux Foundation annonce OpenJS Foundation, issue de la fusion des fondations Node.js et JS, pour soutenir la croissance de JavaScript
CSS s'enrichit du support des fonctions trigonométriques jusqu'ici accessibles via JavaScript après l'approbation du World Wide Web Consortium