Organiser un projet CSS

Quelques notes rapides et en vrac sur la façon dont je gère des projets CSS depuis quelques temps. Comme il n'existe pas vraiment de standards dans le domaine, il s'agit ici d'une simple habitude de travail.

Structuration du projet

  • Séparer ce qui qui pourrait être utilisé ailleurs de ce qui est spécifique au projet.
  • Diviser le code en unités (fichiers) facilitant son utilisation et sa maintenance.
  • css
Structuration de base du projet.

Le dossier core contiendra les règles CSS génériques, qui pourraient être utilisées dans n'importe quel autre projet(1) ; le dossier project contiendra lui les feuilles de styles spécifiques au projet développé.

Règles génériques

La structuration en fichiers dépendra ensuite des habitudes de chacun ou des contraintes du projet lui-même ; il n'y a pas de normes à proprement parlé. On peut par exemple imaginer subdiviser son code en fonction de la nature des règles CSS : un premier fichier pour les règles construisant la maquette, un deuxième pour gérer la typographie et un troisième pour les couleurs. J'ai décidé pour ma part de séparer ces règles applicables à tout type de projet dans trois fichiers :

  • base.css : règles qui s'appliquent aux éléments HTML directement.
  • common.css : ensemble de classes génériques.
  • important.css : règles qui se surimposent à toutes les autres.
  • css
    • core
      • base.css
      • common.css
      • important.css
Organisation du coeur d'un projet CSS en trois fichiers.

Règles spécifiques

Tout comme pour les règles génériques, les règles propres au projet peuvent être dispatchées dans plusieurs fichiers. L'un d'entre eux devra avoir un rôle particulier : styles.css est construit pour importer tous les autres fichiers nécessaires.

  • css
    • core
      • base.css
      • common.css
      • important.css
    • project
      • main.css
      • styles.css
Exemple d'un projet avec un simple fichier main.css et le fichier « maître » styles.css.
/* Import base and common rules */
@import "../core/base.css";
@import "../core/common.css";
/* Import your project rules */
@import "main.css";
/* Import important rules */
@import "../core/important.css";
Structure du fichier styles.css

L'utilisation d'un fichier d'import à plusieurs avantages :

  • Comme un unique appel à ce fichier dans un document web met à disposition l'ensemble du projet, nous n'avons plus à craindre de fragmenter le code entre plusieurs fichiers comme bon nous semble.
  • Il nous permet de modifier rapidement le rendu CSS dans un document web en important certains fichiers et pas d'autres, sans avoir à toucher au code HTML lui-même.
  • Il servira par ailleurs de base à la concaténation de tous ces fichiers en un seul (voir plus bas).

Performances en production : concaténer, minifier et compresser

L'organisation décrite plus haut est acceptable en développement, celui du projet CSS lui même ou celui d'un projet web (site, application) qui l'utilise. Elle n'est cependant pas recommendable en production, où il faut aussi penser à réduire le nombre et la taille des requêtes envoyées au serveur.

  • Concaténation de tous les fichiers en un seul.
  • Minification du fichier résultant.
  • Compression de ce fichier.

La concaténation peut se faire à l'aide d'un script bash, en se basant sur le fichier d'import styles.css ; la compression s'appuiera sur un logiciel comme gzip(2) ; la minification demandera elle un outil plus spécifique. Il en existe plusieurs, codés avec différents langages ; j'utilise pour ma part Yui Compressor.

Script bash de traitement

Voici, à titre d'exemple, une version simplifiée des fonctions bash que j'utilise pour concaténer, minifier et compresser mes projets CSS :

YUI_COMPRESSOR="path/to/yuicompressor-x.x.x.jar"
MIN_EXT=".min"

# Concaténation, minification et compression d'un fichier CSS
# @param {String} $1 Nom/chemin du fichier CSS d'import à traiter
# @param {String] $2 Nom/chemin du fichier compressé à créer.
#
build_css() {
    if [[ $1 ]] && [[ $2 ]]; then
        concatenate $1 $2
        minify css $2
        compress $2".min"
    fi
}

