# WEBPERF FRONT ... la base

### "Facts" sur l'impact de la WebPerf :

- The fastest newspaper website loads in half a second
- **A second delay in page response decreases customer satisfaction by 16%**
- 73% of mobile users said they’ve encountered a site that was "too slow" to load
- If Amazon increased page load time by +100ms they lose 1% of sales
- If Google increased page load by +500 ms they get 25% fewer searches
- **64% of smartphone users expect pages to load in less than 4 seconds**
- **99% of response time problems are still caused by the UI being too slow**


## Qu'est ce qu'on entend par WebPerf côté Front ?

Plusieurs indicateurs peuvent être utilisés pour définir si un site est rapide. 
L'event *load* et/ou l'event *domContentLoaded* sont les plus faciles à vérifier.
Si un site charge vite alors ces événements seront déclenchés assez rapidement. On estime qu'un site qui charge en moins de 3 secondes en mobilité est un site très rapide.

Seulement, un site peut avoir un événement load qui est déclenché à 850ms et cependant avoir un speed index très mauvais.
Le principe du speed index c'est le temps que met la page pour se dessiner, et donc l'impression de rapidité.

Donc un site qui charge vite n'est pas forcement un "site rapide", alors qu'un site qui se dessine rapidement (avec un temps de chargement plus long) sera perçus comme rapide et satisfera plus l'utilisateur.

Enfin un site qui se dessine rapidement, mais qui n'est pas utilisable rapidement, sera considéré comme lent malgré tout, il donnera l'effet d'un site "laggy".

La WebPerf c'est donc le meilleur compromis possible pour afficher rapidement le contenu à un utilisateur et permettre à ce dernier de le consommer.

> 99% of response time problems are still caused by the UI being too
> slow...

C'est donc un enjeu principalement Front que de garantir un site rapide. Evidement le backend et l'infra ont leurs rôles à jouer. Les meilleures optimisations ne serviront à rien si la requête HTML de la page met 5 secondes.

### La performance web, concept à plusieurs facettes

- Technique : 
	- Back
	- Front
	- Infra
- UX :
	- Conception
	- FX
	- Structure de l'information
	- Responsive (penser mobilité)

Le Front et l'UX sont par definition liés.
Beaucoup d'actions peuvent être entreprises pour avoir un site performant, mais c'est un concept qui doit être intégré dès les phases de conceptions et challengé côté produit.

### Lexique

Quelques termes que l'on va utiliser :

- **Render** : l'interprétation de la requête HTML par le navigateur et son dessin (CSS) pour afficher le contenu
- **Repaint** : lorsque le navigateur redessine des éléments non structurant (couleur par exemple)
- **Reflow** : lorsque le navigateur redessine des éléments structurant (injection html, modification width, padding ...)
- **FCP** : First Content Paint => indicateur de rapidité pour la premier image / text dessiné
- **FMP**: First Meaningful Paint => indicateur de rapidité de la visibilité du contenu principal
- **Viewport** : ce qui est visible dans la fenêtre navigateur, peu importe la taille de l'écran (ou de la fenêtre)
- **Fold** : ou "ligne de flottaison", la ligne qui représente la limite entre le viewport et le reste d'uns page. *Above The Fold*, au dessus de la ligne de flottaison
-  **Lazyload** : "Chargement paresseux", chargement asynchrone de certaines ressources sur une action utilisateur ou sur un événement (ex : scroll ou load de la page)
- **Speed Index** : indice / calcul du temps d'affichage de la page web. Plus l'index est petit plus rapide a été l'affichage
- **Time To Interaction** : le temps nécessaire au navigateur pour rendre le site affiché utilisable


## Comment estimer sa WebPerf ?

Différents outils sont mis à notre disposition :

- Lighthouse (audit dans Chrome dev-tools)
- Pagespeedinsight (basé sur le moteur de lighthouse) https://developers.google.com/speed/pagespeed/insights/
- Webpagetest (utilisé pour le classement webperf de JDN et parmi les premiers à intégrer la notion de Speed Index) https://www.webpagetest.org/
- Onglet "network" des Chrome dev-tools


## Lighthouse comme base de réflexion / amélioration

LightHouse est l'outil qui va servir de référence car il est complet. Il va analyser les pages et faire un rapport sur les performances mais aussi sur les "best pratices" à mettre en place. Il peut même permettre d'analyser votre site si vous en faites une *progressive web app* (PWA).

