Сжатие CSS файлов

Иван Никитин

11 Март 2011

Во время верстки сайтов часто приходится иметь дело с большим количеством CSS файлов, например, отдельные стили для описания цветов, фонов элементов, отдельные стили для мобильных устройств и т.п. Зачастую количество файлов .css бывает хорошо за десяток. Однако для оптимизации производительности сайта настойчиво рекомендуется минимизировать количество загружаемых файлов и по возможности включить их сжатие. Как это сделать достаточно просто рассказывает эта заметка.

Поскольку мне приходится делать это буквально на каждом сайте, я озадачился написать  достаточно простое и легко переносимое решение, и, наконец, сегодня до этого дошли руки.

Итак, идея довольно простая:zip_icon

  • Сваливаем все css файлы в одну папку на сайте, например, /css/
  • C помощью скрипта, который стоит дефолтовым документом в этой папке, проверяем время изменения файлов
  • На основе этого времени реализуем правильную реакцию на запрос GET If-Modified-Since, чтобы задействовать кэш браузера и возможных прокси
  • Обязательно разрешаем кэширование, при условии валидации в запросе (см. пункт выше)
  • Сливаем все файлы в CSS в один поток и сжимаем его gzip
  • Что получилось, то и отдаем пользователю (304 Not Modified или gzip поток)

В общем, эта нехитрая идея реализуется вот таким скриптом index.php

<?php
/*
 * Сборка CSS файлов в один и передача его с сжатом виде клиенту
 */
 
// Расширение файлов, с которым работаем
define('FILE_EXT', '.css');
// Тип содержимого
define('CONTENT_TYPE', 'text/css');
// Политика кэширования
define('CACHE_CONTROL', 'public, max-age=86400, must-revalidate');
 
// Ищем все файлы по расширению, стоем спискок файлов, проверяем дату последней модификации
$myFiles = array();
$lastModified = 0;
if ($handle = opendir('.')) {
    while (false !== ($file = readdir($handle))) 
	{
        // Это мой файл?
		if (strpos($file, FILE_EXT) !== false)
		{
			// Запомним файл
			$myFiles[] = $file;
			// Дата его модификации
			$currentFileModification = filemtime($file);
			if ($currentFileModification > $lastModified)
				$lastModified = $currentFileModification;
		}
    }
    closedir($handle);
}
 
 
 
// Если файлов нет, возвращаем 404
if (count($myFiles) == 0)
{
	header('HTTP/1.0 404 Not Found');
	exit;
}
 
// Если был запрос If-Modified-Since, обрабатываем его
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) 
{
	// Разбираем заголовок If-Modified-Since и формируем timestamp
	$if_modified_since = strtotime(preg_replace('/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE']));
 
	if ($if_modified_since > $lastModified)
	{
		// Изменений не было
		header('HTTP/1.1 304 Not Modified');
		header('Cache-Control: ' . CACHE_CONTROL);
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
		exit;
	}
}
 
// Передаем заголовки
header('Content-Type: ' . CONTENT_TYPE);
header('Cache-Control: ' . CACHE_CONTROL);
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
 
 
// Старт сжатия
ob_start("ob_gzhandler");
 
// Порядок передачи -- по алфавиту по именам файлов
asort($myFiles);
 
// Передаем файлы
foreach($myFiles as $file)
	include($file);
 
// Вывод результата
ob_end_flush();
?>

Теперь наши CSS подключаются невероятно просто:

<link rel="stylesheet" type="text/css" href="/css/">

Результат, как видно, налицо!

  • Более чем 3х кратное сжатие (7,37 Kb сжимается до 2 Kb)
  • Активное использование кэша браузера
  • Минимальное число обращений к серверу

Первый запрос:

Первый запрос CSS

Второй запрос (другая страница):

Второй запрос CSS

Этот простой способ можно также применить и для сжатия нескольких JS-файлов.

Вот пример скрипта для скачивания.

6 комментариев к “Сжатие CSS файлов”

  1. Сжатие файлов проще делать настройками веб-сервера. Чтобы быстрей отдавать статику лучше использовать NGINX как проксирующий сервер.

    Про сливание CSS и JS файлов:

    1. Браузер не должен запрашивать файлы ему не предназначенные, хотя, он это делает. Но если слить screen и handheld css в один файл? Там же буде пересечение селекторов.

    2. Аналогично и с JS. Если у вас в файле присутствует код, который работает с DOM не всего сайта, а только с некоторыми страницами? Будет ошибка.

    Еще один минус данного подхода, это то, что каждый раз при запросе статики приходится дергать PHP.

    В общем, я пришел к такой стратегии: NGINX в качестве проксирущего сервера для статики; вся статика на поддоменах, чтобы не утыкаться в 4-+ параллельных запроса и правильные настройки кэширования.

    Некоторые, особо продвинутые товарищи пишут скрипты билда, которые при обновлении проекта сами склеивают нужные файлы.

    Ну и еще классная, но сложная в поддержки штука – css-спрайты.

  2. Здравствуйте Иван. По этой ссылке http://rutracker.org/forum/viewtopic.php?t=3469109 раздаётся ваш видео курс (мне это неприятно ввиду того, что тренинг ваш слишком хорош для такой халявы)
    P.S. (мыло в данных комента указано левое)

  3. Абсолютно согласен, Алексей!
    Предлагаемое здесь решение из разряда «дешево и сердито», для серьезных и больших проектов на production server конечно же сжатие лучше делать руками, и даже не средствами сервера, а просто сжав сами файлы.
    По поводу пересечений селекторов CSS и DOM/JS — думаю, здесь все зависит от того, насколько корректно само приложение спроектировано, так ведь?

  4. Согласен, но часто приходиться работать с чужим кодом.

  5. Может проще сжимать в gz и кэшировать на стороне клиента?

  6. Конечно же, проще! Но для быстрого действа, особенно, когда файлы часто меняются, предложенный способ хорош. А кэширование на клиенте как раз здесь и включается.

Комментарий

Если у Вас есть пожелания, например, увидеть продолжение этой статьи или какие-то новые материалы сайта, пожалуйста, заполните несложную форму пожеланий авторам