WordPress et les JavaScripts asynchrones

Voici une optimisation simple à mettre en place et qui peut améliorer considérablement le temps de chargement de vos sites : différer le chargement des fichiers javascripts.

En règle générale, les javascripts sont synchrones, c’est à dire que lorsqu’une page web se charge et tombe sur une URL de fichier JS, le cacul de la page est suspendu, le temps de le télécharger.

On se retrouve donc sur certains sites WordPress avec une dizaine de librairies et de scripts qui viennent ralentir le chargement et l’affichage.

L’astuce dont je vais parler consiste à rendre tous les javascript appelés par WordPress « asynchrones » : Ils seront chargés parallèlement au reste de la page.

Cette méthode vous fera gagner en performance, donc en expérience utilisateur et potentiellement en référencement.

Différez le chargement des fichiers javascripts sur WordPress
WP Deferred Javascripts : un plugin pour charger vos JS de façon asynchrone

La librairie LabJS

Pour charger des javascripts de façon asynchrone, il faut utiliser une librairie de chargement de fichier JS.

LabJS en est une, comme il en existe d’autres, mais celle-ci pèse 640 bits avant gzippage.

Cela veut dire qu’au lieu de charger :

  • jQuery
  • deux ou trois librairies
  • un ou plusieurs fichier JS

… pour parfois plusieurs centaines de Kbits au chargement de la page, votre site ce contentera de charger :

  • un  script de 640bits
  • une simple instruction pour chaque autre script

Vos javascripts seront alors importés indépendamment du reste de la page.
Et là c’est comme les légumes : ça compte pas…

Mise en place

Pour appliquer cette astuce, la procédure est de modifier la variable de WordPress qui contient la file de javascript, avant que le CMS n’essaye d’imprimer les fichiers.

Nous allons prendre les infos qu’elle contient, et les insérer dans une autre variable globale qui nous servira plus tard, à afficher les scripts ajoutés à la queue, comme bon nous semble.

Écouter la variable globale $wp-scripts

La variable qui contient toutes les infos relatives au JS sur WordPress s’appelle $wp_scripts. Elle est un objet de la classe du même  nom et WordPress l’utilise par le biais des fonctions regroupées dans le fichier functions.wp-scripts.php.

Avant toute chose, à l’init de WordPress, nous allons initialiser une variable $mes_scripts dans laquelle nous injecterons toutes les données qui nous intéressent.

add_action( 'init', 'initialize_script_global' );
function initialize_script_global() {
global $mes_scripts;
$mes_scripts = array();
}

Ensuite, avant que WordPress n’affiche les scripts, il faut que nous regardions le contenu du tableau queue de $wp_scripts.

Prenons ces infos et insérons-les dans notre variable perso.

// On se greffe sur wp_header et sur wp_footer...
// ... avant que wp_print_script ne soit exécuté...
// ... sinon ce sera trop tard, il n'y aura plus de hook pour empêcher...
// ... l'impression des scripts
add_action( 'wp_head', 'je_veux_mes_scripts', 1 );
add_action( 'wp_footer', 'je_veux_mes_scripts', 1 );
function je_veux_mes_scripts() {
global $mes_scripts,$wp_scripts;
foreach( $wp_scripts->queue as $s ){
$mes_scripts[ $wp_scripts->registered[ $s ]->handle ] = array(
'src' => $wp_scripts->registered[ $s ]->src, // src du script
'deps' => $wp_scripts->registered[ $s ]->deps, // dépendances du script
'extra' => $wp_scripts->registered[ $s ]->extra //données relatives (variables)
);
}
}

Nous venons d’obtenir la liste des scripts mis à la file, tout au long du chargement de WordPress, ainsi que toutes les infos qui leur sont relatives…

Empêcher l’affichage normal des scripts

Pour empêcher que WordPress ne vienne lui-même imprimer les scripts, il suffit simplement de vider le tableau queue de l’objet $wp_script.

Dans le header et le footer, après avoir ajouté nos scripts dans $mes_scripts (donc avant de fermer la fonction je_veux_mes_scripts()), il faut ajouter le code suivant :

