L’intégration des Web Vitals dans l’approche du positionnement du moteur de recherche de Google soulève beaucoup de questions. Parmi les 3 indicateurs en question, le FID concentre toutes les attentions car il est plus complexe à mesurer, interpréter et améliorer. Alors même que le TBT est plus simple à appréhender pour améliorer le FID, il existe des techniques pour répondre plus rapidement à une interaction de l’utilisateur en utilisant un système de queue ou autrement nommé un ordonnanceur JavaScript.
Un seul fil d’exécution : le thread principal
La plupart des tâches exécutées en JavaScript sont effectuées sur un seul thread : le thread principal. Cela permet aux développeurs d’avoir un modèle d’exécution robuste mais au détriment de l’expérience utilisateur. En effet, si la page web effectue beaucoup de tâches JavaScript au chargement de la page, alors qu’une interaction utilisateur est déclenchée via un évènement, au clic par exemple ; la page ne gérera pas l’événement du clic tant que les tâches précédentes ne seront pas terminées.
Le temps cumulé durant lequel une page est dans l’incapacité de répondre efficacement à une interaction utilisateur est justement le fameux Total Blocking Time (TBT). Pour être précis ce TBT est calculé à partir de la somme des temps alloués aux tâches longues correspondant à un temps de plus de 50 millisecondes. Or, n’oubliez pas l’objectif : réduire le FID qui selon les recommandations de Google doit être en dessous de 100 millisecondes.
Des tâches plus courtes et séparées par des intervalles
Pour résoudre le problème, le premier réflexe est de diviser le JavaScript en blocs plus petits (cf figure 2), et de les exécuter à intervalle régulier (avec un setTimeout()
par exemple) pour qu’à chaque intervalle le navigateur puisse répondre aux interactions (cf figure 3).
NB : même si le setTimeout()
est à 0ms, il faut un certain temps au navigateur pour vérifier sa file d’attente d’interaction, traiter les événements et sélectionner la tâche JavaScript suivante. D’où l’espace de temps entre chaque tâche JavaScript (cf figure 3 : le temps de 10ms alloué à cet intervalle sur ce schéma est une valeur à titre d’exemple. Cet intervalle diffère selon l’appareil et le navigateur). Dès lors, le navigateur répond plus rapidement aux événements, mais le temps de chargement global de la page est ralenti.
Créer un ordonnanceur JavaScript
Nate Schloss et Andrew Comminos sur web.dev nous décrivent un principe d’ordonnanceur JavaScript. Le principe est très astucieux… Au lieu d’appeler les tâches les unes derrières les autres, un ordonnanceur va les répertorier dans une fonction “todolist”.
Celle-ci va exécuter les différentes tâches dans l’ordre et va effectuer un test avant de lancer la tâche suivante de la todolist. En effet, la fonction va vérifier qu’un certain délai n’est pas encore écoulé :
- Si non il lance la tâche suivante.
- Si oui, il arrête l’exécution et se relance avec un setTimeout.
En s’interrompant ainsi, l’ordonnanceur cède la place à une éventuelle interaction utilisateur. Et comme le setTimeout()
ne défini pas de timing, l’ordonnanceur reprendra immédiatement après cette interaction avec la tâche suivante de sa todolist et avec le même délai. C’est d’autant plus malin si on définit un délai inférieur ou égal à 50ms.
En terme de code cela donne ceci :
function todoList() { const DEADLINE = performance.now() + DELAY; while (workQueue.length > 0) { if (performance.now() >= DEADLINE) { setTimeout(todoList); return; } let job = workQueue.shift(); job.execute(); } }
Mais cela pourrait être encore mieux. En effet, avec cette solution le SID (Second Input Delay qui n’est absolument pas une abréviation courante mais créée ici pour les besoins de la démonstration) est un peu plus long en figure 4 que ce qu’il pourrait être…
Une nouvelle API JavaScript : isInputPending()
En effet, le test entre chaque tâche de la todolist pourrait être beaucoup plus pertinent si au lieu de tester un délais il testait l’existence réel d’une interaction en attente.
C’est donc ce que permet cette nouvelle api isInputAppend()
proposée par les développeurs de Facebook qui a été introduite dans la version 87 de Google Chrome disponible depuis le mois dernier ! A noter que isInputAppend()
est autant disponible pour événement court comme le “clic” que pour les évènements longs comme le “move”.
Ce qui change le code de notre ordonnanceur JavaScript comme ceci :
function todoList() { while (workQueue.length > 0) { if ( navigator.scheduling.isInputPending() ) { setTimeout(todoList); return; } let job = workQueue.shift(); job.execute(); } }
Application de l’ordonnanceur JavaScript
Attention, n’en déplaise à Mel Gibson, ce n’est pas une arme absolue. Tout d’abord car l’api isInputAppend()
n’est pas disponible dans tous les navigateurs. Il conviendra dès lors d’hybrider notre fonction pour cumuler un test avec un délais et l’api isInputAppend()
. Ce qui donnerait ceci :
function todoList() { const DEADLINE = performance.now() + DELAY; while (workQueue.length > 0) { if (performance.now() >= DEADLINE || navigator.scheduling.isInputPending() ) { setTimeout(todoList); return; } let job = workQueue.shift(); job.execute(); } }
Le résultat est un peu moins rapide qu’avec un seul test d’interaction utilisateur mais reste correct.
Cette hybridation présente un autre intérêt. Elle laisse de la place à d’autres scripts qui pourraient provenir de services tiers via des requêtes asynchrones ou des iframes. N’oubliez jamais que les tâches longues nuisent à l’expérience utilisateur.
Ensuite, tout dépend du fonctionnel attendu. On peut imaginer une première série de tâches qui ne peuvent pas être interrompues puis une autre série qui passerait par ce type d’ordonnanceur. Il faudra bien juger de la pertinence de cet ordonnanceur selon ce fonctionnel.
D’ailleurs, Nate Schloss et Andrew Comminos sont assez clairs sur le sujet :
Nous vous encourageons
à utiliserisInputPending()
avec discernement.
Notamment car son usage s’avère plus subtil qu’il en a l’air. Nous vous invitons donc à lire l’article sur web.dev où nous avons découvert ces principes. Je ne doute pas que les amateurs de Web Performance que je connais, ont déjà leurs neurones en pleine réflexion en découvrant ce concept ingénieux.