Écrit par Harry Robert sur Magie CSS.
Table des matières
- Qu’est-ce qu’un extrait de code asynchrone ?
- Héritage
async
Soutien - Qu’est-ce qui ne va pas avec le polyfill ?
- Le scanner de préchargement
- La nouvelle syntaxe
- Lorsque nous ne pouvons pas éviter les extraits de code asynchrones
- Emplacements des scripts dynamiques
- Injecter dans une page que vous ne contrôlez pas
- Plats à emporter
Si vous êtes développeur Web depuis un certain temps, vous avez très probablement rencontré un extrait asynchrone avant. Dans sa forme la plus simple, cela ressemble un peu à ceci :
<script>
var script = document.createElement('script');
script.src = 'https://third-party.io/bundle.min.js';
document.head.appendChild(script);
</script>
Ici, nous…
- créer un
<script>
élément… - dont
src
l’attribut esthttps://third-party.io/bundle.min.js
… - et le joindre au
<head>
.
La première chose que je trouve la plus surprenante est que la majorité des développeurs que je rencontre ne savent pas comment cela fonctionne, ce que cela fait ou pourquoi nous le faisons. Commençons par là.
Qu’est-ce qu’un extrait de code asynchrone ?
Des extraits comme ceux-ci sont généralement utilisés par des tiers pour que vous puissiez les copier/coller dans votre code HTML, généralement, mais pas toujours, dans le <head>
. La raison pour laquelle ils nous donnent cet extrait encombrant, et pas beaucoup plus succinct <script src="">
est purement historique : Les extraits de code asynchrones sont un hack de performance hérité.
Lors de la demande de fichiers JavaScript à partir du DOM, ils peuvent être soit blocage ou
non bloquant. De manière générale, le blocage des fichiers est moins bon pour les performances, en particulier lorsqu’il est hébergé sur l’origine de quelqu’un d’autre. Les extraits asynchrones injectent des fichiers dynamiquement afin de les rendre asynchrones, ou non bloquants, et donc plus rapides.
Mais qu’y a-t-il dans cet extrait qui rend le fichier asynchrone ? Il n’y a pas async
attribut en vue, et le code lui-même ne fait rien de spécial : il injecte simplement un script qui se résout en un blocage régulier
<script>
balise dans le DOM :
...
<script src="https://third-party.io/bundle.min.js"></script>
</head>
En quoi est-ce différent du chargement normal du fichier ? Qu’avons-nous fait qui rend cela asynchrone ? Où est la magie ?!
Eh bien, la réponse est rien. Nous n’avons rien fait. C’est la spécification qui stipule que tous les scripts injectés dynamiquement doivent être traités comme asynchrones. En insérant simplement le script avec un script, nous avons automatiquement opté pour un comportement de navigateur standard. C’est vraiment l’étendue de toute la technique.
Mais cela soulève la question… ne pouvons-nous pas simplement utiliser le async
attribut?
Comme petite anecdote supplémentaire, cela signifie que l’ajout
script.async="async"
est redondant—ne vous embêtez pas avec ça. Fait intéressant, ajouter script.defer=defer
fonctionne, mais encore une fois, vous n’avez pas besoin d’un extrait de code asynchrone pour obtenir ce résultat – utilisez simplement un <script src=""
.
defer>
Héritage async
Soutien
Ce n’est qu’en 2015 (il y a sept ans, il faut l’avouer…) que tous les navigateurs ont pris en charge le async
attribut. Pour tous majeur navigateurs, cette date était 2011, il y a plus de dix ans. Ainsi, afin de contourner ce problème, les fournisseurs tiers ont utilisé des extraits de code asynchrones. Les extraits de code asynchrones sont, à la base, un polyfill.
De nos jours, cependant, nous devrions passer directement à l’utilisation <script src=""
. À moins que vous ne deviez prendre en charge les navigateurs dans le domaine d’IE9, Opera 12 ou Opera Mini, vous n’avez pas besoin d’un extrait asynchrone (sauf si vous le faites…).
async>
Qu’est-ce qui ne va pas avec le polyfill ?
Si le polyfill fonctionne, quel est l’avantage de passer au async
attribut? Bien sûr, utiliser quelque chose de plus moderne est plus agréable, mais s’ils sont fonctionnellement identiques, est-ce mieux?
Eh bien, malheureusement, ce polyfill de performance est mauvais pour les performances.
Pour tout le script résultant est asynchronele <script>
bloc qui le crée est entièrement synchrone, ce qui signifie que la découverte du script est régie par tout travail synchrone qui se produit avant lui, qu’il s’agisse d’un autre JS synchrone, HTML ou même CSS. En effet, nous avons masqué le fichier du navigateur jusqu’au tout dernier moment, ce qui signifie que nous échouons complètement à tirer parti de l’un des éléments internes les plus élégants du navigateur… le
Scanner de préchargement.
Le scanner de préchargement
Tous les principaux navigateurs contiennent un analyseur secondaire inerte appelé Preload Scanner. C’est le travail du scanner de préchargement d’anticiper l’analyseur principal et de télécharger de manière asynchrone toutes les sous-ressources qu’il peut trouver : images, feuilles de style, scripts, etc. Il le fait parallèlement au travail de l’analyseur primaire qui analyse et construit le DOM.
Étant donné que le scanner de préchargement est inerte, il n’exécute aucun JavaScript. En fait, pour la plupart, il ne recherche vraiment que tokenisable src
et href
attributs définis ultérieurement dans le HTML. Parce qu’il n’exécute aucun JavaScript, le Preload Scanner ne peut pas découvrir la référence au script contenu dans notre extrait de code asynchrone. Cela laisse le script complètement caché à la vue et donc incapable d’être récupéré en parallèle avec d’autres ressources. Prenez la cascade suivante :
On voit bien ici que le navigateur ne découvre la référence au script (3) qu’au moment où il a fini de traiter le CSS (2). En effet, le CSS synchrone bloque l’exécution de tout JS synchrone ultérieur, et rappelez-vous que notre extrait de code asynchrone lui-même est entièrement synchrone.
La ligne violette verticale est un performance.mark()
qui marque le point auquel le script s’est réellement exécuté. On constate donc une absence totale de parallélisation, et un horodatage d’exécution de 3 127 ms.
Pour en savoir plus sur le scanner de préchargement, rendez-vous sur Andy Davis‘ Comment le préchargeur du navigateur accélère le chargement des pages.
La nouvelle syntaxe
Il existe plusieurs façons de réécrire vos extraits de code asynchrones maintenant. Pour le cas le plus simple, par exemple :
<script>
var script = document.createElement('script');
script.src = 'https://third-party.io/bundle.min.js';
document.head.appendChild(script);
</script>
… nous pouvons littéralement simplement échanger cela pour ce qui suit au même endroit ou plus tard dans votre HTML :
<script src="https://third-party.io/bundle.min.js" async></script>
Ceux-ci sont fonctionnellement identiques.
Si vous vous sentez nerveux à l’idée de remplacer complètement votre extrait de code asynchrone, ou si l’extrait de code asynchrone contient des variables de configuration, vous pouvez remplacer ceci :
<script>
var user_id = 'USR-135-6911-7';
var experiments = true;
var prod = true;
var script = document.createElement('script');
script.src = 'https://third-party.io/bundle.min.js?user=' + user_id;
document.head.appendChild(script);
</script>
…avec ça:
<script>
var user_id = 'USR-135-6911-7';
var experiments = true;
var prod = true;
</script>
<script src="https://third-party.io/bundle.min.js?user=USR-135-6911-7" async></script>
Cela fonctionne parce que, même si le <script src="" async>
est asynchrone, le
<script>
bloquer avant qu’il ne soit synchroneet est donc assuré de s’exécuter en premier, en initialisant correctement les variables de configuration.
async
ne veut pas dire courez dès que vous êtes prêt
ça veut dire courez dès que vous êtes prêt au moment ou après votre déclaration
. Tout travail synchrone défini avant un async
le script s’exécutera toujours en premier.
Nous pouvons maintenant voir le scanner de préchargement en action : une parallélisation complète de nos requêtes et un horodatage d’exécution JS de 2 340 ms.
Fait intéressant, le script lui-même a pris 297 ms de plus à télécharger avec cette nouvelle syntaxe, mais il a quand même été exécuté 787 ms plus tôt ! C’est la puissance du Preload Scanner.
Lorsque nous ne pouvons pas éviter les extraits de code asynchrones
Il y a quelques fois où nous ne pouvons pas éviter les extraits de code asynchrones, et donc ne pouvons pas vraiment les accélérer.
Emplacements des scripts dynamiques
Plus particulièrement, ce serait lorsque l’URL du script lui-même doit être dynamique, par exemple, si nous devions passer l’URL de la page actuelle dans le chemin du fichier lui-même :
<script>
var script = document.createElement('script');
var url = document.URL;
script.src = 'https://third-party.io/bundle.min.js&URL=' + url;
document.head.appendChild(script);
</script>
Dans ce cas, l’extrait de code asynchrone consiste moins à contourner un problème de performances qu’à un problème de dynamisme. La seule optimisation que je recommanderais ici, si le tiers est suffisamment important, est de compléter l’extrait avec un preconnect
pour l’origine en question :
<link rel=preconnect href="https://third-party.io">
<script>
var script = document.createElement('script');
var url = document.URL;
script.src = 'https://third-party.io/bundle.min.js&URL=' + url;
document.head.appendChild(script);
</script>
Injecter dans une page que vous ne contrôlez pas
Le deuxième besoin le plus probable d’un extrait de code asynchrone est si vous êtes un tiers injectant un quatrième parti dans le DOM de quelqu’un d’autre. Dans ce cas, l’extrait de code asynchrone consiste moins à contourner un problème de performances qu’à un problème d’accès. Il n’y a pas d’amélioration des performances que je recommanderais ici. Jamais preconnect
une quatrième, cinquième, sixième partie.
Plats à emporter
Il y a deux choses principales que j’aimerais que les gens obtiennent de ce post :
- Plus précisément, ces extraits asynchrones sont presque toujours un anti-modèle. Si vous les utilisez, essayez de passer à une nouvelle syntaxe. Si vous en êtes responsable, essayez de mettre à jour votre service et votre documentation pour utiliser une nouvelle syntaxe.
- Généralement, ne travaillez pas à contre-courant. Bien que nous ayons l’impression de faire la bonne chose, ne pas connaître la situation dans son ensemble peut nous amener à travailler contre nous-mêmes et à aggraver les choses.
☕️ Cela vous a-t-il aidé ? Achetez-moi un café !