$wp_scripts->queue = array();

WordPress lorsqu’il exécutera wp_print_script(), ne verra aucun fichier dans la file, et ne fera rien du tout.

Gérer les dépendances des JavaScripts

Si à ce stade nous regardons notre variable globale, nous nous apercevront que les scripts sont bien dedans, mais chargés dans n’importe quel ordre.

Nous ne pouvons pas lancer le rendu du JS directement, il faut d’abord nous inquiéter des dépendances. Dans le cas on nous omettrions cette étape, nous risquerions d’obtenir de nombreuses erreurs de javascripts (et accessoirement le site ne fonctionnerai plus^^)

Et là on se dit :

Mais comment fait WordPress pour gérer ça ?

La technique employée par WordPress est de faire une boucle pour trier les scripts :

  1. Les scripts sans aucune dépendances sont chargés directement.
  2. Ceux qui ont besoin d’une dépendance déjà instanciée peuvent se charger
  3. Ceux qui attendent leur dépendances feront un deuxième tour (ou plus)
  4. Ceux qui requiert un script qui n’est pas dans la file feront aussi un autre tour, mais demandent l’instanciation des scripts dont ils dépendent
  5. Ceux qui ont des dépendances qui n’existent nul part crèvent en silence…
  6. Lorsque tous les cas sont traités, la boucle prend fin.

La méthode peut paraître bourrin au premier abord, mais en fait il n’y a pas vraiment de solution mieux adaptée.

Il faut créer une fonction, qui va être hookée sur wp_footer, dans laquelle nous allons trier les javascripts. C’est dans cette même fonction que nous rendront le JS par la suite. Elle doit être exécutée assez tardivement ; il faut lui attribuer une priorité assez élevée.

Les outils que nous allons utiliser sont :

  • notre variable global $mes_scripts où sont stockés nos scripts
  • la global $wp_scripts
  • une variable temporaire $mes_scripts_ordonnes où nous allons stocker les javascripts, dans le bon ordre.

Au fur et a mesure de la boucle nous allons également avoir besoin de vider notre variable globale, il nous faut donc pour finir, une dernière variable $souvenir_de_mes_scripts qui sera une simple copie de $mes_scripts.

add_action( 'wp_footer', 'afficher_moi_tout_ca', 99 );
function afficher_moi_tout_ca() {
global $mes_scripts,$wp_scripts;
$i = count( $mes_scripts );

$mes_scripts_ordonnes = array();
$souvenir_de_mes_scripts = $mes_scripts;

//la boucle infinie et le foreach sur la globale
while( $i > 0 ){
foreach( $mes_scripts as $k => $s ){
//traitons nos scripts ici
}
}
}

Pour commencer, les scripts sans dépendances sont ajoutés directement.

if( empty( $s[ 'deps' ] ) ){
$mes_scripts_ordonnes[ $k ] = $s;
unset( $mes_scripts[ $k ] );
$i--;
}

Ceux qui en ont font l’objet d’un traitement particulier, il ne seront ajoutés que si toutes leurs dépendances ont déjà été vues.

else{
$alldepscounter = 0; //nombre de conditions remplies
foreach( $s[ 'deps' ] as $d ){
if( ! array_key_exists( $d, $souvenir_de_mes_scripts ) ) { //je n'aurais jamais ma dépendance...
if( isset( $wp_scripts->registered[ $d ] ) ) { //... à moins qu'il y ai un script ?
$mes_scripts[ $wp_scripts->registered[ $d ]->handle ] = array(
'src' => $wp_scripts->registered[ $d ]->src,
'deps' => $wp_scripts->registered[ $d ]->deps,
'extra' => $wp_scripts->registered[$d]->extra);
$souvenir_de_mes_scripts[ $wp_scripts->registered[ $d ]->handle ] = $mes_scripts[ $wp_scripts->registered[ $d ]->handle ];
}else{ //retourne dans ton monde, créature démoniaque...
unset( $mes_scripts[ $k ] );
$i--;
break;
}
}else{
if( array_key_exists( $d, $mes_scripts_ordonnes ) ) { // je rempli une condition en +
$alldepscounter++;
}
}
}
if( $alldepscounter == count( $s[ 'deps' ] ) ) { //bravo, toutes les conditions sont remplies
$mes_scripts_ordonnes[ $k ] = $s;
unset( $mes_scripts[ $k ] );
$i--;
}
}