Voici les différents points qui auront un impact direct dans l'analyse que fera LightHouse :

- **HTML & CSS** : un DOM léger sera *parsé* plus rapidement. Un CSSOM pertinent sera *parsé* et *matché* au DOM rapidement, cela va donc avoir un impact positif sur le Speed Index.
- **Image / Assets / sources externe et lazyload** : *Postponer* le chargement des images non visibles à plus tard (scroll utilisateur). Charger toutes les ressources tierces et scripts externes après le contenu du site. Vive l'asynchrone !
- **Requêtes bloquantes** : les éviter ou les limiter dans le DOM et sinon les lazyloader.
- **Economiser les requêtes** : Moins de requêtes === navigateur moins sollicité et donc plus rapide.


# HTML & CSS 

La base de toute page web.

Pour permettre au navigateur de parser efficacement et rapidement le CSSOM et le DOM il faut prévoir des intégrations légères : 

- Eviter les imbrications HTML inutiles
- Utiliser des sélecteurs CSS pertinents et rapide à parser

### HTML

Les imbrications inutiles en HTML sont une erreur que nous faisons tous facilement, pour des raisons de cohérences de structures, de sémantique, mais le plus souvent par habitude.

    <div class="toto"><a class="link">MY LINK</a></div>

Si il n'y a aucune raison, pour un design spécifique, il vaux mieux alors écrire :

    <a class="link">MY LINK</a>
    
et simplement lui donner un *display: block* côté css.

Il faut penser à l'économie réalisée, à l'échelle d'une grosse page web, en terme de poids de page (moins de DOM) mais aussi en terme de parsing navigateur et donc de render.
Plus le DOM sera simple plus rapide sera le render.
L'exemple ici est simple et logique mais nous avons tous des modules HTML qui ne respectent pas cette règle, souvent avec des intégrations du type :

    <div><div><div><p><a /></p></div></div></div>

multiplié par 30 sur la même page ... juste pour afficher un lien...

LigthHouse recommande d'avoir un DOM qui contient moins de 1500 nodes :

> A large DOM can increase memory usage, cause longer style calculations, and produce costly layout reflows.

### CSS

Le CSS se lie de droite à gauche et parse tout le DOM pour récupérer ses références.
Cela se traduit par deux erreurs fréquemment commises :

