A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.md 24KB

title: Ébauche de workflow Gulp : tâches courantes, unCSS, includes HTML et critical-CSS url: http://www.alsacreations.com/tuto/lire/1685-ebauche-de-workflow-gulp-taches-uncss-includes-critical-css.html hash_url: dc35a12d6b

Préambule : cet article part du principe que vous n’êtes pas totalement étranger aux notions et outils tels que LESS, NodeJS, Gulp ni à la ligne de commande, il ne s'agit d'un tutoriel de découverte de ces outils mais d'usage en environnement professionnel.

Introduction

Au sein de l’agence web Alsacreations.fr, nous avons instauré un processus de travail (un “workflow”) composé de langage LESS, compilé avec des tâches Gulp et saupoudré de conventions internes et de KNACSS.

Le site goetter.fr est mon site personnel, mon bac à sable et mon espace de test pour moults expériences web.
La version actuelle du site est très artisanale et manuelle. J’ai voulu tester la mise en place d’un workflow automatisé afin de profiter de toutes les tâches courantes (compilation LESS, minification, préfixes CSS automatiques, concaténation des fichiers JS, optimisation des images) tout en testant deux fonctionnalités intéressantes : les includes de fichiers en HTML ainsi que l’outil de perf web prôné par Google : “critical CSS”.

Cela m’a pris une journée de reprendre tout le workflow de mon site perso. Je vous la partage ici…

goetter.fr

L’environnement Gulp

Dans la version précédente de mon site personnel, mon dossier de travail était le même que celui de production : je faisais simplement attention à ne pas mettre en prod les fichiers inutiles (CSS non minifié, images non optimisées) et j'employais le logiciel Prepros.io pour compiler mes fichiers.

Avec le choix de Gulp, mon processus de travail est aujourd'hui différent dans la mesure où je scinde deux environnements distincts :

  • tous mes fichiers en développement sont dans _src/
  • les fichiers compilés, à envoyer en prod sont tous dans _dist/

Gulp est un outil d'automatisation de tâches (un "task manager") basé sur nodeJS. Une fois NodeJS en place, Gulp s'installe en ligne de commande :

npm install gulp -g

Pour fonctionner, Gulp nécessite deux fichiers de travail :

  • package.json (contient tous les plugins nécessaires aux tâches à éxécuter)
  • gulpfile.js (exécute les tâches)

Pour rappel, je ne prévois pas de rentrer dans les détails de ce que sont gulp, gulpfile.js et package.json car cet article n'est pas une introduction à ces outils. Si ces termes vous sont totalement étrangers, je vous invite à vous renseigner sur les très nombreux écrits sur ce sujet (on me souffle dans l'oreillette qu'il va prochainement y avoir un article à ce sujet sur Alsacréations).

J’utilise à présent Gulp pour réaliser toutes mes tâches courantes (pas bien complexes mais souvent répétitives), et voici quels sont les plugins prévus au sein de mon fichier package.json :

  • “gulp”
  • “gulp-less” (compilation LESS vers CSS)
  • “gulp-autoprefixer” (ajout des préfixes nécessaires)
  • “gulp-minify-css” (minification CSS)
  • “gulp-rename” (renommage en “.min.css”)
  • “gulp-uglify” (minification JS)
  • “gulp-imagemin” (optimisation des images)
  • “gulp-html-extend” (includes HTML)
  • “gulp-uncss” (suppression des CSS non utilisés)
  • “critical” (CSS inline)

Les tâches courantes

Toutes les tâches suivantes sont inclues dans mon fichier gulpfile.js et exécutées sur demande ou automatiquement (watch) en ligne de commande.

tâches CSS

  • LESS
  • autoprefixer
  • minification

La tâche “css” du fichier gulpfile.js :

// Tâche "css" = LESS + autoprefixer + minify
gulp.task('css', function() {
  return gulp.src(source + '/assets/css/styles.less')
    .pipe(less())
    .pipe(autoprefixer())
    .pipe(rename({
      suffix: '.min'
    }))
    .pipe(minify())
    .pipe(gulp.dest(prod + '/assets/css/'));
});

