# Charger le JS et le CSS intelligemment sur WordPress avec defer et async

> defer, async, script_loader_tag : le guide pratique pour arrêter de bloquer le rendu de vos pages WordPress avec des scripts mal chargés.

- Auteur : Clément Hadrot
- Publié le : 2022-07-07
- Mis à jour le : 2022-07-07
- Catégorie : Performance
- URL : https://wpmoderne.dev.wordpress-developpement.fr/performance/defer-async-js-wordpress/

## L’essentiel

- defer et async ne se comportent pas du tout de la même façon
- jQuery doit rester chargé en priorité s'il est une dépendance
- script_loader_tag reste la méthode pour ajouter ces attributs en WP 6.0

Chaque script chargé de façon classique dans le `<head>` d'une page WordPress bloque le rendu : le navigateur doit le télécharger et l'exécuter avant de continuer à construire la page. Sur un site avec plusieurs plugins, chacun enregistrant son propre fichier JavaScript, cette accumulation peut faire grimper le temps de rendu de façon significative sans qu'aucun octet de PHP ne soit en cause.

Les attributs `defer` et `async` permettent de sortir ces scripts du chemin critique de rendu. Mais les deux ne sont pas interchangeables, et les appliquer sans discernement peut casser des scripts qui dépendent les uns des autres, à commencer par jQuery. Voici comment faire les choses proprement en WordPress 6.0.

## Comprendre la différence entre defer et async

Sans attribut, un script est téléchargé et exécuté de façon synchrone, dans l'ordre où il apparaît dans le document, en bloquant le parsing du HTML pendant ce temps.

- **async** télécharge le script en parallèle du parsing HTML, puis l'exécute dès qu'il est prêt, en interrompant le parsing à ce moment précis. L'ordre d'exécution entre plusieurs scripts `async` n'est pas garanti.
- **defer** télécharge également le script en parallèle, mais reporte son exécution jusqu'à ce que le parsing HTML soit terminé, juste avant l'événement `DOMContentLoaded`. L'ordre d'exécution entre plusieurs scripts `defer` est, lui, garanti et respecte l'ordre du document.

Pour la grande majorité des scripts WordPress (widgets, animations, tracking non critique), `defer` est le choix le plus sûr : il préserve l'ordre de dépendance tout en libérant le rendu. `async` convient mieux à des scripts totalement indépendants, comme certains outils de mesure d'audience qui n'ont besoin d'aucun autre script pour fonctionner.

## Pourquoi ne pas simplement passer un argument à wp_enqueue_script

À la date de cet article, WordPress est en version 6.0. La fonction `wp_enqueue_script()` ne propose pas encore de paramètre natif pour définir une stratégie de chargement : son cinquième argument reste un simple booléen ou tableau contrôlant la position du script (pied de page ou non) et, plus récemment, quelques options limitées. Il n'existe pas d'argument `strategy` permettant de demander `defer` ou `async` directement à l'enregistrement du script.

La méthode qui fonctionne aujourd'hui consiste donc à passer par le filtre `script_loader_tag`, qui permet de réécrire la balise `<script>` générée par WordPress avant qu'elle ne soit imprimée dans le HTML.

## Ajouter defer avec le filtre script_loader_tag

Voici un exemple qui ajoute l'attribut `defer` à un script précis, identifié par son *handle* d'enregistrement :

```
function monsite_defer_script( $tag, $handle, $src ) {
    $scripts_a_differer = array( 'mon-script-animation', 'mon-script-carousel' );

    if ( in_array( $handle, $scripts_a_differer, true ) ) {
        return str_replace( ' src', ' defer src', $tag );
    }

    return $tag;
}
add_filter( 'script_loader_tag', 'monsite_defer_script', 10, 3 );
```

> L'essentiel à retenir : defer et async ne se comportent pas du tout de la même façon ; jQuery doit rester chargé en priorité s'il est une dépendance ; script_loader_tag reste la méthode pour ajouter ces attributs en WP 6.0

Le principe est le même pour `async` :

```
function monsite_async_script( $tag, $handle, $src ) {
    if ( 'mon-script-analytics' === $handle ) {
        return str_replace( ' src', ' async src', $tag );
    }

    return $tag;
}
add_filter( 'script_loader_tag', 'monsite_async_script', 10, 3 );
```

Il est préférable de cibler explicitement les *handles* plutôt que d'appliquer `defer` à tous les scripts sans distinction. Une liste blanche évite les mauvaises surprises sur des scripts tiers ajoutés par des plugins dont on ne maîtrise pas le comportement.

## Le piège de jQuery et des scripts qui en dépendent

jQuery est encore, en 2022, une dépendance de nombreux plugins WordPress et de nombreux thèmes, notamment via `wp_enqueue_script()` avec `jquery` déclaré dans le tableau des dépendances. Différer jQuery sans différer également tout ce qui en dépend casse immanquablement ces scripts : ils s'exécutent avant que `jQuery` ne soit défini dans l'espace global, provoquant des erreurs JavaScript silencieuses côté navigateur.

> Conseil maison : avant de différer un script, ouvrez la console développeur et repérez ses dépendances via l'inspection du DOM ou, plus fiable, en listant les dépendances déclarées dans le code du plugin. Si jQuery est différé, tous ses dépendants doivent l'être aussi, dans le même ordre.

Concrètement, cela signifie que si vous différez `jquery-core` et `jquery-migrate`, il faut également différer chaque script qui les déclare en dépendance, faute de quoi vous obtiendrez des erreurs du type `$ is not defined`. La solution la plus sûre reste souvent de laisser jQuery se charger normalement et de ne différer que les scripts qui n'en dépendent pas, comme les scripts d'analytics ou les widgets autonomes en JavaScript natif.

## Cas particulier du CSS : préchargement plutôt que defer

Le CSS n'a pas d'équivalent direct de `defer`, puisqu'un attribut `defer` sur une balise `<link rel="stylesheet">` n'a aucun effet. Pour éviter qu'une feuille de style non critique ne bloque le rendu, la technique consiste à la charger en `rel="preload"` puis à basculer son `rel` vers `stylesheet` une fois le téléchargement terminé, via un petit script inline ou l'attribut `onload` :

```
function monsite_preload_style( $html, $handle, $href, $media ) {
    if ( 'mon-style-non-critique' === $handle ) {
        return '<link rel="preload" as="style" href="' . esc_url( $href ) . '" onload="this.onload=null;this.rel=\'stylesheet\'">';
    }

    return $html;
}
add_filter( 'style_loader_tag', 'monsite_preload_style', 10, 4 );
```

## Vérifier le résultat sans casser le site

Avant de déployer ces changements en production, quelques vérifications s'imposent :

1. Ouvrir la console développeur du navigateur et vérifier l'absence de nouvelles erreurs JavaScript.
2. Tester chaque fonctionnalité interactive du site : menu mobile, formulaire de contact, carrousel, popin.
3. Vérifier dans l'onglet réseau que les scripts différés portent bien l'attribut attendu dans le HTML source.
4. Refaire un test de performance pour confirmer le gain sur le temps de rendu.

## En résumé

Différer intelligemment le JavaScript reste, en WordPress 6.0, une opération manuelle qui passe par le filtre `script_loader_tag`, faute d'argument natif dans `wp_enqueue_script()`. La règle d'or : `defer` par défaut pour préserver l'ordre d'exécution, `async` réservé aux scripts totalement indépendants, et une vigilance particulière sur jQuery et ses dépendants pour ne pas casser le site en voulant l'accélérer.
