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.
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…
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 :
_src/
_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
:
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.
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.
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.
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 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.
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 :
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
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 :
async
, defer
),<style>
comme au bon vieux temps des newsletter).
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
) :
La contrepartie est que les avantages... ne sont pas négligeables non plus :
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.
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 :
<style>
dans la page<noscript>
Dans mon cas, j’ai décidé que seront concernés tous les fichiers à la racine du site (et uniquement ceux-là).
Maintenant que l’on s’est bien amusé, passons quelques tests pour découvrir si ça en valait vraiment la peine…
Google PageSpeed Insights donne une “note globale de performance” sur 100 à votre page en version mobile et desktop.
Illustration avant critical :
Illustration après critical :
Dareboost testé sur mobile (Nexus 5, en 3G et en France) attribue une note totale de 92% avant et après critical.
Avant Critical :
Après Critical :
Illustration : comparaison avant et après :
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 :
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.
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 ! :)