Une des optimisations essentielles en webperf consiste à différer le chargement des JavaScript. Pour comprendre l’intérêt de cette démarche, nous allons d’abord voir ce qu’est un script bloquant, pourquoi il a un impact sur le temps de chargement, les solutions pour le rendre “non bloquant” mais aussi les écueils dans lesquels ne pas tomber. Nous détaillerons enfin la solution que nous avons développée : une fonctionnalité appelée DeferJS.

Qu’est-ce qu’un script bloquant et pourquoi il ralentit le chargement d’une page web

Pour afficher une page, le navigateur analyse le code HTML afin de construire le DOM. Dès que l’analyseur rencontre du JavaScript (JS), il interrompt la construction du DOM, récupère le JS, l’exécute et continue ensuite son analyse du HTML. Comme le navigateur affiche les éléments d’une page en fonction des informations qu’il découvre pendant sa lecture du fichier HTML, l’endroit où se trouve le JS a un impact direct sur le temps de chargement et l’expérience utilisateur, et détermine le rendu de la page. C’est en cela qu’il est bloquant : son chargement et son exécution interrompent l’analyse du HTML.

Nous avons expliqué ce processus dans un article précédent, le principe est illustré ici :

Javascript Web Performance HTML Parsing

Mais pourquoi bloquer le rendu ? Eh bien parce que tant que le fichier JS n’est pas récupéré et analysé, le navigateur ne sait pas ce qu’il devra en faire, et arrête tout dans l’attente des instructions.

Le code peut contenir des scripts internes ou des scripts venant de tiers (Third Parties). Sachez que dans le second cas, comme le navigateur doit aller chercher le JS là où il se trouve (sur un serveur distant, un cache…), le temps nécessaire pour le chargement est encore plus long.

En résumé, à domicile ou depuis un tiers, JavaScript a un impact sur le temps de chargement. Mais heureusement, il existe des solutions pour limiter cet impact en indiquant au navigateur qu’il peut exécuter les scripts plus tard, au lieu de lancer la récupération et l’analyse dès qu’il les découvre dans le HTML. En effet, certains scripts ne sont pas indispensables pour le rendu de la page et peuvent attendre la fin du chargement pour être exécutés. Alors profitons-en pour rendre la navigation la plus fluide possible !

Rendre un script “non bloquant” avec les attributs Async et Defer

Le code HTML contient les informations qui guident le navigateur, et celui-ci peut-être orienté à l’aide d’indications complémentaires qui organisent son périple.

Les balises <script> qui appellent les ressources JS peuvent ainsi être enrichies des attributs suivants :

  • Async : afin de télécharger le script tout en poursuivant l’analyse du HTML pour construire le DOM, et exécuter le script une fois qu’il est disponible. Recommandé notamment pour les Third Parties ou les scripts qui n’ont pas d’impact sur d’autres fichiers, cet attribut permet d’améliorer l’UX mais peut toutefois bloquer l’analyse du HTML au moment de l’exécution ;
  • Defer : pour exécuter le JS seulement après l’analyse complète du HTML, en respectant l’ordre d'exécution tel que défini dans le HTML.

Ces deux attributs permettent ainsi de modifier le comportement du navigateur en lui indiquant qu’il doit exécuter les instructions dans un ordre différent de la lecture du HTML. Nous apportons des précisions sur ces techniques dans notre article dédié à JS Asynchrone (en rappelant pourquoi il ne rend pas les scripts “gratuits” en termes de webperf).

Mais alors, comment différer l’exécution des scripts JS pour l’ensemble de vos pages, sans passer un temps infini à réécrire tout votre code, tout en gérant les dépendances entre les scripts ? Nous avons prévu une fonctionnalité qui automatise cette tâche à la volée pour vous faire gagner du temps : DeferJS.

Automatiser intelligemment la réécriture du code pour différer l’exécution des scripts

Nous l’avons vu, pour rendre un script bloquant “non bloquant”, un point essentiel à prendre en compte est la dépendance éventuelle entre les scripts. 

