Javascript non bloquant

Je vous l’avais dit, une balise <script> bloque le rendu et les nouveaux téléchargements dans le navigateur le temps que le javascript soit complètement téléchargé et exécuté. Une des solutions c’est de reléguer cette balise à la fin du document.

Il y a des fois où ce n’est pas idéal et c’est Steve Souders qui étudie les solutions dans sa présentation aux conférences Velocity 2008. Quelques cas en vrac :

  • Le Javascript est prioritaire, il doit être téléchargé et exécuté le plus tôt possible, mais pas au prix d’un blocage de tout le reste ;
  • Il n’est pas possible déplacer l’insertion de la balise à la fin du document, le système ne le permettant pas ;
  • Vous ne voulez pas montrer à l’utilisateur que la page est toujours en train de se charger (logo et barre de statut) ;
  • Vous ne voulez pas que l’événement onload de la page attende le téléchargement et l’exécution du javascript.

Ne pas bloquer le rendu

Pour résumer les solutions intéressantes abordées par Steve Souders :

Passer par l’API DOM

La première solution est d’insérer le javascript via la création d’un noeud DOM. Le script ne bloque alors plus les nouveaux téléchargements. Le problème principal est que certains navigateur délaient l’événement onload tandis que d’autres l’exécuteront avant la fin du script. Cette différence peut rendre la gestion de la page un peu difficile

var se = document.createElement('script');
se.src = 'http://example.org/test.js';
se.type = 'text/javascript' ;
document.getElementsByTagName('head')[0].appendChild(se);
Passer par une iframe

La seconde solution est d’insérer le javascript via une iframe. Le rendu et les nouveaux téléchargements ne sont pas bloqués, l’événement onload réagit de la même façon sur tous les navigateur. Le défaut est que l’iframe fait elle-même perdre un peu de temps au rendu et que pour réutiliser le contenu on est limité à avoir la page et le script sur le même domaine. Impossible donc d’utiliser un CDN sur un domaine tiers.

Cela demande de plus de réorganiser le script lui même. La page principale devra passer par window.frames[x] pour accéder aux fonctions du script, et le script devra lui passer par parent.document pour accéder au DOM de la page principale.

Passer par XMLHttpRequest

La solution qui peut paraitre magique c’est d’insérer le javascript via une requête XMLHttpRequest. Le script ne bloque ni les nouveaux téléchargements ni l’événement onload. Mieux le navigateur ne montre pas à l’utilisateur que la page continue à se charger. Cela peut amener l’utilisateur à avoir une meilleure impression de vitesse. Par contre on est ici aussi limité à un seul domaine pour la page HTML et le javascript, donc pas de CDN sur domaine tiers.

Je ne vous met pas de code, chacun a sa bibliothèque pour ça. Vous pouvez utiliser DOM ou eval, comme vous voulez.

Passer par un script DEFER

Enfin, vous avez l’attribut DEFER que vous pouvez ajouter à une balise de script des plus classiqes. Là non plus le script ne bloque pas les nouveaux téléchargements … mais uniquement sous Internet Explorer.

Préserver l’ordre d’exécution

Tout ça donne déjà un bon aperçu mais tout n’est pas résolu. Dès que vous avez plusieurs javascript vous avez certainement des dépendances et l’ordre de chargement est important. Là ça se gâte. Vous oubliez la technique de l’iframe qui ne vous garantit rien.  

Si vous pouvez vous permettre d’avoir le javascript sur le même domaine que la page HTML, alors il va vous falloir gérer vous même un code dans XMLHttpRequest pour assurer l’ordre d’exécution.

Dans le cas contraire, Firefox préservera l’ordre d’exécution quand vous insérez vos scripts via DOM. Pas Internet Explorer. Il ne vous reste plus qu’à faire du cas par cas : charger via DOM pour Firefox, et utiliser une balise de script classique avec un attribut DEFER pour Internet Explorer.

L’écran 26 de la présentation donne une excellente grille de lecture de tout ça. Et les autres navigateurs ? motus et bouche cousue. Dans l’ensemble les comportements ont des chances d’être similaires à ceux de Firefox, mais c’est à tester. Il y a quelques liens vers le Cuzillon donc si certains veulent tester et donner les résultats Safari et Opera…

Utiliser YUI

L’équipe Yahoo! est toujours sur la brèche côté performance. Vous avez doc un outil YUI pour charger dynamiquement scripts et des feuilles de style en tenant compte des dépendances, sans bloquer le navigateur. Visiblement tout n’est pas clair sur la situation de Safari, il faudrait que je fouille un peu pour tirer ça au clair. D’ordinaire la politique est d’avoir un comportement stable sur tous les navigateurs grade A, dont Safari fait partie, donc j’ai bon espoir que si ce n’est pas déjà le cas, une solution finisse par être trouvée.

Pour le même prix l’outil YUI permet aussi de charger aussi les feuilles de style et éviter leur aspect bloquant sur Firefox.

Stoyan Stefanov en parle lui aussi, en détail.

Et l’avenir ?

Le bon côté c’est que les équipes Webkit et IE8 ont dans leurs cartons la possibilité de charger les scripts sans bloquer le navigateur et en les exécutant dans l’ordre d’apparition dans la page.

