Опубликован: 16.06.2010 | Уровень: специалист | Доступ: платный
Лекция 5:

Уменьшение количества запросов

< Лекция 4 || Лекция 5: 12345 || Лекция 6 >
Аннотация: В этой лекции делается упор на методы автоматического объединения файлов, которые позволяют значительно уменьшить издержки на пересылку запросов между браузером и сервером, возникающие в силу различных сетевых задержек. Также здесь рассматриваются различные подходы для клиентского и серверного кэширования.

4.1. Автоматическое объединение текстовых файлов


На тему автоматической "склейки" стилей и скриптов написано уже довольно много статей, но нигде не было описано полное решение, которое учитывало бы "подводные камни", связанные с браузерами и различными способами использования указанных файлов. Ниже стоит рассмотреть то практическое решение, которое реализовано в Web Optimizer и обкатано уже на нескольких тысячах сайтов.

4.1.1. Объединение CSS-файлов

Несмотря на более простой и поистине академический синтаксис, CSS-файлы довольно сложно объединять в силу разных причин. Тут и различные атрибуты media (указывающие на устройства, для которых предназначен данный файл), и возможность сделать "вложенную" загрузку стилей при помощи @import и т. д. Для начала рассмотрим процесс получения ссылок и содержимого самих файлов из исходной структуры веб-страницы.

Получаем код

Если в CMS у нас предусмотрена возможность вставки CSS-файла как отдельного объекта в секцию head страницы, то это ограждает от множества проблем по "вычленению" этих объектов из готового HTML-кода. В противном случае нам придется использовать примерно следующий вариант:

/* регулярное выражение для нахождения всех
<link rel= "stylesheet"> и <style type="text/css">
внутри head-секции */
$regex =
"!(<link[^>]+rel\\s*=\\s*(\"stylesheet\"|'stylesheet'|stylesheet)
([^>]*)>|<style\\s+type\\s*=\\s*(\"text/css\"|'text/css'|text/css
)([^>]*)>(.*?)</style>)!is";
preg_match_all($regex, $this->head, $matches, PREG_SET_ORDER);
if (!empty($matches)) {
foreach($matches as $match) {
$file = array();
$file['tag'] = 'link';
$file['source'] = $match[0];
/* вырезаем из найденного куска HTML-кода обрамляющие теги,
чтобы идентифицировать внутренние стилевые правила */
$file['content'] =
preg_replace("/(<link[^>]+>|<style[^>]*>[\t\s\r\n]*|[\t\s\r\n]*<\
/style>)/i", "", $match[0]);
/* определяем все дополнительные атрибуты */
preg_match_all("@(type|rel|media|href)\s*=\s*(?:\"([^\"]+)\"|'([^
']+)'|([\s]+))@i", $match[0], $variants, PREG_SET_ORDER);
if(is_array($variants)) {
foreach($variants AS $variant_type) {
$variant_type[1] = strtolower($variant_type[1]);
$variant_type[2] = !isset($variant_type[2]) ?
(!isset($variant_type[3]) ?
$variant_type[4] :
$variant_type[3]) :
$variant_type[2];
switch ($variant_type[1]) {
/* выставляем источник для файла стилей */
case "href":
$file['file'] = trim($this->strip_querystring($variant_
type[2]));
$file['file_raw'] = $variant_type[2];
break;
default:
/* пропускаем media="all|screen" для предотвращения некорректного
поведения Safari при @media all{} или @media screen{} */
if ($variant_type[1] != 'media' || ($variant_type[1]
== 'media' && !preg_match("/all|screen/i",
$variant_type[2]))) {
$file[$variant_type[1]] = $variant_type[2];
}
break;
}
}
}
$this->initial_files[] = $file;
}
}
Листинг 4.1.

Подавая на вход данного алгоритма код секции head нашего документа ( $this->head ), на выходе мы получаем готовый массив $this->ini-tialfiles. Стоит сразу отметить, что в массиве для файлов стилей атрибут media не выставляется, если он равен all (в этом случае он просто бесполезен) либо screen (по умолчанию у нас все стилевые правила применяются для отображения сайтов на мониторах, поэтому данное значение также можно безболезненно опустить).

Разбираем вложенность

Получить ссылки на используемые файлы мало, нам необходимо полное содержимое этих файлов. Следует иметь в виду, что нам нужно распознать все внутренние конструкции @import (подключающие дополнительные файлы стилей) в порядке их появления в исходных файлах. Проще всего с данной проблемой может разобраться рекурсивная функция resolve_css_imports:

function resolve_css_imports($src) {
$content = file_get_contents($src);
/* удаляем из первоначального содержимого @import внутри
комментариев */
$content = preg_replace("!/\*\s*@import.*?\*/!is", "",
$content);
/* выбираем все @import */
preg_match_all('/@import\s*(url)?\s*\(?([^;]+?)\)?;/i',
$content, $imports, PREG_SET_ORDER);
if (is_array($imports)) {
foreach ($imports as $import) {
$src = false;
/* очищаем найденный путь к файлу от пробелов и кавычек */
if (isset($import[2])) {
$src = $import[2];
$src = trim($src, '\'" ');
}
if ($src) {
/* запускаем рекурсию для обнаруженного файла, чтобы разрешить
все @import уже внутри него */
$content = str_replace($import[0],
$this->resolve_css_imports($src), $content);
/* изменяем все пути для CSS-изображений и ресурсов (относительно
заданного файла) на абсолютные (относительно корня документа) */
$content = $this->resolve_relative_paths($src, $content);
}
}
}
return $content;
}
Листинг 4.2.