Explications : mon fichier de travail (styles.less) est tout d'abord compilé en CSS classique, puis préfixé, puis renommé en .min.css avant d'être minifié et placé dans mon dossier de production.

tâches JavaScript

  • concaténation
  • minification (uglify)

La tâche “js” du fichier gulpfile.js :

// Tâche "js" = uglify + concat
gulp.task('js', function() {
  return gulp.src(source + '/assets/js/*.js')
    .pipe(uglify())
    .pipe(concat('global.min.js'))
    .pipe(gulp.dest(prod + '/assets/js/'));
});

Explications : tous les fichiers JavaScript du dossier  /js/ sont minifiés pour concaténés (regroupés) en un seul fichier global.min.js et placé dans mon dossier de production.

tâches d’optimisation d’images

  • optimisation PNG, JPG, SVG

La tâche “img” du fichier gulpfile.js :

// Tâche "img" = Images optimisées
gulp.task('img', function () {
  return gulp.src(source + '/assets/img/*.{png,jpg,jpeg,gif,svg}')
    .pipe(imagemin())
    .pipe(gulp.dest(prod + '/assets/img'));
});

unCSS pour alléger les fichiers

unCSS est un outil (disponible en versions grunt et gulp) permettant de supprimer les styles CSS non utilisés au sein de votre projet.

Il s'agit d'une véritable bénédiction lorsque vous travaillez à l'aide de frameworks CSS tels que Bootstrap ou KNACSS car il va réduire drastiquement le poids de vos fichiers CSS :

If you're building a #twitterbootstrap page with #grunt or #gulp, consider using #uncss. > CSS file reduced from 150kB to 11kB! #Javascript

— Sascha Sambale (@mastixmc) 11 Mars 2015

unCSS fonctionne bien évidemment sur un ensemble de fichiers et fait très bien son boulot... à condition que vous n'ayez oublié aucun fichier dans la boucle !

Sur le site de goetter.fr, la feuille de style CSS minifiée est passée de 16ko à 10ko après l'action de unCSS, soit une réduction de 40%. Imaginez le résultat sur des gros fichiers de centaines de ko. Le pire est que le site fonctionne toujours, rien n'est cassé :)

unCSS a été intégrée dans ma tâche "css" ainsi :

// Tâche "css" = LESS + autoprefixer + unCSS + minify
gulp.task('css', function() {
  return gulp.src(source + '/assets/css/*.less')
    .pipe(less())
    .pipe(autoprefixer())
    .pipe(uncss({
      html: [source + '/{,_includes/}/{,conf/}/{,livres/}*.html']
    }))
    .pipe(rename({
      suffix: '.min'
    }))
    .pipe(minify())
    .pipe(gulp.dest(prod + '/assets/css/'));
});

Explications : tous les fichiers HTML à la racine du site ainsi que ceux situés dans les dossiers /conf, /livres et /_include seront testés par un navigateur fantôme. Tous les styles qui ne sont pas utilises au sein de ces fichiers sont supprimés.

Des includes en HTML

Passons à présent à des tâches moins classiques, voire carrément spécifiques à mes besoins et envies.

En tant qu'intégrateur HTML/CSS, j’ai toujours été quelque peu frustré de devoir passer par un langage serveur pour faire des inclusions de fichiers partiels (header, footer, nav, etc.).

Bien-sûr, il est possible de passer par JavaScript, AJAX, webcomponents / polymer ou des langages tels Jade, HAML ou Handlebars pour atteindre cet objectif, mais en toute honnêteté je ne suis pas fan de rajouter une couche de complexité à mon workflow et je ne supporte pas les syntaxes “à la Jade”.

Je veux écrire du bête HTML et avoir des include de fichiers. Je veux le beurre et l’argent du beurre. Et là, sur mon site perso, je peux me le permettre.

Par chance, puisque mon workflow est déjà basé sur NodeJS / Gulp, il existe des plugins pour parvenir à mes fins.

L’un de ces plugins est “Gulp HTML extend”.

À l’aide de simples commentaires HTML, il offre (au-moins) deux fonctionnalités qui m’intéressent tout particulièrement :

  • les inclusions de fichiers partiels
  • l’utilisation de variables

