CSS critique « inline » avec Parcel

Publié par Maël Genevrais le

Même avec les « bundlers » actuels et les fichiers optimisés générés, les performances peuvent être dégradées par des fichiers volumineux ou de mauvaises conditions réseaux. C'est pourquoi il est essentiel d'extraire le CSS nécessaire à l'affichage du contenu dit critique (qui se situe au dessus de la ligne de flottaison) afin que les navigateurs soient en capacité de l'afficher le plus rapidement possible.

Pré-requis

Compilation avec Parcel

Parcel permet comme beaucoup d'autres « bundlers » de compiler des fichiers (tels que CSS, Javascript, etc...) afin d'optimiser le chargement d'un site web. Plus simple d'utilisation que pas mal de ses concurrents, il est aussi moins configurable car très automatisé dans son fonctionnement de base. Mais sous cette apparence de simplicité se cache un outil puissant que l'on peut modeler suivant nos besoins.

Prenons l'exemple d'un site simple avec la structure suivante :

index.html :

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS « inline » avec Parcel</title>
<link rel="stylesheet" href="./scss/index.scss">
</head>
<body>
<header>
<a href="#accueil">Accueil</a>
<a href="#articles">Articles</a>
</header>
<main>
<h1>Bienvenue !</h1>
<h2>CSS « inline » avec Parcel</h2>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Accusamus rem, cupiditate dolore cumque placeat beatae officia porro explicabo. Fugiat qui nisi perferendis ducimus facere quas eaque, voluptatum suscipit quam quibusdam!</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Accusamus rem, cupiditate dolore cumque placeat beatae officia porro explicabo. Fugiat qui nisi perferendis ducimus facere quas eaque, voluptatum suscipit quam quibusdam!</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Accusamus rem, cupiditate dolore cumque placeat beatae officia porro explicabo. Fugiat qui nisi perferendis ducimus facere quas eaque, voluptatum suscipit quam quibusdam!</p>
</main>
<footer class="bg-light">
<a href="#social">Réseaux sociaux</a>
<a href="#blog">Blog</a>
</footer>
</body>
</html>

index.scss :

scss
a {    
color: #fff !important;    
text-decoration: none;    
background-color: transparent;
}

header {    
background: #00485e;    
padding: 0.5rem 1rem;    
font-size: 1.5rem;    
display: flex;    

a {        
margin-right: 2rem;    
}
}

main {    
font-size: 2rem;    
padding: 1rem;    
min-height: 150vh;
}

h1,h2 {    
font-weight: 500;    
line-height: 1.2;    
margin-top: 0;
}

h1 {    
font-size: 3rem;    
margin-bottom: 1em;
}

h2 {    
font-size: 3rem;    
margin-bottom: 1em;
}

p {    
margin-top: 0;    
margin-bottom: 3em;
}

footer {
padding: 0.5rem 1rem;
display: flex;    

a {
color: #000 !important;
margin-right: 2rem;    
}
}

@import "~bootstrap/scss/bootstrap";

package.json :

json
{
[...]
"devDependencies": {
"replace": "^1.2.0"
},
"dependencies": {       
"bootstrap": "^4.5.0"  
}
}

Installation de Parcel :

Tout d'abord il est nécessaire d'installer le paquet Parcel pour pouvoir l'utiliser.

bash
npm install parcel-bundler -g

Lancement d'une compilation Parcel avec serveur de développement :

Pour nous aider à développer notre site, Parcel nous propose de lancer un serveur rechargeant automatiquement notre page en cas de modifications apportées à notre code :  

bash
parcel ./public/index.html

Notre site est maintenant accessible à l'adresse : http://localhost:1234

Audit

Lors de l'audit du site (réalisé avec Lighthouse dans les DevTools de Chrome) on peut s'apercevoir que le fichier CSS (dont le nom a changé car il a été compilé par Parcel) est une ressource bloquant le rendu de la page :

Extraction du CSS avec Parcel

L'important est maintenant d'identifier le code CSS critique. Dans notre cas c'est assez simple, car seuls les éléments header et main (ainsi que les éléments les composant) sont au dessus de la ligne de flottaison. Nous allons donc extraire le CSS correspondant dans un nouveau fichier.

critique.scss :

scss
a {    
color: #fff !important;    
text-decoration: none;    
background-color: transparent;
}

header {    
background: #00485e;    
padding: 0.5rem 1rem;    
font-size: 1.5rem;    
display: flex; 
   
a {        
margin-right: 2rem;    
}
}

main {    
font-size: 2rem;    
padding: 1rem;    
min-height: 150vh;
}