Задав полный путь к файлу стилей для функции resolve_css_imports, мы полностью разрешим все внутренние включения, чем сведем число HTTP-запросов к минимуму.

Объединяем

После того как мы разобрались с массивом файлов и научились получать полное их содержимое, нам нужно корректно их объединить. Как уже описывалось в книге "Разгони свой сайт" (http://speedupyourwebsite.ru/ books/speed-up-your-website/), для этого лучше всего применять конструкцию @media. Предположим, что в результирующем массиве у нас объект имеет следующий формат:

$this->initial_files = array(
array(
'content' => 'полное содержимое файла',
'media' => 'print|handheld|etc',
'file_raw' => 'исходный код файла в head-секции'
),
...
)
Листинг 4.3.

Тогда нам нужно просто объединить весь CSS-код в соответствие со спецификацией:

foreach ($this->initial_files as $file) {
if (!empty($file['media'])) {
$full_content .= '@media '. $file['media'] . '{';
}
$full_content .= $file['content'];
if (!empty($file['media'])) {
$full_content .= '}';
}
}
Листинг 4.4.

На выходе мы получим весь CSS-код, обнаруженный внутри секции head, объединенный в одну строку, которую можно записать в один кэши-рованный файл. Далее нужно, используя свойство fileraw, удалить исходные файлы и внутренний код из документа и вставить (например, сразу же после <head>) вызов этого кэшированного файла.

Минимизируем

А что, если мы хотим не только объединить файлы, но и уменьшить их в размере? Gzip-компрессию здесь рассматривать не будем: она достаточно тривиальна в реализации (и может сводиться к нескольким правилам в конфигурационном файле сервера). Нам более интересен вопрос уменьшения CSS-кода в соответствии с CSS-спецификацией. Здесь разумнее всего воспользоваться одним из трех путей.

  • Набор простых регулярных выражений (он был описан еще в книге "Разгони свой сайт"). Ниже приведен его код на Perl.
    $data = ? s!\/\*(.*?)\*\/!!g; # удаляем комментарии
    $data = ? s!\s+! !g; # сжимаем пробелы
    $data = ? s!\} !}\n!g; # добавляем переводы строки
    $data = ? s!\n$!!; # удаляем последний перевод
    строки
    $data = ? s! \{ ! {!g; # удаляем лишние пробелы
    внутри скобок
    $data = ? s!; \}!}!g; # удаляем лишние пробелы и
    синтаксис внутри скобок
  • CSS Tidy (http://csstidy.sourceforge.net/) — наиболее мощная библиотека для разбора CSS-правил. Для ее использования необходимо загрузить ее в папку проекта, внести изменения в настройки по умолчанию (находятся в файле class.csstidy.php ) и осуществить минимизацию простыми вызовами:
    $css = new csstidy();
    $css->load_template($root_dir . 'css.template.tpl');
    $css->parse($css_code);
    echo $css->print->formatted();

    При этом для максимального сжатия лучше использовать следующий шаблон ( css.template.tpl ):

    |{||{|||;|}||}||{||

  • YUI Compressor (http://developer.yahoo.com/yui/compressor/). Эта библиотека требует установленной Java на сервере и запускается еще проще. Необходимо из командной строки выполнить:
    java -jar yuicompressor.jar -o output.css input.css

    Результат произведенных действий будет сохранен в файле output.css.

Полный код для CSS Tidy и все аспекты практической реализации можно почерпнуть из исходного кода Web Optimizer (http://www.web-optimizer.ru/).

Сразу стоит оговориться, что в Intenet Explorer (по 8-ю версию включительно) есть проблема с отображением более 4096 (по сведениям из MSDN, http://msdn.microsoft.com/en-us/library/aa358796(VS.85).aspx) CSS-селекторов из одного файла (и ограничение в 32 на число @import). При разработке грамотного процесса объединения CSS-файлов этот момент стоит учитывать.

< Лекция 4 || Лекция 5: 12345 || Лекция 6 >
Сергей Крупко
Сергей Крупко

Добрый день.

Я сейчас прохожу курс  повышения квалификации  - "Профессиональное веб-программирование". Мне нужно получить диплом по этому курсу. Я так полагаю нужно его оплатить чтобы получить диплом о повышении квалификации. Как мне оплатить этот курс?

 

Галина Башкирова
Галина Башкирова

Здравствуйте, недавно закончила курс по проф веб программиованию, мне прислали методические указания с примерами тем, однако темы там для специальности 

Системный администратор информационно-коммуникационных» систем.
Мне нужно самой найти тему? или делать по высланным темам

 

Александр Юдичев
Александр Юдичев
Россия
Даниил Климович
Даниил Климович
Беларусь, Гомель