Lorsque la globale est vide, alors la boucle est rompue et tous les scripts sont ordonnés dans notre nouvelle variable.

Il ne reste plus qu’à appeler LabJS et lui indiquer les fichiers à charger.

Charger les scripts

Voilà, maintenant il est temps de tout afficher, à condition qu’il y ai des scripts, bien sûr !

Donc si $mes_scripts_ordonnes n’est pas vide, alors on ajoute la librairie LabJS, puis on ouvre une balise script.

Dans cette balise script il suffit de boucler sur $mes_scripts_ordonnes et pour chacun, afficher leurs datas (issues de wp_localize_script()) puis leur sources :

// code à insérer avant la fermeture de affichez_moi_tout_ca()
if( ! empty( $mes_scripts_ordonnes ) ) {
$output = '<script src="' . get_bloginfo( 'template_url' ) . '/js/lab.min.js"></script>' . "\n";
$output.= '<script>';
foreach( $mes_scripts_ordonnes as $s )
$output.= $s[ 'extra' ][ 'data' ] . '$LAB.script("' . $s[ 'src' ] . '");' . "\n";
$output.= '</script>';
echo $output;
}

Le plugin WP Deferred Javascripts

Le code est complet et l’astuce devrait maintenant être opérationnelle, sauf dans certains cas :

  • Si votre thème est codé un peu à l’arrache et que des scripts sont mis « en dur »
  • Si des scripts sont mis en dur, avant wp_footer()
  • Si vous chargez des scripts dans le header, puis ajoutez des données qui lui sont relatives directement dans le template, via un wp_localize_script()
    Ce bug a été corrigé dans la version 1.2 du plugin 🙂

Voici le code dans son intégralité :

//init global
add_action( 'init', 'initialize_script_global' );
function initialize_script_global() {
global $mes_scripts;
$mes_scripts = array();
}

//remplissage de $mes_scripts et vidage de $wp_scripts->queue
add_action( 'wp_head', 'je_veux_mes_scripts', 1 );
add_action( 'wp_footer', 'je_veux_mes_scripts', 1 );
function je_veux_mes_scripts() {
global $mes_scripts,$wp_scripts;
foreach( $wp_scripts->queue as $s ){
$mes_scripts[ $wp_scripts->registered[ $s ]->handle ] = array(
'src' => $wp_scripts->registered[ $s ]->src, // src du script
'deps' => $wp_scripts->registered[ $s ]->deps, // dépendances du script
'extra' => $wp_scripts->registered[ $s ]->extra //données relatives (variables)
);
}
$wp_scripts->queue = array();
}