1. Nombre de sélecteurs CSS trop nombreux et non qualifiés (utilisation de tag html dans les css et/ou trop grand nombre de sélecteurs)

	Donc si nous avons : 

	    .toto .tata div p a { color: #ABCDEF }

	Le navigateur pour dessiner la page  va d'abord récupérer tout les *a href*, les limiter à ceux qui sont enfants de *p*, filtrer de nouveau à ceux qui sont enfants de *div* etc ... jusqu'a .toto. et enfin appliquer les styles voulu à ... *a href*.

	La bonne pratique : 

	    .toto .link { color: #ABCDEF }
	    
	Le navigateur va récupérer tout les *.link* de la page les filtrer aux enfant de *.toto*.
	Moins de temps de parsing et donc un render plus rapide.

2. Les sélecteurs CSS non utilisés pour l'interface dans une feuille de style.
C'est à dire avoir des *.toto* dans sa feuille de style qui n'ont pas leur références dans le DOM (pas d'élément HTML qui possède cette class).
En effet dans ce cas, le navigateur va quand même chercher dans le DOM les références à ces class, en vain.
Ce sont des boucles et du temps que l'on peut économiser.
Bien-sûr, il est très difficile d'arriver à une feuille de style où 100% des sélecteurs sont utilisés.
Pour réduire un maximum les class inutiles, la bonne pratique est d'avoir une CSS par UI.
Autre impact directement visible sur la WebPerf, cela réduit forcement le poids de la feuille de style. (Ne pas oublier, pour réduire encore plus la taille des CSS, de les minifier)

Il faut donc éviter le plus possible les sélecteurs trop génériques se basant sur la structure HTML (tag, >, *, :nth-child, ...).
Sans forcement tomber dans le OOCSS (mais c'est quand même c'est bien pratique) il faut garder à l'esprit que plus les CSS sont pertinentes, directes et légères, plus le travail du navigateur sera facilité.
Cela aura aussi un impact positif sur la maintenabilité.

Sur AlloCiné, la règle c'est : pas plus de 3 sélecteurs CSS et aucun sélecteur par tag HTML.
Evidement il y a des exceptions (commentés et expliqués dans le code).

### Inliner le CSS

Nous avons maintenant une feuille de style pertinente et légère... appelé via un tag *link* dans le *head*.
Mais la balise *link* utilisée pour les CSS est bloquante (bloque le parsing du DOM) et le render ne sera possible que lorsque cette CSS sera chargée... 

Une des clés de la WebPerf c'est de limiter les requêtes.
La clé pour avoir un Speed Index faible c'est un render rapide.

**Let's inline the css !**

Le principe est simple, on prend les CSS correspondantes à tout l'HTML qui se trouve au dessus de la ligne de flottaison (on vise un peu large pour un site responsive) et on l'inline (minifié) dans le *head* de notre page.

On vient de gagner une requête bloquante et de permettre au navigateur de dessiner notre page plus rapidement !!!

Inconvénient : on vient aussi d'augmenter le poids de la requête HTML, et il faudra penser à faire une requête pour les CSS secondaires (en dessous de la ligne de flottaison).
Mais l'utilisateur aura l'impression que le site apparait plus vite, car le navigateur aura très tôt les CSS et l'HTML pour la partie visible. Cela se traduira donc par un FCP & un FMP faible et une page perçus comme rapide.


# Les images (quick win WebPerf) ...

Un site est avant tout un produit visuel. Les images sont nécessaires pour présenter ou mettre en avant du contenu.
Une image === une requête.
Si l'image, n'est pas à proprement parlé une ressource bloquante, elle est néanmoins une ressource qui peut couter chère en terme de rendering (requête + dessin * nombre d'image + calcul des CSS etc .... **BOOM you just made your website slow**)
Lorsqu'il parse le DOM, le navigateur doit envoyer une requête par image en plus de calculer et mettre en page l'UI. L'utilisateur ne va peut-être même pas consommer certaines images qui se situent plus bas dans la page...

L'idée est donc de ne charger que l'image (ou les images) nécessaire au bon affichage de la page dans le viewport, *Above the Fold*. (youpi on vient de gagner 500ms sur le *domContentLoad*)

Le lazyload des images consiste donc à les charger de manière asynchrone.
Concrètement, cela revient à ne pas renseigner l'attribut *src* d'une image par la ressource voulue, mais par un placeholder (sur AlloCiné nous utilisons un pixel transparent en base64) et de passer l'information de la ressource dans un data-src.
Un script JS se chargera d'écouter le scroll et de calculer le meilleur moment pour passer le *data-src* en *src* et charger les images "lazyloadées".

Le site va se charger plus rapidement, en évitant d'envoyer des requêtes inutiles pour l'affichage *above the fold*.
Nous faisons économiser du temps de rendu au navigateur et de la bande passante à l'utilisateur.

C'est un *quick win*. Facile à mettre en place et peut faire économiser beaucoup sur le FMP.

Il faut aussi penser à utiliser des images optimisées, si possible dans les nouveaux formats supportés par les navigateurs (requêtes plus légères, plus rapides et donc gain pour l'utilisateur).


# ... les Assets ...

Les ressources internes, non prioritaires, utilisées pour le design des pages (CSS additionnelles, images de design, fonts ...) peuvent faire perdre pas mal de temps au chargement d'une page.
De plus, le chargement de ces ressources peut déclencher des *repaint* voir des *reflows*.

Si possible, il faut donc appliquer la même logique à ces ressources qu'aux images : **ASYNC POWAAA !**
Il faut aussi penser à limiter le plus possible ces ressources internes.
Aucun intérêt pour un utilisateur d'avoir 5 custom-fonts sur une page. (ok c'est rare qu'un DA en demande autant ... )
Ca coûte en requêtes et en *repaint*, sans parler du FOUT (Flash of unstyled text : https://css-tricks.com/fout-foit-foft/) qui donne une mauvaise expérience du site (effet laggy)

Sur AlloCiné, nous utilisons une méthode trouvée par Smashing Magazine, pour gérer nos customs fonts.
Au premier chargement pour un utilisateur, nous requettons les custom-fonts après le *load*. Elles sont alors stockées dans le localStorage mais pas chargées :
**Une requête, pas de FOUT et le render pas impacté.**
Lors de le seconde page vue par l'utilisateur nous allons très tôt (depuis le *head*) chercher ces fonts dans le localStorage et nous les chargeons :
**Pas de requête, toujours pas de FOUT et les polices correctement affichées.**

D'autre méthodes existes, il faut trouver celle qui correspond le mieux à vos besoins / possibilités.


# ... et les Scripts Tiers (ressources externes)

Les ressources externes sont pratiquement impossibles à éviter sur un site (et réellement impossible pour un Pure Player) : 

- tracking
- adserver
- share (Facebook / Twitter / ...)
- module de commentaires
- ...

Les documentations sur ces script demandent en général de mettre ces derniers dans le *head* de nos pages. Si le script en question n'est pas complètement "old school" de rajouter l'attribut "async".

Il faut savoir que la balise *script* est bloquante.
Cela veux dire que le parser du navigateur s'arrête lorsqu'il trouve une balise script, et ne reprendra que lorsque l'appel du script sera fait et le script exécuté, ralentissant donc le render.

Donc si un script tier dans le *head* de vos pages met 1s à se charger et s'executer, vous venez de faire perdre 1 seconde à votre utilisateur.

> A second delay in page response decreases customer satisfaction by 16%

De plus en général, ces scripts une fois exécutés viennent modifier le DOM et donc créer des *reflow* / *repaint* qui peuvent donner un effet "laggy" à vos pages.

Pour éviter cela, nous pouvons ajouter l'attribut *async* sur ces scripts.
L'attribut *async* ne va bloquer le parsing de l'HTML que lors de l'execution du script (et non plus pendant le chargement du script). **GOOD, BUT NOT ENOUGH FOR ME...**

Du coup si on peut utiliser *async* on peut sûrement utiliser *defer* (attention tout dépend de l'architecture de votre site et de ce qui est fait par ces scripts et à quel moment ...)
Defer ne bloque par le parsing pendant le chargement du script et ne l'exécute qu'après le *domContentLoaded*, donc à la fin du dessin de la page... 

*Defer* et *Async* sont donc, d'une certaine manière, une façon de "lazyloader" les scripts tiers en fonction de vos besoins.
Voici une petite doc pour bien comprendre les deux : https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html. De plus, *Defer* execute les scripts dans l'ordre d'apparition dans le DOM.

**SO DEFER IS GOOD, COULD BE ENOUGH, BUT STILL WE CAN DO BETTER**

On ne va pas se contenter de simplement "lazyloader" les ressources bloquantes pour le render...
On veut potentiellement aussi ne pas charger un script externe si on n'en a pas besoin.

Exemple : j'utilise un service tier pour les commentaires sur mon site. Les commentaires se trouvent à la fin de mon contenu. Pourquoi prendre de la ressource navigateur et de la bande passante à l'utilisateur, qui ne voudra peut-être même pas commenter, et pour un module invisible au dessus de le ligne de flottaison ???
Réponse, on peut s'en passer. Nous allons charger ce script, et l'executer, uniquement au clique sur un bouton "commenter" via le JS.

Nous venons de gagner une requête, et un *reflow* / *repaint* pour l'utilisateur si il n'utilise pas les commentaires.
Si il utilise les commentaires, le *reflow* / *repaint* et l'appel du script se fera à un moment où le navigateur n'est pas aussi occupé que lors du chargement.

Certaines ressources ne pourront pas se charger sur une action utilisateur (pub / tracking / ...). Dans ces cas là, nous pouvons utiliser la même logique que pour nos commentaires mais sur un événement navigateur comme le *domContentLoaded* ou le *load*.


# WebPerf, ce n'est que le début ...

Toutes ces optimisations sont relativement simple à mettre en place et permettent d'avoir un vrai gain en terme de performance web.

### Les points à retenir

- limiter un maximum les requêtes
- limiter un maximum la taille des requêtes prioritaires.
- le contenu du site avant les services tiers (asynchrone powaaa)
- contenir l'HTML et les CSS au minimum nécessaire
- favoriser le contenu au dessus de la ligne de flottaison

### Les services tiers handicapant

Il est difficile de se positionner correctement en terme de WebPerf, quand le site est dépendant de service tiers comme la publicité. Le mieux est donc d'appliquer les bonnes pratiques et de garder un moyen de tester un site avec et sans ces services.

### Et le JS dans tout ça ?

On l'a vu, le JS va nous permettre de faire beaucoup de chose pour améliorer et limiter le chargement des ressources non prioritaires.
Mais si le fichiers JS est lui même une ressources nécessaire au bon fonctionnement du site, il ne déroge pas aux bonnes pratiques (minification, fichier JS par template de page, webchunk ...). C'est la suite des optimisations Front.