Tout ceci est pour demain, et les navigateurs d’aujourd’hui ne sont pas amenés à disparaitre rapidement, mais au moins, à moyen terme, on pourra n’avoir de ralentissement que sur les anciens navigateurs. Reste juste à espérer que Firefox et Opera bougent aussi sur ce point, mais malheureusement je n’ai pas vu d’information dans ce sens.

Publié par edaspet

Plus d'informations sur mon profil en ligne

15 réponses sur « Javascript non bloquant »

  1. J’avais testé la méthode DOM il ya longtemps, et j’en avais conclu que le onload était pas fiable du tout, même sous Firefox: des fois le script n’était pas entièrement chargé à ce moment la, on obtenait donc des undefined de partout en tentant d’utiliser des fonctions définies dans le script.

    Le problème est ptet corrigé depuis… Le seul moyen que j’avais trouvé pour être sur que le script soit « prêt » est un callback de la page principale qui était appelé à la fin du script en question.

    Sinon, HTML 5 ajoute defer et async. Par contre, je suis pas certain que leur spec soit 100% compatible avec celle de IE, d’après mes vagues souvenir celle de IE est très très vague et il faut s’en mefier, surtout quand ya plusieurs defer sur la page.

  2. @Yves: J’ai vu très très peu de sites qui méritent d’être inutilisables sans Javascript. Même un netvibes mériterait un contenu par défaut sans js, quitte à ce que certains modules soient castrés ou non présents. La plupart du temps avoir un contenu simple et statique puis l’enrichir par javascript est préférable (pour des questions d’accessibilité par exemple, mais pas uniquement).

    Pour Mozilla Tamarin est intéressant, il est dans ma liste de sujets de recherche, mais je ne crois pas avoir vu des travaux pour la parallélisation des javascript.

  3. Après beaucoup d’échecs principalement sur explorer, je me suis mis à utiliser une bibliothèque toute prête: jQuery, mais j’imagine que d’autres sont aussi performantes.
    Je crée donc un tableau des ressources js à charger, et grâce à la magie de $.getScript, (un peu d’Ajax), je charge les scripts les uns après les autres, à l’aide d’une fonction en boucle. Lorsque le premier est chargé, le deuxième est appelé, etc.
    Les dépendances sont maintenant construites dans l’ordre. Je perds certes un peu en temps puisque le chargement est linéaire, mais avec une bonne compilation (http://compressorrater.thruhere.net/) on dépasse rarement les 8ko. Qui plus est le javascript étant pour ma part essentiellement utilisé comme un complément et jQuery étant non intrusif, le site n’est pas inutilisable pendant le chargement… qui dure parfois le temps de bouger le rongeur…

  4. Ca implique tout de même de précharger Jquery, ce qui est loin d’être neutre. Maintenant si tu sais déjà la liste des fichiers à charger, je t’incite à regarder si tu peux les concaténer en un seul fichier, plutot que de faire plusieurs requêtes indépendantes.

  5. C’est pas bête, mais cela manque un peu de souplesse dans le développement.
    En tous cas, merci pour tes informations qui me rendent déjà de précieux services et qui sont d’une grande clarté. En plus c’est en français. For me dable !

  6. Il existe des moyens de faire la concaténation au dernier moment, sur le serveur la première fois que quelqu’un fait la requête.
    Bref, que le client fasse un seul appel n’implique pas que vous développiez sur un seul fichier.

    Ca peut ressembler à :

    /assemble-js.php?fichier1.js&fichier2.js&….

    C’est réalisable en quelques lignes de PHP (attention cependant à bien intégrer une liste blanche des contenus qu’on peut inclure ainsi, pour que personne ne demande un fichier arbitraire sur votre serveur)

    Ca a plusieurs avantages annexes :

    – le script d’assemblage peut s’assurer que les entêtes d’expiration sont bien envoyées, éventuellement le faire avec un petit PHP si on n’a pas accès à la configuration du serveur

    – le tout est dynamique donc on peut ajouter/supprimer des fichiers à la demande sans se prendre la tête à faire trop d’opération à la main

    – on peut même automatiser la minimisation des fichiers, et éviter donc de le faire à la main

  7. Pour rappel, $.getScript ne fonctionne pas sous Safari 2 ou du moins pas correctement.
    Sans oublier que le support safari 2 est terminé donc prudence.
    Cependant, jquery monte comme une flêche en terme de performance.
    J’en avais discuter avec plusieurs personnes à l’Afup en décembre 2008, les rustines c’est bien mais vivement une meilleur prise en charge par les navigateurs futurs.
    Je pense bien entendu a firefox, safari.
    IE 8 JE ne sais pas trop il me déçois de plus en plus en terme de performance

  8. IE8 est plutot bon coté perf.

    Il arrive à peu près entre Firefox 3 et Firefox 2 côté exécution Javascript (de mémoire, j’ai la flemme de vérifier). Ce n’est pas idéal mais c’est honnête.

    Par contre ils ont amélioré le parallélisme et des choses utiles : http://stevesouders.com/ua/

    (les rouges sont sur des points finalement secondaires, par contre le vert de troisième colonne et le nombre de connexions parallèles, ça ça va vraiment améliorer les choses)

    Je ne tirerai pas à boulets rouges sur IE8, pas sur le côté des perf web ressenties par e visiteur (la pure vitesse javascript n’est pas forcément le principal)

Les commentaires sont fermés.