CSS et Unix

Regrouper plusieurs feuilles de styles ensemble sous Unix

À des fins de performance, cela peut-être une bonne idée de regrouper nos feuilles de styles pour réduire le nombre de requêtes HTTP.

Si avec HTTP 1.1, c'était bien vue de ne servir qu'un seul fichier CSS. Avec HTTP 2, il est conseillé de découper ses feuilles en plusieurs ballots hiérarchisés, plutôt qu'en un seul gros bloc de code.

À ce sujet, voir le banc d'essai de Harry Roberts: CSS and Network Performance.

Règle @import

La règle CSS @import, n'est pas conseillé en production pour justement une question de performance.

Alors quelle autre option nous reste-t-il si on veut découper nos styles sans passer par un préprocesseur?

Utilitaire cat

Pour concaténer des fichiers ensemble sous Unix il faut utiliser l'utilitaire cat.

cat - concatenate and print files

Avec peu de fichiers CSS, on peut simplement faire:

cat a.css b.css c.css d.css e.css f.css > styles.css

En voulant être plus concis cela serait:

cat *.css > styles.css

Mais en CSS l'ordre des déclarations est important. Nous ne voudrions pas neutraliser ou déclasser certains styles.

Dans les utilitaires du shell il y a un flux entrant (standard input ou stdin) et un flux sortant (standard output ou stdout). La lecture se fait par le stdin et l'écriture par le stdout. Il y a aussi un flux sortant pour les erreurs (standard error ou stderr).

Il est toutefois possible détourner ces flux. Comme par exemple avec < ou > en appliquant des redirections vers un fichier, comme dans mon exemple ci-haut, avec l'utilitaire cat, où l'on redirige la sortie vers le fichier styles.css. Sans cette redirection le résultat s'afficherait dans le terminal avec stdout.

La redirection d'entrée utilise le symbole <. Cela permet d'indiquer qu'un fichier sera passé en stdin.

Concaténer une liste de CSS avec while, read et <

Maintenant nous allons utilser une boucle de type while, combiné avec l'utilitaire read, pour concaténer un groupe de fichiers CSS.

Notre liste se trouve dans un fichier nommé global-styles.txt. Nous y avons inscrit une feuille de style par ligne:

global/tokens/typography.css
global/tokens/colors.css
global/tokens/grid.css

global/base.css
global/a11y.css

Ensuite, notre script sera dans un autre fichier que nous appellerons css.sh:

#!/bin/sh

while read -r line; do
  [ -n "$line" ] && cat "./$line"
done < global-styles.txt

Attention, il est important de s'assurer que ce fichier soit exécutable (chmod u+x css.sh), avant de lancer la commande:

./css.sh

Si tout s'est bien passé, on constate que le contenu de toutes les feuilles que nous avons listées s'affichent dans le terminal.

Dans ce script, la valeur de chaque ligne est assignée à la variable line. Puis [ -n "$line" ], teste si la chaîne de caractère retournée par $line n'est pas null avant de d'exécuter cat. Et finalement, notre liste est dirigée dans la boucle avec <.

L'étape suivante serait de rediriger stdout vers un fichier CSS avec >:

#!/bin/sh

while read -r line; do
  [ -n "$line" ] && cat "./$line"
done < global-styles.txt > global.css

On pourrait aussi ne pas vouloir de fichier .txt et lister directement dans le script les fichier à concaténer:

#!/bin/sh

while read -r line; do
  [ -n "$line" ] && cat "./$line"
done <<EOF
global/tokens/typography.css
global/tokens/colors.css
global/tokens/grid.css

global/base.css
global/a11y.css
EOF

Ici EOF est une commande qui permet de faire une entrée multiligne. Donc toutes les lignes qui se trouve entre <<EOF et EOF est passé à stdin.

Minifier une feuille de style avec tr pour réduire son poid

L'utilitaire tr sert à traduire ou éliminer des caractères. Il va permettre de retirer les sauts de ligne et les retours dans notre CSS, pour ramener toutes les règles sur une seule ligne. Le poid du fichier sera donc un peu moindre:

cat global.css | tr -d '\n\r' > "$global.min.css"

Le caractère |, que l'on appelle pipe, sert à lier deux processus en prenant le flux de sortie du premier, pour en faire le flux d'entrée du second.

Nous pourrions aussi retirer les tabs avec tr -d '\t', et les espaces multiples avec tr -s ' ' ' '. Cependant je ne conseillerais pas de supprimer les espaces simples avec tr, et ce, parce que des fonctions CSS comme calc(), nécessitent des espaces entre certains opérateurs.

Pour ma part, j'ai développé en lua, mon propre petit utilitaire pour contourner ce genre particularités. Ceci dit, dépendamment de la taille total de votre fichier CSS, les gains sont assez négligeables.

Voyons maintenant à quoi ressemble notre minification avec ces deux commandes tr supplémentaires…

cat global.css | tr -d '\n\r' | tr -d '\t' | tr -s ' ' ' ' > "$global.min.css"

Retirer les commemtaires avec sed

Malgré que, ce ne soit pas la façon la plus optimale de minifier, cela permet de faire des gains de plusieurs kilo-octets.

Mais les blocs de commentaires demeurent, et sed pourrait sans doute les retirer pour nous:

cat global.css | tr -d '\n\r' | tr -d '\t' | tr -s ' ' ' ' | sed -r ':a; s%(.*)/\*.*\*/%\1%; ta; /\/\*/ !b; N; ba'

Conclusion

Rendu ici, nous pouvons rassembler la concaténation et minification:

#!/bin/sh

while read -r line; do
  [ -n "$line" ] && cat "./$line"
done <<EOF | tr -d '\n\r' | tr -d '\t' | tr -s ' ' ' ' | sed -r ':a; s%(.*)/\*.*\*/%\1%; ta; /\/\*/ !b; N; ba' > "global.min.css"
global/tokens/typography.css
global/tokens/colors.css
global/tokens/grid.css

global/base.css
global/a11y.css
EOF

Pas si mal hein? Sans rien installer d'externe sur notre poste de travail, nous sommes en mesure de concaténer et réduire la taille des fichiers CSS, avec à peine, quelques lignes de POSIX shell.

Et, contrairement à de la configuration de modules NodeJS, ce que nous apprenons ici, peut nous servir dans d'autres contextes qui, n'ont rien à voir avec le développement web.

Post-scriptum

entr, est un utilitaire qui permet d'exécuter des commandes quand des fichiers sont modifiés.

Ce qui en fait l'outil parfait pour mettre à jour automatiquement les changements portés à nos feuilles de styles:

find . | entr -d ./css.sh