CSS verkleinern mit CSSTidy

Zum Bloggen gezwungen, wieder mal. Diesmal aber nicht von Ina, sondern vom Kollegen gegenüber. Es geht sich um das Zusammenführen und Verkleinern von CSS-Dateien.

Zunächst einmal stellt sich aber die Frage, warum man das überhaupt tun sollte. Stellen wir uns einmal vor, wir hätten eine ziemlich umfangreiche, gut frequentierte Website, die von mehreren Entwicklern gepflegt wird. Um der einfacheren Pflege halber ein wenig Struktur und Dokumentation in das Projekt zu bringen, teilt man seine CSS-Dateien in logische Abschnitte auf, wie z.B. typography.css, grid.css und colors.css. Diese enthalten natürlich auch eine Unmenge Kommentare, schließlich wollen große Projekte gut dokumentiert sein. Damit schwillen einige Dateien schon mal um ein paar Kilobyte an, was unnötigen Traffic und damit Kosten verursacht. Darüberhinaus entstehen mehrere HTTP-Zugriffe, was Ladezeit kostet. Yahoo! erklärt das recht gut.

Also liegt es nahe, all diese kleinen Dateien zu einer Datei zusammenzufassen und sie zu verkleinern (z.B. Kommentare entfernen, unnötige Zeichen oder unnötigen Whitespace löschen). Hilfreich ist hier neben PHPs eigenen Funktionen die CSSTidy-Klasse. Grob umrissen, ermöglicht sie das Parsen und anschließende Manipulieren von CSS-Dateien.

Beim folgenden PHP-Skript gehen wir davon aus, dass nur die Dateien im »inc«-Verzeichnis unseres CSS-Verzeichnisses zusammengefasst und komprimiert werden sollen. Das macht Sinn, wenn andere, umfassendere Stylesheets auf gemeinsame, logische CSS-Regeln zugreifen sollen. Dass die Pfade natürlich auf die eigene Umgebung angepasst werden müssen, versteht sich von selbst.

<?php
/**
 * Paths
 */
$tidy_directory = dirname(__FILE__) . 'csstidy/';
$css_directory = dirname(__FILE__) . 'css/';
$inc_directory = $css_directory . 'inc/';
$merged_file = 'merge.css';

/**
 * Merge CSS files to one file
 *
 * @param string $inc_directory
 * @param string $target_css_file_name
 */
function mergeCSS($target_directory, $target_css_file_name) {
	if (file_exists($target_directory . $target_css_file_name))
		unlink($target_directory . $target_css_file_name);
	if ($handle = opendir($target_directory)) {
		while (false !== ($inc_file = readdir($handle))) {
			if ((substr($inc_file, -4) === '.css') &&
				($inc_file !== $target_css_file_name))
					file_put_contents(
						$target_directory . $target_css_file_name,
						file_get_contents($target_directory . $inc_file),
						FILE_APPEND
					);
		}
		closedir($handle);
	} else echo('Directory not readable.');
}

/**
 * Compress merged CSS file with CSSTidy
 *
 * @param string $inc_directory
 * @param string $target_css_file_name
 * @param string $tidy_location
 */
function compressCSS($target_directory, $target_css_file_name, $tidy_location) {
	/* Get CSSTidy and the CSS file to compress */
	require_once $tidy_location . 'class.csstidy.php';
	$tidy = new csstidy();
	$css_file = file_get_contents($target_directory . $target_css_file_name);

	/* CSSTidy settings, usage: http://csstidy.sourceforge.net/usage.php */
	$tidy->set_cfg('preserve_css', FALSE);
	$tidy->set_cfg('remove_bslash', TRUE);
	$tidy->set_cfg('compress_colors', TRUE);
	$tidy->set_cfg('lowercase_s', FALSE);
	$tidy->set_cfg('timestamp', FALSE);
	$tidy->set_cfg('optimise_shorthands', 2);
	$tidy->set_cfg('remove_last_;', TRUE);
	$tidy->set_cfg('sort_properties', TRUE);
	$tidy->set_cfg('sort_selectors', FALSE);
	$tidy->set_cfg('merge_selectors', 2);
	$tidy->set_cfg('compress_font', TRUE);
	$tidy->set_cfg('silent', TRUE);
	$tidy->set_cfg('case_properties', 1);

	/* Compression template, template3.tpl = highest compression */
	$tidy->load_template($tidy_location . 'template3.tpl', TRUE);

	/* Parse CSS file with settings and templates */
	$tidy->parse($css_file);

	/* Write parsed output to target file */
	file_put_contents(
		$target_directory . $target_css_file_name,
		$tidy->print->plain()
	);
}

/**
 * Execute functions
 */
mergeCSS($inc_directory, $merged_file);
compressCSS($inc_directory, $merged_file, $tidy_directory);

Das Skript muss dann ausgeführt werden (ob auf Kommandozeile, via URL, über einen Build-Prozess sei Euch überlassen), anschließend wird die neue, komprimierte Datei erstellt oder überschrieben und kann verknüpft werden.

Disclaimer: Ja, ich könnte es noch besser dokumentieren und erklären. Ja, etwas mehr Fehlerbehandlung wäre toll. Ja, es ist nicht das beste PHP aller Zeiten. Ja, man könnte noch validieren, automatisieren, g’zippen… Macht’s einfach besser, dafür ist das Skript als erster Ansatz ja da. 😝

Antworten

  1. Check this out:
    How To Minimize Your Javascript and CSS Files for Faster Page Loads

  2. Mike

    Erstmal ein dickes Lob und Danke für Deine Erläuterung und den Ansatz mehr draus zu machen.
    Mir stellt sich allerdings eine Frage: Du sprichst von größeren Projekten mit guter Frequentierung. Hällst Du es denn dann für sinnvoll das Script auch über kleinere Seite wie Z.b. Blogs, etc. laufen zu lassen?

  3. @Marc:
    Den Artikel kenne ich, aber er geht eher auf die Möglichkeiten der JavaScript- denn auf CSS-Kompression ein. Was JavaScript-Kompression angeht, vertraue ich auf den CompressorRater und infolgedessen meist auf Dean Edwards Packer. Echte CSS-Kompression kommt meist zu kurz; verfolgt man aber meinen Ansatz und fügt dem Ganzen noch Gzip hinzu, spart man viel Traffic. Auf den Gzip-Kram gehe ich hier aber nicht ein, da ich denke, dass sowas nicht von PHP, sondern vom Server erledigt werden sollte, auch wenn’s natürlich ginge.

    @Mike:
    Schaden kann’s niemals, allerdings bringt es meiner Meinung nach keinen signifikanten Nutzen bei low scale projects. Dafür rechnet sich die Einsparung an Traffic und damit der Kosten nicht, da man für private Projekte bei den gängigen Hostern Unmengen Traffic out-of-the-box erhält.

  4. Dickes dankeschön! Die Original Doku ist schlechter als n übler Witz, mit dem code hier kann ich was anfangen.