# Concaténation des fichiers CSS déclarés dans un fichier d'import
# @param {String] $1 Nom/chemin du fichier d'import CSS à concaténer
# @param {String] [$2] Nom/chemin de la version concaténée du fichier
#
concatenate() {
    if [[ $1 ]]; then
        # Concatener les fichiers
        echo -e "[INFO] Concaténation des fichiers css"
        local root_path=`dirname $1`"/"
        local concatenate_file = $1".concatenate"
        if [[ $2 ]]; then
            local concatenate_file=$2
        fi
        while read line
        do
            local res=$(expr "$line" : '.*"\([^"]\+.css\)";')
            if [ "$res" ]; then
                echo -e "[INFO] Concaténation de "$res
                cat $root_path$res >> $concatenate_file
            fi
        done < $1
    fi
}

# Minification de fichier à partir de yuiCompressor
# @param $1 Type de fichier (css|js)
# @param $2 Nom du fichier source
# @param [$3] Nom du fichier minifié
#
minify() {
    if [[ $2 ]]; then
        if [[ $3 ]]; then
            local file_min=$3
        else
            local file_min=$2$MIN_EXT
        fi
        echo -e "[INFO] Minification du fichier "$2
        java -jar $YUI_COMPRESSOR $2 --type $1 --line-break 1000 -o $file_min
    fi
}

# Fonction de compression d'un fichier au format gzip
# avec extension min
# @param $1 Nom du fichier
#
compress() {
    if [ $1 ]; then
        local file_name=$1
        echo -e "[INFO] Compression du fichier "$file_name
        gzip $file_name
        if [[ "$?" != 0 ]]; then
            echo -e "[ERROR] Erreur lors de la compression gzip du fichier "$file_name
            exit
        fi
        mv $file_name".gz" $file_name
    fi
}
Fonctions bash pour concaténer, minifier et compresser un projet CSS.

Remarques et problématiques

Une remarque concernant le processue de concaténation : nous nous sommes efforcer plus haut de structurer le code dans différents fichiers classés dans une arborescence de répertoires afin de faciliter son organisation et sa maintenance. La concaténation casse cette structuration et, de fait, peut aussi casser certaines règles css, notamment celles faisant appel à des images.

  • css
    • core
      • base.css
      • baz.png
      • common.css
      • important.css
    • project
      • main.css
      • qux.png
      • styles.css
Utilisation d'images dans le projet CSS.
.foo {
    background-image: url("../core/baz.png");
}

.bar {
    background-image: url("qux.png");
}
Règles CSS utilisant des images dans le fichier main.css.

Une fois les fichiers concaténés, ces règles css utilisant des urls relatives pour définir des images de fond n'auront plus de sens. La mise en production ne se réduira donc pas à concaténer et à compresser les feuilles de styles : il faudra aussi penser à déplacer les éventuelles images utilisées et/ou éditer les fichiers avant concaténation. Si on décide par exemple de déplacer les images à la racine du dossier qui sera mis en production, voilà à quoi pourrait ressembler l'édition des fichiers CSS dans la fonction concatenate :

# cat $root_path$res >> $concatenate_file
sed "s|../core/||g" $root_path$res >> $concatenate_file
Changement du chemin relatif des images avant concaténation.

En reprenant une structuration de projet décrite par ailleurs, nous obtenons l'organisation suivante :

  • css
    • build
      • baz.png
      • qux.png
      • styles.css.min
    • css
      • core
        • base.css
        • baz.png
        • common.css
        • important.css
      • project
        • main.css
        • qux.png
        • styles.css
    • build.sh
    • LICENCE
    • README
Structure générique d'un projet.

Le dossier css/css serait le dossier de travail (de développement) et le dossier css/build serait celui déployé en production.