//tri et impression des scripts
add_action( 'wp_footer', 'afficher_moi_tout_ca', 99 );
function afficher_moi_tout_ca() {
global $mes_scripts, $wp_scripts;
$i = count( $mes_scripts );

$mes_scripts_ordonnes = array();
$souvenir_de_mes_scripts = $mes_scripts;

//la boucle infinie et le foreach sur la globale
while( $i > 0 ){
foreach( $mes_scripts as $k => $s ){
if( empty( $s[ 'deps' ] ) ){
$mes_scripts_ordonnes[ $k ] = $s;
unset( $mes_scripts[ $k ] );
$i--;
}else{
$alldepscounter = 0; //nombre de conditions remplies
foreach( $s[ 'deps' ] as $d ){
if( ! array_key_exists( $d, $souvenir_de_mes_scripts ) ) { //je n'aurais jamais ma dépendance...
if( isset( $wp_scripts->registered[ $d ] ) ) { //... à moins qu'il y ai un script ?
$mes_scripts[ $wp_scripts->registered[ $d ]->handle ] = array(
'src' => $wp_scripts->registered[ $d ]->src,
'deps' => $wp_scripts->registered[ $d ]->deps,
'extra' => $wp_scripts->registered[ $d ]->extra);
$souvenir_de_mes_scripts[ $wp_scripts->registered[ $d ]->handle ] = $mes_scripts[ $wp_scripts->registered[ $d ]->handle ];
}else{ //retourne dans ton monde, créature démoniaque...
unset( $mes_scripts[ $k ]);
$i--;
break;
}
}else{
if( array_key_exists( $d, $mes_scripts_ordonnes ) ) { // je rempli une condition en +
$alldepscounter++;
}
}
}
if( $alldepscounter == count( $s[ 'deps' ] ) ) { //bravo, toutes les conditions sont remplies
$mes_scripts_ordonnes[ $k ] = $s;
unset( $mes_scripts[ $k ] );
$i--;
}
}
}
}

//print this
if( ! empty( $mes_scripts_ordonnes ) ) {
$output = '<script src="' . get_bloginfo( 'template_url' ) . '/js/lab.min.js"></script>' . "\n";
$output.= '<script>';
foreach( $mes_scripts_ordonnes as $s )
$output.= $s[ 'extra' ][ 'data' ] . '$LAB.script("' . $s[ 'src' ] . '");' . "\n";
$output.= '</script>';
echo $output;
}
}

Vu qu’il s’agit d’une optimisation assez avancé, l’intérêt n’est pas de recoder ça sur chaque site. Daniel Roch et moi en avons donc fait un plugin qui est disponible sur l’extend de WordPress : WP Deferred Javascripts (actuellement en version [baw_papii plugin= »wp-deferred-javascripts » info= »version » cache= »48″]).

Si vous avez quoi que ce soit comme soucis relatif au plugin, je vous invite à nous faire remonter les bugs via l’onglet « Support » de la page du plugin.

Alors, quel est maintenant votre ressenti sur le gain du temps de chargement lié à ce plugin ?

22 commentaires
Ca à l’air super ça, je teste sur ma v3 et sur secupress, promis 😉
Merci pour ce premier plugin, d’autres prévus ? 😉
Pierre, il y a 12 ans
Ça m’a l’air pas mal du tout ^^, je l’ai mise en production sur 3 sites pour des tests pour voir le gain de perfs …
Willy Bahuaud, il y a 12 ans
Merci !! En ce qui concerne les autres plugins, j’en ai fait un il y a quelques semaines (et faudrait que je fasse un article dessus pour expliquer son utilité) : Multi image metabox

Sinon j’ai un autre plugin sur le feu depuis plus de deux ans… faudrait que je le termine… ;-P

Daniel Roch, il y a 12 ans
Content de voir que notre plugin plaise en tout cas. L’autre truc bien d’ailleurs avec lui, c’est que l’on voit tout de suite les plugins et les thèmes mal codés…

: si tu trouves le plugin performant, n’hésite pas à faire une review ici.

: deux ans, tu es large encore ^^

Aurélien Denis, il y a 12 ans
Bon… va falloir que j’essaie même si j’ai pas tout compris de l’article ! 😀
Julien Maury, il y a 12 ans
Excellent, m’a permis de débuguer mon js qui plus est. J’ai pu constater le gain en mode admin (pas de cache).
Morgan, il y a 12 ans
Pas eu le courage de lire jusqu’au bout après les quelques articles que je me suis tapé sur d’autres blogs, mais ce que j’ai pu lire m’a plu. Je repasserai par là pour essayer ce plugin et voir ce qu’il vaut.
Arnaud, il y a 12 ans
Du bon boulot, je vais regarder pour mettre ça en place.
En plus le chargement du Javascript prend pas mal de place pour mon site, et ça n’a pas l’air compliqué à mettre en place.
Fabrice, il y a 12 ans
Excellent et je m’en vais ci-après ce commentaire, essayer le plugin…

Thanks Willy & Daniel, c’est mon indice Google speed qui va être content 😉

Ps/ Willy, j’ai essayé ta 404…. une tuerie !!!

GeekPress, il y a 12 ans
Je suis septique sur un éventuel gains de temps sur un site qui contient un unique js et minifié. J’ai même lu que ça pouvait ralentir le chargement du script !