Le principe est simplissime : les instructions se présentent sous forme de commentaires HTML (pour préserver la syntaxe et la validité), par exemple <!-- @@include fichier.txt --> pour inclure un fichier dans le document, ou bien <!-- @@var variable --> pour inclure une variable.

Voici un exemple de page index.html avant compilation :

<head>
  <!-- @@include _includes/head.html {"title": "Mon site web personnel", "path": "../"} -->
</head>

Le fichier inclus head.html :

<meta charset="UTF-8">
<title><!-- @@var title --></title>
...
<link rel="stylesheet" href="<!-- @@var path -->assets/css/styles.min.css" media="all">

Et le résultat compilé index.html en prod :

<meta charset="UTF-8">
<title>Mon site web personnel</title>
...
<link rel="stylesheet" href="../assets/css/styles.min.css" media="all">
</head>

Voici la tâche “html” du fichier gulpfile.js correspondant à mes besoins :

// Tâche "html" = includes HTML
gulp.task('html', function() {
  return  gulp.src(source + '/{,conf/}/{,livres/}*.html')
    // Generates HTML includes
    .pipe(extender({
      annotations: false,
      verbose: false
    })) // default options
    .pipe(gulp.dest(prod))
});

Explications : tous les fichiers HTML à la racine du site ainsi que ceux situés dans les dossiers /conf et /livres seront compilés en tenant compte des inclusions et des variables fournies.

La documentation et les options de ce plugin : https://www.npmjs.com/package/gulp-html-extend

Critical CSS

Autre plugin que j’ai pu mettre en place sur mon site perso : “Critical CSS”.

Vous avez déjà certainement été confronté à l’un des conseils récurrents de Google PageSpeed Insights (l’outil de perf web très pratique pour tester son site mobile et desktop) qui est Éliminer les codes JavaScript et CSS qui bloquent l’affichage du contenu au-dessus de la ligne de flottaison.

L’explication est celle-ci : JavaScript et CSS retardent l’affichage de la page tant qu’ils ne sont pas chargés.
Google considère que la partie visible d’une page web, c’est à dire tout se qui se trouve au-dessus de la ligne de flottaison (ou “above the fold”), devrait être affiché instantanément, soit :

  • en différant ou en rendant les ressources asynchrones (async, defer),
  • soit en éliminant toutes les requêtes de styles CSS “critiques” (= ceux participant à l’affichage du design au dessus de la ligne de flottaison). Pour cela, la méthode est d’insérer ces CSS “critiques” directement dans la page HTML (on “inline” les CSS dans l’élément <style> comme au bon vieux temps des newsletter).

ligne de flottaison

Sans surprise, la méthode d‘“inliner” les CSS dans l’élément <style> présente un sacré paquet d’inconvénients (un peu les mêmes que l’on retrouve avec les data-URI) :

  • les fichiers HTML sont beaucoup plus lourds
  • ils sont également moins lisibles et plus difficiles à maintenir
  • c’est moche

La contrepartie est que les avantages... ne sont pas négligeables non plus :

  • les résultats sont très probants en terme de performance d’affichage (des gains de parfois plusieurs secondes)
  • de très gros sites comme The Guardian ont réussi à charger leur page d’accueil en moins de 1 seconde grâce à l’optimisation de leurs CSS “critiques”.
  • il est possible d’automatiser ce processus

Quelques ressources intéressantes sur ce sujet qui ne l’est pas moins :

Si vous ne deviez retenir qu’une seule ressource, je vous invite vivement à vous imprégner de la conférence de Patrick Hamann (The Guardian), “CSS and the critical path” dont voici les slides et la vidéo associée.

Critical CSS en pratique

Il existe deux outils connus et suffisamment maintenus et testés pour réaliser la tâche de “inliner des CSS” : Critical d’Addy Osmani et CriticalCSS de Filament Group. Les deux proposent une version Grunt et Gulp.

Pour ma part et sur mon site perso goetter.fr, j’ai utilisé le premier de ces deux outils, Critical, en version Gulp bien sûr.

