⚠️ Cet article est marqué comme obsolète
Après vous avoir fait un petit état de l'art des préprocesseurs CSS historiques et vous avoir parlé des postprocesseurs, je me dois de vous montrer le chemin qui me semble le plus pertinent aujourd'hui, en 2014, maintenant que les spécifications CSS sont découpées en modules qui peuvent du coup avancer (plus rapidement) chacune de leur côté.
Bon après faut avouer que chez {p!} on fait genre on est des hipsters hackers, du coup quand on voit que Sass est le game changer of the year et que tout le monde l'adopte, on se doit de rester hipster. Du coup on est obligés de passer à autre chose.
Mise à jour du 11 décembre 2014: depuis le temps où j'ai rédigé cette article, j'ai travaillé sur un transpileur CSS future-proof, basé sur PostCSS. Je vous invite à jeter au project cssnext.
Je vais ici utiliser le mot préprocesseur, alors que certains trouveront postprocesseurs plus pertinent, car pour moi le préprocessing reste simplement une étape avant le processing de nos feuilles de styles par les navigateurs.
Cela dit il faut bien distinguer les étapes :
Revenons rapidement sur ces 3 points :
Ici je parle d'un langage, car superset ou pas, on a des éléments nouveaux,
incompréhensibles par le navigateur (ex: @if
, @foreach
...).
Note: Je case Stylus dans le langage spécifique car il n'est pas vraiment compatible, ne tolérant pas l'indentation :
.Block {
prop: value;
}
.Block-element {
prop: value;
}
/* 💥 BOOM ! CA PAS MARCHER EN STYLUS */
Ici on conserve la syntaxe CSS, le parsing est donc simple est connu. Mais attention on peut avoir un résultat non compréhensible par certains navigateurs (ex: unité REM sur IE 8, variables CSS...).
À la différence de la solution du dessus, on reste ici avec un langage à la syntaxe simple et connue, avec des spécifications (en brouillon ou pas).
Dès que c'est possible on va rendre le CSS encore plus compatible en ajoutant
des fallbacks (roues de secours) à tout va. Ainsi par exemple pour assurer une
utilisation de l'unité REM, on peut (en roue de secours) doubler nos valeurs en
px
. Autre exemple avec les variables CSS natives : on peut (pour une
utilisation simpliste) faire un prérendu et mettre des roues de secours là où
c'est possible.
Ici on a blindé notre feuille de fallback, on est (en théorie) sensé avoir un support "au mieux".
Allons à l'essentiel : pour créer votre préprocesseur on va utiliser une bibliothèque. À ce jour nous avons le choix entre Rework ou PostCSS qui permettent d'inspecter et de manipuler nos CSS. Tout deux sont des bibliothèques écrites en JavaScript (NodeJs).
Pour la petite histoire, Autoprefixer est à l'origine de PostCSS. En effet les premières versions utilisaient Rework, mais à cause de limitation dues à l'API de Rework, l'auteur d'Autoprefixer a décidé de créer son propre moteur, qui a une API presque plus sympa il faut le dire (il faudra voir comment évolue Rework).
D'un autre côté Rework étant plus ancien, il possède un écosystème plus fourni et répondra donc mieux à nos besoins.
Cela étant dit, avec l'approche futureproof nous pourrons switcher de Rework à PostCSS sans problème et surtout sans toucher à nos feuilles de styles. Il suffira de trouver un plugin équivalent (ex: rework-vars et postcss-vars font la même chose).
Pour des raisons d'écosystème, je partirai sur Rework histoire de ne pas réinventer la roue.
Rework prend une chaîne CSS en entrée, produit un AST (arbre de syntaxe abtrait) de notre CSS et nous fournis une API pour le manipuler.
var rework = require("rework");
var unPlugin = require("rework-BIDULE");
// usage simple
var css = rework("html { font-size: 2rem}")
.use(unPlugin)
.toString();
Un plugin Rework n'est rien d'autre qu'une fonction JavaScript. L'exemple suivant remplacera toutes les couleurs de texte par du noir.
var monPlugin = function plugin(ast, reworkInstance) {
ast.rules.forEach(function(rule) {
// dans notre cas on ne veut que travailler sur des règles
if (rule.type !== "rule") return;
rule.declarations.forEach(function(declaration, index) {
if (declaration.property === "color") {
declaration.value = "#000";
}
});
});
};
Il est certain que l'exemple ci-dessus ne doit pas vous exciter beaucoup. Moi non plus. Je n'ai d'ailleurs pas du tout accroché lorsque Rework est apparu par manque de compréhension. Ou plutôt par manque d'imagination.
Pour un exemple plus costaud je vous invite à regarder le code rework-vars.
Heureusement pour nous, nous avons un écosystème. Donc pour un usage classique (écrire du CSS futureproof ou ajouter quelques petites améliorations) il n'y aura pas besoin d'écrire de plugin, juste en utiliser fera l'affaire.
Rework embarque en natif quelques plugins. Il faudra tout de même les activer (on voit comment juste après).
@extend
quoi).@2x
.rgba(#fc0, .5)
.height: @width
)url()
s via
une fonction JavaScript.On a déjà plus d'une soixantaine de plugins Rework disponible sur NPM en plus des plugins natifs.
Si vous souhaitez écrire des CSS sans pour autant vous limiter à certaines implémentations, ces plugins devraient vous plaire.
rework-vars permet un usage des
variables CSS. Ce plugin est (pour
l'instant) restreint aux déclarations à la racine (:root
). Cela permet
toutefois un usage classique de variables globales. Et de se séparer de Sass ou
Less si on utilise pas beaucoup plus que des variables.
rework-calc ajoute les résultats des
opérations calc()
quand c'est possible (même unité). Très pratique
spécialement couplé avec rework-vars pour ajouter un peu de math via vos
variables.
rework-npm nous parse @import
comme
on l'aime. Peut taper dans votre dossier de sources ou en plus dans
node_modules
(pratique pour utiliser
normalize.css
via npm par exemple). Il existe aussi
rework-importer qui amène quelques
différences de syntaxe.
rework-rem-fallback ajoute un fallback sur les unités REM. Pratique si vous voulez utiliser REM mais que vous devez supporter IE 8.
rework-color-function permet de manipuler les couleurs via les nouvelles fonctions en cours de spécifications (hue, saturation, lightness, whiteness, blackness, tint, shade, blend, blenda, contrast).
rework-mixin-opacity ajoute
l'opacity
pour IE 8.
Sans pour autant partir en vrille vers des @if
ou @each
, voici quelques
plugins qui peuvent ajouter un peu de beurre dans les épinards :
rework-parent permet de référencer le
sélecteur précédent via &
. Pratique dans pour les media-queries ou pour les
états (:hover etc).
rework-breakpoints permet de
spécifier des media-queries via des breakpoints prédéfinis. Pratique en
attendant un plugin gérant les
@custom-media
.
rework-hex-alpha permet de spécifier des couleurs avec alpha sous la forme #rrggbbaa.
rework-clearfix permet d'utiliser
clear: fix
via l'insertion automatique du
micro clearfix de @necolas.
rework-assets permet de copier les assets référencés. Pratique avec rework-npm et des modules externes.
rework-namespace-css
permet de namespacer vos CSS par une classe sur <html>
.
rework-namespace permet
de namespacer vos CSS par un préfixe. Pratique avec BEM
lorsque vous préfixez par votre .org-
.
rework-classmap permet de renommer des classes via du mapping. Avec ça on pourrait presque avoir un code Bootstrap propre.
rework-palette permet d'ajouter une palette de couleur personnalisée (via des noms de couleurs).
rework-deduplicate permet de supprimer les règles dupliquées.
rework-split-media permet de couper les contenus des media queries dans d'autres fichiers.
rework-move-media permet de regrouper les contenus des media queries. Pas très utile car gzip fera aussi bien.
En utilisant le parser Rework, on peut faire plus que des ajustements ou du remplacement: on peut balancer des erreurs.
En partant dans d'autres directions on pourrait réaliser des statistiques sur nos CSS (nombre de sélecteurs, de couleurs utilisées etc) comme le fait CSS Stats.
Retrouvrez en plus de la recherche via npm (qui sera la plus à jour), une liste des plugins et utilitaires sur le wiki de Rework.
Maintenant que nous avons vu comment utiliser Rework et quels sont les plugins les plus sympas, on se faire un petit fichier pour automatiser tout ce process.
Plutôt que de réinventer la roue comme l'a fait Pleeease (en gérant un watcher et tout le tralala), on va plutôt partir comme Myth.io ou Styl (successeur spirituel de Stylus) qui se concentrent sur le rendu et non le workflow pour générer ce rendu.
Nous allons donc faire simple et efficace avec une task gulp. Ceci pourrait bien entendu être aussi bien fait avec grunt ou même make et watchman.
$ mkdir monrework && cd monrework
# on crée un package.json pour sauvegarder les références des paquets qu'on va utiliser
$ npm init
# on install gulp, autoprefixer et rework & co en les sauvegardants dans la partie "devDependencies"
$ npm i -D minimist gulp gulp-util gulp-plumber gulp-autoprefixer gulp-rework rework-npm rework-vars rework-calc rework-color-function rework-rem-fallback rework-parent rework-ie-limits
# on créé notre fichier vide
$ mkdir src && mkdir src/styles && touch src/styles/index.css
Ensuite il nous reste à faire notre petit Gulpfile.js
var options = require("minimist")(process.argv.slice(2));
var gulp = require("gulp");
var util = require("gulp-util");
var plumber = require("gulp-plumber");
var rework = require("gulp-rework");
var reworkPlugins = {
atimport: require("rework-npm"),
parent: require("rework-parent"),
vars: require("rework-vars"),
calc: require("rework-calc"),
colorFn: require("rework-color-function"),
remFallback: require("rework-rem-fallback"),
ieLimits: require("rework-ie-limits"),
};
var autoprefixer = require("gulp-autoprefixer");
gulp.task("styles", function() {
// ici on prend toutes les CSS à la racine
// on considère que celles dans des sous dossiers sont à importer
return gulp
.src("./src/styles/*.css")
.pipe(opts.production ? plumber() : util.noop())
.pipe(
rework(
reworkPlugins.atimport({ dir: "./src/styles/" }),
rework.colors(),
rework.references(),
rework.ease(),
rework.inline,
reworkPlugins.parent,
reworkPlugins.vars(), // notez que certains plugins nécessitent d'être éxecutés (retournant une fonction dynamique)
reworkPlugins.calc,
reworkPlugins.colorFn,
reworkPlugins.remFallback(),
reworkPlugins.ieLimits,
),
)
.pipe(autoprefixer())
.pipe(gulp.dest("./dist/styles"));
});
gulp.task("default", ["styles"], function() {
gulp.watch("./src/css/**/*", ["styles"]);
});
Ensuite il ne reste plus qu'à lancer Gulp au besoin qui s'occupera d'éxecuter le preprocessing au démarrage et lors des changements de fichiers. Il ne reste plus grand chose à faire si ce n'est ajouter livereload en plus pour avoir le petit process aux petits oignons.
$ gulp
[gulp] Using gulpfile ~/Development/monrework/Gulpfile.js
[gulp] Starting 'styles'...
[gulp] Finished 'styles' after 49 ms
[gulp] Starting 'default'...
[gulp] Finished 'default' after 4.16 ms
Le watch est lancé, on peut remplir notre fichier CSS
:root {
--fontSize: 1rem;
--lineHeight: 1.5rem;
--color-highlight: rgba(#f00, 0.8);
}
html {
width: 100%;
height: @width;
font-size: var(--fontSize);
}
p {
margin: calc(var(--lineHeight) / 2) 0;
}
a {
color: var(--color-highlight);
transition: all 500ms ease-out-back;
}
&:hover {
color: color(var(--color-highlight) lightness(-10%));
trasnform: rotate(1deg);
}
Et on obtiendra
html {
width: 100%;
height: 100%;
font-size: 16px;
font-size: 1rem;
}
p {
margin: 12px 0;
margin: 0.75rem 0;
}
a {
color: rgba(255, 0, 0, 0.8);
-webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition: all 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
a:hover {
color: rgba(204, 0, 0, 0.8);
transform: rotate(1deg);
}
Gardez bien en tête qu'avec le code CSS d'origine, vous avez (en majeur partie) un code futureproof. D'ici quelques temps on pourra supprimer une grande partie des plugins et les navigateurs prendront le relais 😉.
Bon alors, on Less tomber Sass ?