En effet, les premiers scripts déclarent généralement des librairies (comme JQuery) qui vont être utilisées par les scripts embarqués dans la page ou déclarés à la suite.

Pour cette raison, il n’est pas possible de leur appliquer un attribut Async (qui ne suit pas l’ordre de l’analyse HTML pour exécuter les scripts), parce que cela reviendrait à prendre le risque que la librairie ne soit pas prête au moment où elle doit entrer en scène.
Quid de l’attribut Defer ? Il pourrait fonctionner puisqu’il permet de conserver l’ordre d'exécution des scripts, mais... à la seule condition qu’il s’applique à la totalité du script et qu’aucun script embarqué dans la page n’aie de dépendances avec des librairies. C’est un cas très rare, autant chercher une aiguille dans une botte de foin.

A la lumière de ces précisions, vous comprendrez pourquoi il est difficile de systématiser l’utilisation des attributs Async et Defer, puisqu’ils ne gèrent pas les cas où les scripts modifient le DOM à l’emplacement déclaré (via document.write).

Venons-en maintenant à notre sujet préféré : l’automatisation intelligente des optimisations webperf !
Il est possible de pallier les limites d’Async et Defer en orchestrant l’exécution des JS, et c’est justement ce que fait DeferJS. 

En pratique, voici comment fonctionne DeferJS :

  • Les scripts bloquants ne sont exécutés qu’au moment du DOMContentLoaded pour reproduire le comportement de l’attribut Defer et ne pas bloquer le premier rendu de la page.
  • Les scripts ainsi différés peuvent écrire dans le DOM à l’endroit prévu via des instructions document.write. Ils peuvent alors faire des appels au DOM de façon correcte en simulant un DOM construit partiellement jusqu’au nœud script (même si le DOM n’est réellement construit qu’au moment de l'exécution).

En tant qu’orchestrateur, DeferJS prend en compte les éventuelles déclarations des attributs Async et Defer présents dans la balise <script> pour assurer des propriétés de chargement telles que celles prévues dans le code.

Le saviez-vous ?

Fonctionnalité centrale de notre plateforme, DeferJS est actif systématiquement dès que le SmartCache est en place. Grâce au fait que DeferJS temporise l'exécution du JavaScript, le SmartCache peut injecter les fragments dynamiques de l’HTML récupérés à l’origine. Bref, du travail d’équipe.

 

Que se passe-t-il alors dans le fichier HTML ? Les attributs src des éléments <script> sont transformés en frz_orig_src, et le moteur ajoute en plus un nouvel attribut type dont la valeur est définie à text/frzjs. Un gestionnaire du window.onload est ajouté au HTML qui exécute alors tous les scripts différés.

defer-javascript-web-perfomance

Et ce n’est pas tout ! Nous ajoutons aussi un lien de préchargement à chaque script différé pour un chargement le plus rapide possible. Cela vient compenser le fait que le préscanner HTML ne peut pas découvrir l’URL des scripts avant de connaître les attributs.

Du bon usage de DeferJS

Dans quelles situations DeferJS est le plus utile ? Principalement pour les scripts qui ont un impact sur l'interactivité de la page. 

Existe-t-il des situations où il vaut mieux éviter de différer les scripts ? Oui ! Sachez que pour des scripts d’A/B test qui peuvent modifier profondément le DOM, des scripts gérant des carrousels ou du lazyload d’images, différer ne ferait que repousser le moment de l’affichage complet de la page. 

C’est pourquoi cette fonctionnalité nécessite un paramétrage fin, et ce pour isoler les scripts critiques pour le rendu de la page en choisissant les balises à exclure du traitement.

Comme toutes nos fonctionnalités, DeferJS est en amélioration continue pour vous aider à mieux gérer vos scripts tiers et avoir un Time To Interactive toujours plus performant.
D’ailleurs, nous explorons en ce moment l’utilisation du RequestIdleCallback… Stay tuned pour être au courant des résultats que nous observons et de son potentiel 😉

Vous souhaitez suivre nos évolutions et être au courant de nos actualités ?
Abonnez-vous à notre newsletter mensuelle :

Je m'abonne !


Hello SMX Paris !