Réduisez le temps d’exécution JavaScript en identifiant les fonctions coûteuses, en supprimant le travail inutile et en chargeant les fonctionnalités à la demande. Si le travail nécessaire monopolise encore le thread principal, répartissez-le entre plusieurs tâches ou déplacez les calculs adaptés dans un web worker. Volume téléchargé, temps CPU total et durée de la plus longue tâche ininterrompue sont trois problèmes différents : mesurez-les avant de choisir une correction.
Enregistrez l’interaction lente avant de modifier le bundle
Utilisez une version de production et une action reproductible : ouvrir la navigation, filtrer des produits ou développer un rapport. Enregistrez séparément le chargement et l’interaction dans Performance de Chrome DevTools. Conservez appareil, limitation CPU, réseau et consentement identiques. Extensions et instrumentation de développement peuvent modifier les résultats ; utilisez un profil propre et notez l’environnement.
Sélectionnez la période de blocage. Examinez Main, puis Bottom-up pour les fonctions à temps propre élevé et Call tree pour leurs appelants. Le temps propre appartient à la fonction, le temps total inclut ses descendants. Une petite enveloppe avec un total élevé indique parfois le chemin vers le problème, sans être elle-même à réécrire. Les source maps relient le code distribué au module maintenu.
Selon web.dev, une tâche est longue au-delà de 50 millisecondes. Cette limite identifie un blocage, ce n’est pas un budget à épuiser. Plusieurs tâches plus courtes peuvent aussi gêner l’utilisation, notamment avec le rendu et les scripts tiers. Évaluez l’interaction plutôt que le nombre de marqueurs d’avertissement.
Les diagnostics JavaScript et la treemap de Lighthouse orientent vers des ressources coûteuses et dépendances volumineuses. Ils ne prouvent pas que le plus gros fichier provoque la pause. Comparez taille et exécution. Le guide pour améliorer l’INP aide lorsque l’attente, le traitement et l’affichage suivant contribuent au retard.
Distinguez suppression, report et ordonnancement
Choisissez l’intervention selon les observations. La compression réduit les octets transférés, mais le navigateur reçoit le programme décompressé. La minification améliore la livraison sans remplacer la réduction du travail exécuté.
Ces mesures résolvent des problèmes distincts et doivent être évaluées séparément.
| Observation | Première piste | Ce qui n’est pas garanti |
|---|---|---|
| Fonction inutile sur la route initiale | Supprimer ou créer un chunk séparé | Premier usage plus rapide |
| Initialisation ou écouteurs répétés | Éliminer la répétition | Téléchargement réduit |
| Calcul long indépendant du DOM | Worker ou meilleur algorithme | Consommation CPU totale moindre |
| Séquence nécessaire bloquant le retour visuel | Céder entre étapes bornées | Fin plus rapide de toute la séquence |
| JavaScript déclenchant styles ou mise en page coûteux | Réduire modifications du DOM et dépendances | Résolution par les seuls attributs du script |
Formulez une hypothèse avant de modifier : fonction appelée moins souvent, chunk absent du chargement initial ou nouvelle occasion de rendu. L’enregistrement suivant expliquera alors le résultat au lieu d’ajouter une simple note à comparer.
Livrez seulement le code nécessaire à la vue actuelle
Examinez la production avec l’analyseur du bundler ou la treemap. Cherchez versions dupliquées, données linguistiques inutilisées, grosse bibliothèque pour un petit utilitaire et fonctionnalités présentes partout. Comprenez pourquoi chaque dépendance entre dans le graphe. Un remplacement plus léger peut modifier le comportement ou augmenter la maintenance.
Le tree shaking supprime les exports inutilisés identifiables statiquement lorsque la compilation le permet. Modules ES et déclarations exactes d’effets de bord facilitent ce travail. Ne déclarez pas tous les paquets sans effets de bord : polyfills, enregistrement de composants et imports CSS essentiels peuvent devoir s’exécuter sans export utilisé. Vérifiez la sortie de production, pas seulement l’instruction import.
Coverage fournit un indice supplémentaire. Un code inutilisé pendant l’enregistrement peut servir après connexion, sur une autre route ou dans une boîte de dialogue. Parcourez ces cas avant suppression, avec erreurs et accessibilité. Un usage rare ne rend pas une fonction superflue. Retirer une fonction abandonnée est préférable à une transformation fragile du code nécessaire.
Chargez une fonction optionnelle par import dynamique
L’import dynamique crée une frontière de chargement si le build prend en charge le découpage. Choisissez une fonctionnalité indépendante, par exemple un aperçu d’export. Découper chaque petite fonction ajoute coordination et requêtes ; séparer une route entière peut rester trop large pour un composant rarement utilisé.
Cet exemple comporte trois fichiers servis par HTTP et charge un module après activation. L’application doit localiser le libellé, l’état d’avancement et les erreurs.
<!-- index.html -->
<button id="preview" type="button">Preview export</button>
<p id="status" role="status"></p>
<pre id="output"></pre>
<script type="module" src="/main.js"></script>Le module d’entrée réactive le contrôle même si l’import échoue.
// main.js
const button = document.querySelector('#preview');
const status = document.querySelector('#status');
const output = document.querySelector('#output');
button.addEventListener('click', async () => {
button.disabled = true;
status.textContent = 'Loading preview';
try {
const { renderPreview } = await import('./preview.js');
renderPreview(output);
status.textContent = 'Preview ready';
} catch {
status.textContent = 'Preview unavailable. Try again.';
} finally {
button.disabled = false;
}
});Le module séparé fournit la fonctionnalité optionnelle.
// preview.js
export function renderPreview(element) {
element.textContent = JSON.stringify({ format: 'csv', ready: true }, null, 2);
}C’est un exemple fonctionnel de chargement, sans calcul coûteux ni gain mesuré. Un vrai module peut encore effectuer une initialisation lourde. Testez sa première activation à cache froid puis les suivantes. Si le premier usage devient lent, améliorez la fonction ou anticipez prudemment son chargement lorsque l’intention est claire. Ne déplacez pas simplement le problème sur le premier clic.
Donnez au navigateur de vraies occasions de répondre
Plusieurs petites fonctions synchrones restent une tâche ininterrompue. Attendre une promesse déjà résolue ne garantit pas une occasion de rendu : ses continuations sont des microtâches. Utilisez une continuation dans une tâche future lorsque l’opération peut être suspendue sans perdre sa correction.
Cet utilitaire privilégie scheduler.yield() et utilise sinon un minuteur ; la boucle limite chaque lot.
function yieldToBrowser() {
if (globalThis.scheduler?.yield) return globalThis.scheduler.yield();
return new Promise(resolve => setTimeout(resolve, 0));
}
export async function normalizeRows(rows, batchSize = 100) {
if (!Number.isInteger(batchSize) || batchSize < 1) {
throw new RangeError('batchSize must be a positive integer');
}
const normalized = [];
for (let start = 0; start < rows.length; start += batchSize) {
const end = Math.min(start + batchSize, rows.length);
for (let index = start; index < end; index++) {
normalized.push(String(rows[index]).trim());
}
if (end < rows.length) await yieldToBrowser();
}
return normalized;
}La taille du lot est illustrative, pas une limite sûre universelle. Un seul élément coûteux peut encore créer une tâche longue. Ajustez selon données et appareils, et envisagez l’annulation si l’entrée change. Le minuteur ne reproduit pas la priorité de l’API native : testez les deux chemins. La documentation officielle sur les tâches longues décrit ce modèle.
Céder améliore parfois la réactivité sans réduire le temps total, voire en ajoutant du travail. Préservez les opérations atomiques et n’annoncez pas une sauvegarde réussie avant sa fin. Le retour visuel doit refléter l’état réel, par exemple un traitement en cours.
Déplacez les calculs indépendants dans un worker
Un worker exécute JavaScript hors du thread principal de la fenêtre, sans accès direct à son DOM. Il convient au traitement indépendant de données, à leur analyse et aux calculs. Il ne résout pas automatiquement insertion d’éléments, mesures de disposition ou peinture. Pour une opération minuscule, démarrage et messages peuvent coûter davantage que l’exécution locale.
Ce worker de module illustre la séparation ; les données sont volontairement trop petites pour suggérer un gain.
// totals.worker.js
self.addEventListener('message', event => {
const total = event.data.reduce((sum, value) => sum + value, 0);
self.postMessage(total);
});L’appelant reçoit le résultat sur le thread principal et libère ensuite le worker.
// totals.js, loaded as a module
const worker = new Worker(new URL('./totals.worker.js', import.meta.url), {
type: 'module'
});
worker.addEventListener('message', event => {
console.log('Total:', event.data);
worker.terminate();
});
worker.addEventListener('error', () => worker.terminate());
worker.postMessage([2, 4, 6]);Dans une vraie application, définissez validation des entrées, erreurs et annulation. Réutilisez le worker si des tâches récurrentes le justifient. Les messages volumineux peuvent nécessiter des copies ; les buffers transférables en évitent certaines mais transfèrent leur propriété, ce qui change leur disponibilité côté émetteur. Mesurez délai complet, disponibilité du thread principal et mémoire.
Différez le démarrage sans masquer un blocage ultérieur
Un script différé s’exécute toujours sur le thread principal. async modifie le moment de chargement et d’exécution ; defer conserve aussi l’ordre des scripts classiques externes après analyse du document. Aucun ne fractionne une fonction coûteuse. Consultez le guide des ressources bloquant le rendu pour les dépendances, puis mesurez l’exécution.
Réservez l’inactivité aux travaux facultatifs. requestIdleCallback() reste inégalement disponible et son appel peut être retardé. Détectez le support, prévoyez une alternative et décidez d’un éventuel délai maximal. Navigation, validation et retour indispensables ne doivent pas dépendre d’un temps libre. Un timeout ne rend pas un gros callback moins coûteux : bornez toujours son travail.
Le HTML serveur peut être visible avant l’activation interactive du framework. L’hydratation peut utiliser beaucoup de CPU ; visibilité ne prouve pas réactivité. Limitez les composants clients, évitez les initialisations doubles et n’utilisez l’hydratation sélective ou différée prise en charge que si le comportement reste correct. Testez clics au démarrage, clavier, focus et premier usage de chaque zone.
Le code tiers concurrence les mêmes occasions d’exécution. Examinez le coût des scripts tiers et retirez les balises dupliquées avant d’ajouter un ordonnancement complexe. Documentez leur rôle et activation pour éviter de recréer le problème plus tard.
Repérez le travail répété et le rendu inutile
Un algorithme coûteux n’est pas la seule cause. Une opération légère peut tourner à chaque état sans rapport, défilement ou remontage. Comptez les appels et reliez-les à l’action utilisateur. Corriger un abonnement ou écouteur dupliqué peut être plus durable que réécrire son callback.
Dans une liste interactive, séparez transformations et affichage. Recalculez seulement lorsque les entrées changent, sans reconstruire toute la liste pour une petite zone. La mémoïsation doit justifier mémoire et invalidation. De mauvaises dépendances peuvent afficher un résultat périmé ; associez tests de correction et comparaison des performances.
Cherchez les alternances d’écriture de styles et lecture de géométrie. Les lectures peuvent imposer une mise en page avant de continuer JavaScript. Si possible, regroupez les mesures avant les écritures. Une fonction rapide peut déclencher ailleurs dans la trace des recalculs de styles et dispositions coûteux.
Retirez intervalles, observateurs et abonnements devenus inutiles. Une trace juste après chargement peut manquer le travail qui s’accumule pendant la navigation. Répétez le parcours sans rechargement et examinez une éventuelle croissance. C’est un scénario diagnostique, pas une accusation de fuite envers un framework.
Vérifiez le même parcours après modification
Répétez l’enregistrement dans les mêmes conditions, puis testez fonction à cache froid, visite répétée et appareil plus lent. Comparez tâches et réponse visible, pas seulement octets. Un démarrage plus court peut dégrader la première interaction, et un callback plus rapide provoquer encore du rendu coûteux.
Consignez modification, preuves et retour arrière dans la liste de contrôle des performances. Utilisez la collection sur la vitesse des pages pour la suite et choisissez la prochaine fonction coûteuse dans la nouvelle trace. Conservez le changement si le parcours initial progresse tout en préservant chargement, erreurs et accessibilité.