Par contre,je pense que ce plugin est plutôt dédié aux thèmes usine à gaz qui mettent 40 fichiers js dans le footer.

Willy Bahuaud, il y a 12 ans
, , Oui, essayez et dites-moi ce que vous en pensez. Il se peut qu’il ai subsisté quelques bug jusqu’à aujourd’hui. Maintenant il semble me semble beaucoup plus stable.

, Oui en mode Admin on se rend vite compte. Si en revanche on a un front bien optimisé avec un seul JS et un bon système de cache, je ne sais pas trop si on sent une différence.

Je suis content que la 404 te plaise ! Et encore elle n’est pas comme je l’aurais voulu…

Un jour mon plugin il sortira, un jour… (tu fais quoi vendredi en 8 ? Une petite coding night ?)

Si vous connaissez quelques sites bien optimisés par défaut, pour y faire un jsPerf avec et sans le plugin, je suis preneur !

Alexandre Ionoff, il y a 12 ans
Merci pour ce plugin, je l’ai mis en prod sur plusieurs sites et ça fonctionne pas mal en effet.
ouvrela, il y a 12 ans
Bonjour,
j’ai installé votre plugin qui fonctionne fort bien mais depuis quelques jours j’ai changé de thème et maintenant j’ai un souci.C’est un thème responsive gratuit pour infos.Dans ce thème il y a un slider et si j’active le plugin celui-ci n’affiche plus les images,en désactivant le plugin , le slider re fonctionne?
Peut-être un conflit de scripts ? mauvais code css?
Auriez-vous une idée , une piste ?
merci
cordialement
Yoav, il y a 11 ans
Excellent ! Combiné à Incapsula.com, WP Cache Machine et WP Defer Javascript; c’est une fusée ! Recommandé 🙂
eslovenie, il y a 11 ans
Hello,

sur mon site, votre plugin fait bugger les commentaires G+ avec ce plugin : Google+ Comments

Phil, il y a 11 ans
Bonjour,

très content de la première version avec une amélioration très net du temps de chargement , fonctionnnant bien avec minify.
Parcontre depuis les dernère mise à jour, j’ai constaté des ralentissements notoire au point que j’ai désactivé le plug in et décoché  » Enable JavaScript Minification » dans minify pour que Lightview Plus puisse fonctionner.
voilà quelques détails , si cela peut vous aider…
Phil

Rosa, il y a 11 ans
J’ai une petite question, si je le mets (ton plugin) sur mon blog, est-ce que je peux enlever assetsminify ?
Jeff, il y a 11 ans
Je bloquais sur des erreurs javascript à la suite de l’installation de plusieurs plugin sur mon thème qui en contient déjà pas mal. Résultat mon site était bloqué.
Avec ce plugin, plus d’erreur. !
Andre, il y a 11 ans
Hello,
Chez moi cette extension empêche « Lightbox Plus Color » de fonctionner. J’aimagine que le javascript ne se charge pas correctement?
++ andre
ctrlfix, il y a 11 ans
Salut, ton pluging fonctionne super bien cependant, j’ai un soucis avec mon theme, le menu ne fonctionne plus lorsque j’active ton extention. Est-ce qu’une solution est possible ? sans a avoir besoin de changer le theme ? Merci
Xavkick, il y a 10 ans
Bonjour,

Je viens de tester votre plugins mais je rencontre des soucis de javascript .
– Ubermenu ne semble pas aimer ( c’est pourtant un plugin très répendu )
– Ninja Popup ne s’affiche plus non plus.
Sinon le gain est énorme. Pourriez vous m’aider et me dire ce qui ne va pas sur mon site ?
merci et encore merci pour tous vos conseils
xavier

Véro, il y a 8 ans

Bonjour, juste une question pour savoir si le plugin fonction toujours sur la dernière version de WP ? Car je ne vois aucune différence dans GT Metrix ou Pingdom. Merci

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Publié le 28 janvier 2013
par Willy Bahuaud
Catégorie Développement WordPress, Référencement naturel