Compresser et ranger son CSS avec PHP.

Voici une méthode permettant de compresser des CSS, que j’utilise depuis quelques mois. Elle répond à plusieurs impératifs :

  • Réduire le nombre de CSS chargés au minimum, pour économiser les requêtes HTTP.
  • Permettre de créer des fichiers CSS à la volée, sans code php à l’interieur, et de les diviser le plus possible.
  • Compresser le résultat.
  • Mettre en cache la version compressée, qui sera servie au visiteur.

Cette méthode est grandement inspirée de l’article sur la compression des CSS chez CatsWhoCode, que j’ai décortiqué pendant un bout de temps 🙂

On fera donc appel à ces fichiers :

  • style.php : appelé pour créer la page, mais qui transmet également les instructions CSS.
  • style-min.css : Le CSS final, appelé par la version du site.
  • style/ : Le sous dossier contenant tous vos CSS.

Les éventuelles images devront être dans le même répertoire que style.php.

Voici donc le code commenté de style.php :

On déclare le contenu comme étant du CSS, pour être sûr de son interpretation.


Ensuite, il faut inclure les CSS, que l'on va trier dans l'ordre alphabétique, pour permettre d'éventuels hacks, et surtout une idée de l'ordre des instructions.

// On cree une variable de retour pour contenir le CSS.
$retour_css = '';

// On parcourt le sous-dossier style/
$dir = opendir("style/");
$fichiers = array();
// On ajoute chaque fichier à un tableau 'fichiers'
while($fichier = readdir($dir))
	{if($fichier!="." && $fichier!="..") $fichiers[] = 'style/'.$fichier;}
closedir($dir);

// Tri par ordre alphabetique des fichiers
asort($fichiers);

// inclusion des fichiers ... dans l'ordre
foreach($fichiers as $fichier)
	$retour_css .= file_get_contents($fichier);

La fonction compress() va agir sur plusieurs points :

  • Retirer les caractères "inutiles", comme les commentaires, tabulations, et transformer les espaces multiples en espaces simples, etc.
  • Permettre la mise en place d'un léger système de variables. Utile si vous avez beaucoup de fichiers 🙂
function compress($buffer) {
$variables_css = array(
	'COULEUR1'=>'#003366',
	'COULEUR2'=>'#336699',
	'FONT1'=>'Georgia, serif'
	);
// On remplace les variables par leur valeur
foreach($variables_css as $code_variable => $valeur)
	$buffer = str_replace('{'.$code_variable.'}', $valeur, $buffer);

	// Suppression des commentaires
	$buffer = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $buffer);

	// Suppression des tabulations, espaces multiples, retours à la ligne, etc.
	$buffer = str_replace(array("\r\n", "\r", "\n", "\t", '  ', '	 ', '	 '), '', $buffer);

// Suppression des derniers espaces inutiles
	$buffer = str_replace(array(' { ',' {','{ '), '{', $buffer);
	$buffer = str_replace(array(' } ',' }','} '), '}', $buffer);
	$buffer = str_replace(array(' : ',' :',': '), ':', $buffer);

	return $buffer;
}

// Compression du CSS
$retour_css = compress($retour_css);

Enfin, nous allons mettre en cache le contenu de la variable $retour_css, et l'afficher, pour les tests.

// Mise en cache du fichier style-min.css
file_put_contents('style-min.css',$retour_css);
// On affiche le contenu compressé
echo $retour_css;

Vous pouvez créer autant de fichiers CSS que vous le désirez dans le sous-dossier style/, afin de séparer couleurs, tailles, réglages de base, Reset CSS et autres grilles.

Vous m'avez lu jusqu'ici ? Woah 🙂 Apprenez donc que ce code est sous licence WTFPL, qu'un lien vers cet article sera accueilli avec grand bonheur, et qu'un commentaire constructif le sera également 🙂