Voici ma tâche “critical” du fichier gulpfile.js :

// Tâche "critical" = critical inline CSS
gulp.task('critical', function() {
  return  gulp.src(prod + '/*.html')
    .pipe(critical({
      base: prod,
      inline: true,
      width: 320,
      height: 480,
      minify: true
    }))
    .pipe(gulp.dest(prod));
});

Le principe général est celui-ci :

  • un navigateur fantôme parcourt la(es) page(s) concernée(s) dans les dimensions précisées (ici 320x480)
  • il en déduit les CSS “critiques”, participant à l’affichage au dessus de la ligne de flottaison
  • il les extrait et les insère directement (minifiés) au sein d’une balise <style> dans la page
  • le “reste” de la feuille de style est chargée de manière asynchrone via JavaScript et LoadCSS
  • pour les navigateurs sans JS, la feuille de style est chargée au sein d’un <noscript>
  • et tout ça tout seul comme un grand !

Dans mon cas, j’ai décidé que seront concernés tous les fichiers à la racine du site (et uniquement ceux-là).

Critical : efficace ?

Maintenant que l’on s’est bien amusé, passons quelques tests pour découvrir si ça en valait vraiment la peine…

D’après Google PageSpeed Insights

Google PageSpeed Insights donne une “note globale de performance” sur 100 à votre page en version mobile et desktop.

  • Avant Critical :
    • note mobile : 91/100
    • note desktop : 97/100
  • Après Critical :
    • note mobile : 99/100*
    • note desktop : 99/100* (*le 1% restant est dû à… la présence du script Google Analytics)

Illustration avant critical :

avant critical

Illustration après critical :

après critical

D’après Dareboost

Dareboost testé sur mobile (Nexus 5, en 3G et en France) attribue une note totale de 92% avant et après critical.

Avant Critical :

  • chargement complet en 2.30s (visuellement complet en 1.37s)
  • speed index de 607

Après Critical :

  • chargement complet en 2.01s (visuellement complet en 0.80s)
  • speed index de 436

Illustration : comparaison avant et après :

avant et après critical

La tâche de mise en production

Voici la tâche complète de mise en production de l’ensemble des contenus de _src/ vers _dist/ que j’emploie :

// Tâche "prod" = toutes les tâches ensemble
gulp.task('prod', gulpsync.sync(['css', 'js', 'html', 'critical','img']));

En un clic, voici les actions menées automatiquement :

  • styles LESS compilés en CSS
  • préfixes CSS3 ajoutés au besoin
  • concaténation et minification des fichiers CSS
  • concaténation et minification des fichiers JavaScript
  • optimisation des images PNG, JPG et SVG
  • compilation des fichiers HTML pour permettre les inclusions de fichiers partiels et des variables
  • “inline” des styles CSS “critiques” directement dans la page

Et pour finir, voici la tâche "watch" qui va surveiller en continu toutes les modifications opérées sur les fichiers LESS et HTML et qui va automatiquement lancer les tâches correspondantes :

// Tâche "watch" = je surveille LESS et HTML
gulp.task('watch', function () {
  gulp.watch(source + '/assets/css/*.less', ['css']);
  gulp.watch(source + '/{,conf/}/{,livres/}*.html', ['html']);
});

Si l'envie vous prend et si vous avez envie de tester tout ça chez vous, vous pouvez récupérer les fichiers package.json et gulpfile.js sur mon espace Gist.

Télécharger les fichiers

Que conclure de tout ça ?

Disposer d'un environnement de travail automatisé (au moins en partie) est devenu une évidence de nos jours où nous ne pouvons plus nous contenter d'écrire du HTML et du CSS. L'industrialisation de notre métier exige d'aller vite, de tester sur de nombreux devices, d'éviter les régressions, de maintenir son code et de le rendre le plus performant possible.

Des outils tels que Grunt, Gulp ou Brunch offrent un "workflow" automatique pour la plupart des tâches courantes.

Pour ce qui est du reste, par exemple inclure des fichiers en HTML ou rendre inline ses CSS "critiques"... il y a aussi une application pour ça ! :)