La performance web côté client : partie 2

Voici la suite du billet sur la performance web. Si vous n'avez pas lu le début, c'est par ici.

Après avoir vu comment réduire le nombre de requêtes vers le serveur, voyons d'autres axes d'améliorations.

Optimiser la lecture de la page par le navigateur

Appels des styles CSS et scripts JS

Il est fréquent que les pages contiennent plusieurs CSS et JS. Nous voyons souvent les appels à ces fichiers dans la balise <head>. Ceci n'est pas optimal car quand le navigateur rencontre une balise <script>, il bloque l'affichage de la page tant que le script n'est pas téléchargé, lu et exécuté.
Ainsi, en mettant une balise <script> dans le <head> comme on le voit trop souvent, tout ce qui suit cette balise ne sera pas affiché avant que le script soit exécuté.
Tous les éléments s'affichant dans une page se trouvent après le <head>. La page restera donc blanche tant que les scripts ne seront pas exécutés. Voilà pourquoi il faut faire appel aux fichiers JS à la fin de la page (juste avant </body>) et non au début.
Si des scripts sont nécessaires au rendu immédiat de la page (c'est rare), il faut les mettre après les CSS et non avant.

Les JS empêchent de télécharger les autres ressources simultanément
css_js.jpg

Appels des styles CSS et scripts JS

Il est fréquent que les pages contiennent plusieurs CSS et JS. Nous voyons souvent les appels à ces fichiers dans la balise <head>. Ceci n'est pas optimal car quand le navigateur rencontre une balise <script>, il bloque l'affichage de la page tant que le script n'est pas téléchargé, lu et exécuté.
Ainsi, en mettant une balise <script> dans le <head> comme on le voit trop souvent, tout ce qui suit cette balise ne sera pas affiché avant que le script soit exécuté.
Tous les éléments s'affichant dans une page se trouvent après le <head>. La page restera donc blanche tant que les scripts ne seront pas exécutés. Voilà pourquoi il faut faire appel aux fichiers JS à la fin de la page (juste avant </body>) et non au début.
Si des scripts sont nécessaires au rendu immédiat de la page (c'est rare), il faut les mettre après les CSS et non avant.

Les JS empêchent de télécharger les autres ressources simultanément
css_js.jpg

Changer l'ordre d'appel fait gagner du temps
css_js2.jpg

L'internaute peut ainsi voir et consulter la page, même si certains scripts sont encore en cours de téléchargement ou d'exécution. Cela est d'autant plus important que de plus en plus de gens vont sur internet en situation de mobilité avec une connexion très limitée. L'affichage rapide est alors primordial sous peine de perdre l'internaute.

De plus, il faut avoir le moins de JS possible dans sa page, car contrairement aux autres types de fichiers, les fichiers JS empêchent le téléchargement simultané de ressources. Nous avons vu dans la 1ère partie que les navigateurs pouvaient télécharger 6 ressources en même temps. Mais en rencontrant chaque fichier JS, ils doivent le télécharger, l'exécuter, et téléchargent les autres ressources seulement après. La disponibilité de l'ensemble des ressources est alors d'autant plus repoussée.
Ceci est une autre bonne raison pour concaténer (ou regrouper) au maximum les fichiers JS.

Une page avec 13 fichiers JS appelés. Aucun n'est téléchargé en même temps qu'un autre car le fichier doit être téléchargé entièrement avant de passer au suivant. Le navigateur met 4.46 secondes pour rapatrier les 13 scripts.
reseau_firebug.jpg

La même page avec les mêmes scripts concaténés en 2 fichiers. Temps total : 1.87 seconde.
reseau_firebug2.jpg

Eviter les imports de CSS dans des fichiers CSS

Importer un fichier CSS dans un autre CSS empêche le téléchargement parallèle (téléchargement simultané de ressources), car le navigateur doit télécharger et exécuter le 1er fichier pour savoir qu’il y en a un second.

Différer l’exécution du javascript

Les scripts bloquent l’affichage de la page tant qu’ils ne sont pas chargés et exécutés.
Mais l’attribut async sur l'appel au script permet de passer outre cette limitation et ne pas bloquer le rendu de la page pendant le téléchargement.

En HTML4, il faut écrire async="true"
async.jpg

Attention à ne pas confondre les attributs async et defer : avec async, le script ne bloque pas la page et sera exécuté dès que disponible, sans tenir compte des autres scripts présents. Si l'attribut est présent sur plusieurs scripts, chacun sera exécuté quand il sera disponible, sans tenir compte de l'ordre d'apparition du script. Cela peut parfois être problématique.
L'attribut defer a le même rôle mais préserve l'ordre d'exécution des scripts.

Une autre méthode consiste à écrire du code Javascript directement dans la page pour charger des scripts externes après le chargement total de la page. Nous sommes donc sûrs qu'ils ne ralentissent pas l'affichage.
Voici un exemple de code chargeant 2 fichiers JS 500 millisecondes après l'affichage complet de la page :

defer_js.jpg

Ne charger que le nécessaire : chargement conditionnel de ressources

Servir au navigateur beaucoup de ressources à télécharger et interpréter est gourmand en performances. Les différents navigateurs et appareils (ordinateurs, tablettes, smartphones) ont tous des capacités différentes.

Certaines ressources ne sont utiles que dans certaines configurations. Par exemple un JS ou CSS gérant le tactile des appareils tactiles, des scripts "de confort" pour faire des effets visuels partout sauf sur mobile pour économiser ses ressources, des fichiers CSS dédiés à certaines tailles d'écran...).

Exemple : détection en javascript de la taille d'écran : si l'écran fait plus de 500 pixels de large, nous insérons un diaporama dans la page.
conditional_js.jpg

Les nouveautés HTML5 sont un bon exemple : beaucoup de choses sont possibles, mais leur support est très inégal. Il existe alors beaucoup de scripts JS qui simulent ces fonctionnalités dans le navigateur.

Dans tous ces cas, faire télécharger et exécuter au navigateur des choses qu'il ne comprend pas, qui ne lui sont pas destinées ou qui lui sont inutiles est non seulement inutile, mais cela crée des requêtes HTTP, du temps de traitement, et allonge le temps de chargement de la page pour rien.

Il est important de pouvoir détecter quand il faut servir ces ressources et quand il ne le faut pas, pour construire sa page de façon optimisée.
Il faut donc détecter les capacités du navigateur qui consulte la page, et les caractéristiques de l'appareil en jeu.
Pour cela, il existe une librairie Javascript fort pratique : Modernizr.

Elle permet de tester facilement des fonctionnalités HTML5, CSS3, des media queries et des caractéristiques physiques de l'appareil.
Voici un exemple permettant de ne charger des ressources que si certaines conditions sont réunies :

modernizr.jpg
Nous chargeons un script ou un autre suivant le support de la géolocalisation, un JS de support de canvas si ce n'est pas le cas, et des CSS et JS particuliers au tactile suivant le résultat du test.

Réduire la taille des requêtes

Pour gagner du temps de chargement il faut réduire les requêtes, optimiser la lecture de la page par le navigateur, mais aussi réduire au maximum la taille des requêtes inévitables.
Pour cela nous pouvons :

  • Minifier les fichiers HTML, CSS et JS

La minification supprime tous les commentaires et tous les espaces des fichiers. Ils sont donc moins volumineux. Suivant les fichiers, le poids gagné peut être important.

Du code CSS minifié
css_minifie.jpg

C'est très simple à mettre en place. De nombreux scripts existent pour le faire manuellement : une fois le travail sur les fichiers terminé, il suffit de les soumettre à un outil en ligne qui traite le fichier et nous le renvoie. Nous devons ensuite le mettre en ligne comme d'habitude.
Quelques scripts :
- CSS Minifier
- Outil de Yahoo pour CSS et JS

  • Optimiser les images

Les images sont en général très gourmandes en performances car elle peuvent être lourdes. Nous avons vu dans la première partie comment regrouper les images de décoration (CSS). Mais il est impossible de regrouper les images de contenu, et impossible de les compresser via le serveur. Tout doit être fait en amont.
il faut qu'elles soient le moins lourd possible.
En fonction du type d'image, le format utilisé est important et permet un gain de poids :
- JPEG pour des photos
- PNG 8 bits pour des pictos
- PNG 24 bits pour gérer la transparence
- GIF pour les petites images ou les images animées

Il existe des outils de compression (et de compression sans perte) pour optimiser le poids :
Photoshop, imageOptim, jpegmini, RIOT...

  • Compresser tout ce qui peut l’être !

Nous pouvons ordonner au serveur web de compresser les données qu'il va envoyer au navigateur.

Pour donner des directives au serveur web, nous avons besoin d'un fichier de configuration pour ce type de serveur. En fonction du type de serveur, il y a différents types de fichiers de configuration. Je prends toujours comme base un serveur Apache, qui a besoin d'un fichier nommé (assez bizarrement) ".htaccess".
Nous y plaçons nos instructions pour le serveur.

Les données seront décompressées par le navigateur à leur réception. Cela leur permet de transiter plus vite car elles sont évidemment moins lourdes une fois compressées.
Il existe plusieurs types de compression dont certains sont plus répandus et supportés que d'autres par les serveurs. GZIP est le plus performant et le plus répandu.
Pour utiliser la compression, il faut que le serveur web la prenne en charge. Cela est possible en activant les modules de compression du serveur.

Voici un article simple sur le sujetd e la compression et sa mise en place sous Apache.

Activation de la compression GZIP (qui utilise le module DEFLATE) pour plusieurs types de fichiers :
htaccess.jpg

  • Mise en cache des ressources et date d'expiration

Pour économiser des requêtes vers le serveur, nous pouvons utiliser le cache du navigateur. Avant d'aller chercher une ressource sur le serveur, le navigateur interroge toujours son cache pour voir s'il contient la ressource. Si oui, il la prend du cache, évitant d'interroger le serveur.

Le cache contient les fichiers des sites web téléchargés par le navigateur lors de précédentes visites. Il a une certaine taille, et s'il est plein, plus rien ne peut être mis en cache. Pour que le cache ne soit jamais plein, les navigateurs utilisent des algorithmes particuliers pour déterminer combien de temps les données doivent rester présentes dans le cache.
Au bout d'un certain temps, les fichiers disparaissent donc du cache.

Mais il est possible de configurer cette durée pour maintenir en cache ce que nous voulons pendant le temps que nous voulons. Nous pouvons donc garder nos données plus longtemps en cache et éviter des requêtes.
Cela se fait à nouveau dans le fichier de configuration du serveur. Pour les fichiers dont le contenu change très fréquemment, nous ne les indiquons pas, ou bien nous indiquons une durée nulle.

Ajout d'une date d'expiration pour quelques types communs de fichiers statiques
htaccess_expires.jpg

Nous indiquons pour chaque type pendant combien de temps la version présente en cache doit être utilisée. Passé ce délai, une requête sera faite au serveur pour avoir la version en ligne.

Mais que faire si nous mettons à jour les fichiers sur le serveur ?

En l'état, les fichiers CSS et JS du cache seront utilisés pendant un an, sans tenir compte des versions de ces fichiers sur le serveur. Si nous faisons des corrections ou évolutions et que nous remettons nos fichiers en ligne, ils seront donc tout simplement ignorés par les navigateurs qui les avaient en cache, et les internautes ne verront les modifications qu'au bout d'un an ! C'est tout simplement impossible de faire ainsi.

Pour que les internautes puissent voir les modification sans délai il y a 2 possibilités :

  • Indiquer des dates d'expiration peu lointaines, correspondant approximativement à la fréquence de nos mises à jour. Mais c'est approximatif, l'internaute a de grandes chances de ne pas voir les nouvelles versions immédiatement.
  • Indiquer des dates d'expiration lointaines (comme dans cet exemple) et utiliser une technique qui va court-circuiter le cache si et seulement si une version plus récente est présente sur le serveur.

Le fingerprinting url :

Cela consiste à ajouter une empreinte à l'url du fichier. L'empreinte est une longue suite de caractères. Quand le contenu du fichier change, l'empreinte change et donc l'url du fichier aussi. Elle devient donc différente de l'url du fichier en cache. Pour le navigateur c'est comme si c'était un fichier différent, il va donc aller le télécharger depuis le serveur.
Ainsi, l'internaute récupère la version à jour du serveur.
Si le fichier n'a pas été modifié sur le serveur, l'empreinte ne change pas, et la version du cache sera utilisée.

Mise en place (exemple en PHP ) :

Il existe une fonction md5_file qui calcule le md5 d'un fichier et renvoie une empreinte correspondante. Si le fichier change, son empreinte aussi.
Voici comment utiliser la fonction pour un fichier CSS :

Nous ajoutons l'empreinte du fichier à la fin de son url dans un paramètre :
md5_1.jpg

Cela donnera une url de cette forme :
md5_2.jpg

Après modification du fichier sur le serveur, son url change automatiquement pour donner par exemple :
md5_3.jpg

Outils

Voici des outils pour diagnostiquer les pages web et identifier les points à améliorer pour la performance.

  • Google Page Speed

C'est une extension pour Firebug / Chrome Developer Tools. Nous lançons l'analyse de la page en cours et nous avons une liste de choses à mettre en place, classées par priorité, indiquant le gain de poids apporté.

page_speed.jpg

C'est aussi une page web sur laquelle nous indiquons l'url d'un site à analyser et qui ressort les mêmes résultats.

  • WebPageTest

Service en ligne similaire, indiquant un waterfall (diagramme en fonction du temps) des ressources téléchargées.

A noter

Il existe un script très intéressant qui met en oeuvre ces bonnes pratiques "d'un seul coup" : nous lui indiquons l'emplacement de nos fichiers statiques, il les minifie, les combine pour n'avoir qu'un seul fichier CSS et un seul fichier JS, crée un répertoire avec les nouveaux fichiers et met à jour les chemins. Il gère le cache des fichiers statiques.
Nous pouvons faire autant de modifications que voulues dans les fichiers CSS et JS originaux, l'outil les traite et met à jour les fichiers minifiés et combinés automatiquement, en court-circuitant le cache.

Ce script applique aussi la compression GZIP des ressources et leur ajoute une date d'expiration.

Cet outil se nomme Minify et se trouve ici

Sources et références

Règles d’or de la performance

http://stevesouders.com/hpws/rules.php https://developers.google.com/speed/docs/best-practices/rtt
https://developers.google.com/speed/docs/best-practices/rendering

Modernizr
http://modernizr.com/

Fonction md5_file PHP
http://php.net/manual/fr/function.md5-file.php

Tout sur mod_pagespeed (module Apache)

https://developers.google.com/speed/docs/mod_pagespeed/filters?hl=fr

Personnaliser le fichier .htaccess

http://www.lije-creative.com/htaccess-cache-compression-gzip-minify-et-plus/

HTML5 Boilerplate
https://github.com/h5bp/html5-boilerplate

Laisser un commentaire

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

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.