Partager cet article

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

  • Le commentaire qui veut tout dire : J’ai testé de copier/coller ce code « à l’arrache », et de le mettre en application, ça marche très bien. J’espère que ça sera la même pour tous :3

  • Non, plus sérieusement : c’est ce que je cherchais à faire y’a pas long (tmtc xdlol). Je le testerai sur mon propre blog (oui monsieur) quand celui-ci voudra bien refonctionner, selon le bon vouloir de mon hébergeur.

  • @FGRibreau J’aime bien utiliser mes propres scripts. Ce qui n’enlève rien à la facilité de maintenance, vu que je commente tout. Minify a l’air très puissant, mais je trouve qu’un petit script du genre fait aussi bien l’affaire, et le résultat ne fait pas de mal au serveur 🙂

  • J’avais fait un truc similaire il y a quelques années, avec en plus une version pour le javascript (ça doit encore trainer quelques part sur codes-source), mais c’était vraiment bien pourri ^^

    Bref je met de coté et si jamais tu te sent de faire un truc équivalent pour javascript, n’hésites pas :p

  • @Julien Grosso modo, ça doit être la même chose, sauf peut-être la compression des commentaires, et évidemment le content-type 🙂

  • Un bon petit script comme je les aimes.Je m’en inspirerai sûrement pour créer un système similaire dans notre CMS.

    @FGRibeau : La réponse à ta question se trouve sur la page d’accueil de Minify :
    « Minify is designed for efficiency, but, for very high traffic sites, Minify may serve files slower than your HTTPd due to the CGI overhead of PHP. See the FAQ and CookBook for more info. »

    ++

  • Hello,

    Petite question concernant le remplacement des espaces : il y a une raison particulière à le faire avec un str_replace (qui limite les cas) plutôt que de passer par un preg_replace genre :

    // Suppression des tabulations, espaces, retours à la ligne, etc.
    $buffer = str_replace(array(« \r\n », « \r », « \n », « \t »),  », $buffer);
    $buffer = preg_replace(‘/\s+/’, ‘ ‘, $buffer);

    Peut-être au niveau des performances ?

    ++

  • @Seb Non, il n’y a pas de problème de performance, vu qu’on met en cache une version statique destinée au visiteur. Et en effet, l’utilisation de preg_replace avec cette regex est franchement plus adaptée 😉 Merci du rappel !

  • As-tu comparé le taux de compression pour un gros code CSS avec YUI Compressor ou Minify ? Je préfère utiliser un petit script bash qui compresse les CSS avec YUI Compressor au moment de l’exportation vers le serveur prod.
    Le point essentiel dans la compression est bien sûr le deflate (ou gzip), qui peut être fait très simplement avec un .htaccess, en prenant garde à conserver la comptabilité avec IE6. Il faut pour cela que les mods deflate et headers soit activés dans Apache. Par exemple :

    http://www.pastebin.com/f51188d64

  • Le taux est moins bon que sur un minify ou un yui compressor, pour la simple raison que la compression tient en 3 lignes de codes 🙂
    L’avantage est juste d’avoir un script simple et efficace, pour ne creer un seul fichier, et donc une seule requete http.

    Et bien évidemment, une compression gzip par dessus est l’idéal, mais je n’ai pas voulu surcharger l’article avec une explication supplémentaire 🙂

  • C’est pas risqué de supprimer les espaces ?
     
    quid des règles suivantes :
     
    padding:0 5px 10px 15px;
    background:transparent url(iamge.png) top left;

  • @Seebz le script supprime les espaces multiples, pas les espaces simples, c’est une précision que j’aurais du indiquer. Comme tu le soulignes, supprimer des espaces dans ce cas précis invaliderait la propriété CSS 🙂

  • Autant pour moi, j’avais mal vu :s
     
    J’ai moi-même fait une ‘tite fonction « minify » mais j’utilise plus d’expressions régulières.
    Je pense qu’on doit retourner la même chose mis à part que je supprime les « ; » situés devant « } », ça fait toujours quelques caractères en moins 😉
     
    Pour info :
    function minify_css($string)
    {
    $string = str_replace(array(« \r », »\n »),  », $string);
    $string = preg_replace(‘`\/[*].*[*]\/`Us’,  », $string);
    $string = preg_replace(‘`\s*({|}|,|:|;)\s*`’, ‘$1’, $string);
    $string = str_replace(‘;}’, ‘}’, $string);

    return $string;
    }

  • @Seebz d’autant plus qu’il n’y a pas de problèmes de performance, vu qu’on met le résultat en cache. Bien vu 😉

  • par contre je suis obligé de lancer ce script sur toutes les pages, et les ressources serveur vont augmenter considérablement ?

  • Il ne faut pas appeler ce script à chaque page. Il faut que tu le lances quand tu fais des modifs dans tes fichiers CSS.

    Ensuite sur chaque page, il te suffit d’appeler le fichier généré (style-min.css dans l’article). 😉

  • oui mais je dois automatiser cette tache, comment dire à chaque changement de code, relancer ton script ?
    Pour un système de blog par exemple

  • ça fait une tache supplémentaire pour vérifier sont code, sa ne me semble plus tellement réaliste

    A la limite faire une action dans ce genre
    echo (substr($_SERVER[« HTTP_HOST »],0,9)===’localhost’)?’localhost’:’ligne’;

  • @Seb Bon résumé 🙂
    @x@v : j’utilise quelque chose du genre :

    $admin = true;
    echo '';

    C’est très léger à l’execution ( condition ternaire ), et ça permet de ne pas se préoccuper ni des perfs, ni de la mise à jour du cache 😉
    Et il semble que RemixJobs utilise mon script, ce qui m’honore, mais la licence WTFPL leur donne même le droit de le revendre 😉

  • Non, là je déclare $admin, mais en développement, $admin prend sa valeur à partir d’un test ( isset($_SESSION[‘admin’]) ou un truc du genre ).
    De même, ne copie/colle pas directement la ligne de CSS, elle n’est pas valide 🙂

  • Bonjour, cet article m’a donné l’envie d’en écrire un pour exposer la solution (complète) que j’utilise actuellement en production (article rédigé en anglais) : http://wp.me/pQw0g-4, Minify and consolidate JavaScript scripts via PHP wrapper. Je suis prêt à recevoir tous les commentaires !

  • Petite question, tu réarranges dans l’ordre alphabétique tes css. Du coup, tu les interprétes par ordre alphabétique. Quid du reset.css qui sera interprété dans les derniers ? Pour ma part je redéfinis des styles dans des css différentes que reset. Si mes styles se font réécraser, je vois pas l’intêret du rangement alphabétique.

  • @Aka : Tu nommes ton reset.css en base.css :3
    Quant à moi, je l’ai un peu compressé, et je cale quelques définitions par défaut dans le même fichier. Du type la taille du body, les actions de liens, etc.

  • Je redéfinis le reset ailleurs. En général je segmente pas mal mes css. J’aime beaucoup la solution apportée par Perishable (utilisation de la compression gzip). Tu pourrais peut être ajouter un petit coup de compression fichier en plus dans ta fonction, sinon miam miam très utile comme outil.

  • @syl : je propose une autre approche avec un csstidy « à la main » via l’outil http://developer.yahoo.com/yui/compressor/. Cet outil permet d’obtenir d’excellent résultats et de plus, s’utilise aussi sur les JS.
    A noter que la compression gzip est un plus indispensable pour garantir des temps de téléchargements décents (pour Aka).
    Enfin, tjs pour Aka, la solution que j’explique sur mon blog permet de laisser les CSS fragmenté, car ça permet clairement une bonne maintenance du site par module ou fonctionnalité.

  • @Syl @Wilfried Beaucoup de tests montrent que le gain de ko n’augmente pas nécessairement la vitesse de téléchargement, alors que le nombre de requêtes HTTP est vraiment significatif 🙂

  • C’est vrai,
    dans l’approche csstidy, pour ma part j’y vois aussi un moyen de « brouillage » du code en le rendant « illisible » … histoire de limiter le pompage de code…
    .

  • L’ancien appel au CSS ( style-min.css / style.php ) aurait pu laisser penser que … Mea culpa 😉

  • j’ai trouver deux script qui combinai donne un trés bon rendu de compression :

    $regex = array(
    « `^([\t\s]+)`ism »=> »,
    « `([:;}{]{1})([\t\s]+)(\S)`ism »=>’$1$3′,
    « `(\S)([\t\s]+)([:;}{]{1})`ism »=>’$1$3′,
    « `\/\*(.+?)\*\/`ism »=> » »,
    « `([\n|\A|;]+)\s//(.+?)[\n\r]`ism »=> »$1\n »,
    « `(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+`ism »=> »\n »
    );
    $buffer = preg_replace(array_keys($regex),$regex,$buffer);

    function minifyCSS($css){
    $css = trim($css);
    $css = str_replace(« \r\n », « \n », $css);
    $search = array(« /\/\*[^!][\d\D]*?\*\/|\t+/ », »/\s+/ », « /\}\s+/ »);
    $replace = array(null, » « , « }\n »);
    $css = preg_replace($search, $replace, $css);
    $search = array(« /;[\s+]/ », »/[\s+];/ », »/\s+\{\\s+/ », « /\\:\s+\\#/ », « /,\s+/i », « /\\:\s+\\\’/i », »/\\:\s+([0-9]+|[A-F]+)/i », »/\{\\s+/ », »/;}/ »);
    $replace = array(« ; », »; », »{« , « :# », « , », « :\' », « :$1 », »{« , »} »);
    $css = preg_replace($search, $replace, $css);
    $css = str_replace(« \n », null, $css);
    return $css;
    }

  • C’est pas mal. Perso pour éviter de me prendre la tête j’ai un système automatique:
    – tous les CSS dans un dossier /css
    – un script php/css.php qui va régénérer le CSS compacté dans /pack/pack.css seulement si un des fichiers de /css est plus récent que le package
    Le code inclut php/css.php
    Même en local, le pack est régénéré automatiquement. Quand on transfère sur le serveur, même le premier client n’a pas à attendre la régénération 🙂

    Une version schématique du source :

    <?php
    $dir = '../inc';
    $out = '../pack/pack.css';

    $script = '';
    $last_change = 0;
    if ($dh = opendir($dir))
    {
    while (($file = readdir($dh)))
    {
    $name = $dir.'/'.$file;
    if (is_file($name))
    {
    $script .= file_get_contents($name);
    $last_change = max ($last_change, filemtime($name));
    }
    }
    closedir($dh);
    }
    if (!file_exists ($out) || (filemtime ($out)

  • Bonjour,
    Le remplacement des variables ne fonctionne pas, sinon super ce script..
    A vous lire..
    Merci

    Ah oui, je suis nul en php,…