Compresser et ranger son CSS avec PHP.
Attention, cet article a été déplacé dans les archives, donc le contenu peut ne plus être à jour. A vous de voir 🙂
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 🙂
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
jcompren pa jé essayé sr mn skyblog é sa march pa jcmpr pa
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.
Ok… tu préfères cette méthode à l’utilisation de minify (http://code.google.com/p/minify/) ? Si oui, pourquoi ?
J’entrevois un argument sur les performances… mais mise à par ça…
@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 🙂
Ok 🙂
Oui, c’est un faux argument, je suis partisan de la performance et de la simplicité du script :3
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 🙂
Tu sais QUI n’a pas voulu surcharger ses articles avec des explications supplémentaires ?!
Tu sais QUI ne pouvait pas utiliser gzip parce que ça n’existait pas ?
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 😉
je pense ajouter ton code dans mon projet open source…
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’;
eux il utilisent ton code 🙂
http://www.remixjobs.com/job/add
il ne précise pas la source comme tu le souhaite
@Seb Bon résumé 🙂
@x@v : j’utilise quelque chose du genre :
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 😉
donc tu mes false en développement ?
enfin true mais tu dois changer chaque fois
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 !
@Wilfried en effet, ta solution est aussi très intéressante 🙂
Merci 🙂
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.
Comme Skreo le définit plus haut, autant le faire via htaccess 🙂
combiné avec ceci serait pas mal :
http://csstidy.sourceforge.net/usage.php
@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…
.
syl > Non, non, non.
http://www.codebeautifier.com/
Si tu ne veux pas qu’on te pique un bout de code, ne le mets pas en ligne.
Ici le but est d’améliorer les performances et le workflow.
Bonjour,
Juste pour rectification, nous n’utilisons pas ce script sur Remixjobs 🙂
Sur la v2, nous avons mis en place sass.
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,…