h1,h2 {    
font-weight: 500;    
line-height: 1.2;    
margin-top: 0;
}

h1 {    
font-size: 3rem;    
margin-bottom: 1em;
}

h2 {    
font-size: 3rem;    
margin-bottom: 1em;
}

p {    
margin-top: 0;    
margin-bottom: 3em;
}

index.scss :

scss
footer {
padding: 0.5rem 1rem;
display: flex;

a {
color: #000 !important;
margin-right: 2rem;
}
}

@import "~bootstrap/scss/bootstrap";

Puis modifier le fichier index.html :

html
<link rel="stylesheet" href="./scss/critique.scss">
<link rel="stylesheet" href="./scss/index.scss">

Audit

Lors d'un nouvel audit voici le résultat :

Les deux fichiers CSS sont maintenant des ressources bloquant le rendu de la page. Pour y remédier il faut passer le code CSS du fichier critique.scss « inline » dans index.html et différer le reste du CSS. Ce qui donne dans index.html :

html
<link rel="stylesheet" href="./scss/critique.scss" inline>
<link rel="preload" href="./scss/index.scss" type="text/css" onload="this.rel='stylesheet'" as="style">

Et là vous me direz que le CSS critique n'est pas « inline » et vous aurez raison. Mais comme ce fichier est susceptible d'être modifié, il faut que ce soit Parcel qui s'occupe de placer automatiquement ce code dans index.html.

Pour en savoir plus sur le CSS différé : https://web.dev/defer-non-critical-css/

L'API Parcel à la rescousse

Parcel mets à disposition une API permettant d'écrire des règles de compilation avancées (https://parceljs.org/api.html).  

Nous allons créer un nouveau fichier build.js :

javascript
const Bundler = require('parcel-bundler');
const Path = require('path');
const replace = require('replace');
const fs = require('fs');

const entryFolder = Path.join(__dirname, './public');
const entryFiles = [Path.join(entryFolder, '/index.html')];
const options = {    
watch: false
// minify: true,
// sourceMaps: false
};
const inlineLinkRegex = new RegExp(/<link.* href="(.*)".* inline.*>/, 'g');

(async function () {

// Création du bundler
const bundler = new Bundler(entryFiles, options);

// Evénement emit par Parcel lorsque le build est terminé
await bundler.on('buildEnd', () => {

// Pour chaque fichier d'entrée, on va chercher si du CSS doit être placé inline        
entryFiles.forEach((file, index) => {

// Fichier de sortie correspondant
const outFile = Path.join(bundler.options.outDir, file.replace(entryFolder, ''));

// Contenu du fichier de sortie
const fileContent = String(fs.readFileSync(outFile));

// On cherche les balises link contenant l'attribut inline
const matches = fileContent.matchAll(inlineLinkRegex);

for (const match of matches) {

// Contient la balise link complète
const fullMatch = match[0];

// Contient l'attribut href de la balise link
const groupMatch = match[1];

// Chemin du fichier CSS compilé (dans le dossier de sortie)
const cssFilePath = Path.join(bundler.options.outDir, groupMatch);

// Contenu du fichier CSS correspondant à la balise link
const cssContent = fs.readFileSync(cssFilePath);

// On remplace la balise link par le contenu du fichier CSS correspondant
replace({
regex: fullMatch,
replacement: "<style>" + cssContent + "</style>",
paths: [outFile],
recursive: false,
silent: true,
});

// Suppression du fichier CSS compilé devenu inutile
fs.unlinkSync(cssFilePath);
}
});
});

// On lance le build pour compiler sans serveur de dev
const bundle = await bundler.bundle();

// On lance le build avec un serveur de dev
// Attention, le code de remplacement ne fonctionnera pas lors de la modification de vos fichiers
// const bundle = await bundler.serve();
})();

Il est maintenant possible de lancer une compilation :

bash
node build.js

Une fois la compilation terminée, dans le dossier de sortie (/dist par défaut) le fichier index.html contient le CSS correspondant au fichier critique.css.  

Attention, ce script n'a pas vocation à être utilisé en développement mais uniquement pour des compilations uniques (pour de la production par exemple).

Résultats

Lançons un dernier audit avec nos modifications :

Conclusion

Grâce à Parcel, nous avons vu qu'il est simple de mettre en place des actions pour optimiser les performances d'un site web. Sa légèreté et sa simplicité permettent de l'adapter au plus prêt d'un besoin spécifique en un temps relativement court.

Il est important de réaliser des Audits de performances lors du développement d'un site web, car en plus du ressenti utilisateur et de l'image que cela renvoi, elles ont un impact important en terme de référencement sur les moteurs de recherche.

Références