В случае с CSS можно начать с полезной модели, состоящей из двух стилей: основного стиля и стиля подраздела. Единый основной стиль используется во всем приложении, а разные стили подразделов контролируют разные функциональные области. В этом случае подавляющее большинство страниц подгружают только два стиля (один из которых кэшируется при первой загрузке — это, конечно, основной).
Для небольших наборов CSS и JavaScript такой подход может замедлить обработку первого запроса (в сравнении с «монолитным» подходом), но если сохранять количество компонентов на относительно низком уровне, то возможно и ускорение работы, поскольку в расчете на одну страницу приходится загружать меньше данных.
Когда речь заходит о сжатии, многие немедленно вспоминают mod_gzip. Однако с ним нужно соблюдать осторожность — mod_gzip, в общем-то, является злом или, по меньшей мере, причиной кошмарного расходования ресурсов. Основная идея сжатия проста. Браузеры, запрашивая ресурсы, указывают в заголовке, в каком виде они могут принять содержимое страницы. Это может выглядеть, например, так:
Accept-Encoding: gzip,deflate
Видя такой заголовок, сервер сжимает отсылаемое содержимое методами gzip или deflate с тем, чтобы клиент распаковал его на месте. Это загружает процессор как на клиенте, так и на сервере, однако уменьшает объем передаваемых данных. Это нормально. Однако вот как работает mod_gzip: он сжимает данные во временный файл на диске, а после отсылки данных удаляет этот файл. На больших системах вы очень быстро столкнетесь с ограничениями скорости ввода/вывода. Этого можно избежать, используя вместо mod_gzip mod_deflate (только в Apache 2), поскольку последний сжимает данные в оперативной памяти. Пользователи первой версии Apache могут создать RAM-диск и хранить временные файлы mod_gzip там — это не так быстро, как работа напрямую с оперативкой, но намного шустрее, чем писать все на жесткий диск.
Но даже без этого мы можем полностью избежать вышеописанных проблем, используя предварительное сжатие нужных статических ресурсов и последующую отправку их клиенту с помощью mod_gzip. Если добавить предварительное сжатие в сборку билда, то сам процесс пройдет для нас совершенно незаметно. Файлов, требующих упаковки, обычно не так уж много — мы не сжимаем изображения (поскольку это вряд ли приведет хоть к какой-то экономии — изображения и так уже достаточно сжаты), так что нам остается упаковать JavaScript, CSS и прочий статический контент. Но мы должны указать mod_gzip, где искать файлы для предварительной компрессии:
mod_gzip_can_negotiate Yes
mod_gzip_static_suffix .gz
AddEncoding gzip .gz
В последних версиях mod_gzip (от 1.3.26.1a и выше) для автоматической предварительной упаковки файлов в конфигурационных опциях достаточно добавить одну строчку. Нужно лишь удостовериться, что в Apache установлены корректные разрешения на создание и перезапись упакованных файлов.
mod_gzip_update_static Yes
Но не все так просто. Текущие версии Netscape 4 (точнее, 4.06—4.08) в заголовке утверждают, что понимают содержимое, сжатое с помощью gzip, однако, на самом деле, не умеют распаковывать эти архивы. Прочие версии Netscape 4 тоже испытывают разнообразные трудности с загрузкой сжатых скриптов и стилей. А значит, мы должны отсекать этих агентов на стороне сервера и подставлять им неупакованный контент. Это довольно просто. Куда интереснее проблемы, возникающие у Internet Explorer (версии с 4 по 6).
Загружая сжатый с помощью gzip JavaScript, Internet Explorer порой некорректно распаковывает его или прерывает распаковку в процессе, отдавая клиенту половину файла. Если для вас критична работоспособность JavaScript, вы должны избегать отсылки сжатых скриптов по запросу IE. Даже в тех случаях, когда IE способен загрузить сжатый JavaScript, он зачастую не кэширует его, независимо от указаний, записанных в тегах (актуально для некоторых версий IE 5.x).
Поскольку сжатие с помощью gzip иногда выходит себе дороже, мы можем обратиться к другим способам упаковки контента, не предполагающих смену формата. Сейчас доступно множество скриптов, сжимающих JavaScript, большая часть которых использует для уменьшения исходного кода наборы правил на основе регулярных выражений. С их помощью мы действительно можем сделать код меньше, удалив комментарии, лишние пробелы, сократив имена переменных и убрав необязательные синтаксические конструкции.
К сожалению, подавляющее большинство этих скриптов либо не слишком эффективны, либо в определенных случаях разрушают код (а иногда — и то и другое вместе). Без подробного грамматического разбора упаковщику трудно отличить комментарий от похожей конструкции, размещенной в закавыченной строке. Кроме того, с помощью регулярных выражений не так-то просто оценить, какая из переменных имеет ограниченный контекст, так что некоторые техники сокращения имен переменных могут разрушить сам код.
Этих проблем можно избежать, сжимая код с помощью Dojo Compressor (alex.dojotoolkit.org/shrinksafe), использующий Rhino (мозилловский JavaScript-движок, написанный на Java) для построения дерева, которое оптимизируется перед работой с файлами. С работой Dojo Compressor справляется неплохо, ресурсов отнимает немного. Расширив наш процесс сборки билда с помощью этого инструмента, мы можем забыть об экономии, писать пространные комментарии, вставлять сколько угодно пробелов и т. д. На рабочем коде это нисколько не отразится.
По сравнению с JavaScript CSS упаковывать легко. Поскольку в стилях практически не используются закавыченные строки (как правило, это пути или названия шрифтов), мы можем справиться с пробелами с помощью регулярных выражений. Если же у нас закавыченные строчки все же есть, мы почти всегда можем свести последовательности пробелов к одному пробелу (маловероятно, что последовательности, состоящие из нескольких пробелов, встретятся нам в указаниях путей или названиях шрифтов). Для этого нам вполне хватит простенького скрипта на Perclass="underline"
#!/usr/bin/perl
my $data = ‘’;
open F, $ARGV[0] or die «Can’t open source file: $!»;
$data .= $_ while <F>;
$data =~ s!/*(.*?)*/!!g; # remove comments
$data =~ s!s+! !g; # collapse space
$data =~ s!} !}n!g; # add line breaks
$data =~ s!n$!!; # remove last break
$data =~ s! { ! {!g; # trim inside brackets
$data =~ s!; }!}!g; # trim inside brackets
print $data;
«Скормим» этому скрипту все имеющиеся у нас CSS по очереди:
perl compress.pl site.source.css > site.compress.css
С помощью такой несложной оптимизации мы можем уменьшить объем передаваемых данных на 50 процентов (во многом это зависит от вашего стиля кодирования — выигрыш может быть и гораздо меньше), а значит, увеличить скорость работы конечного пользователя. Но в идеале нам хотелось бы, чтобы пользователи вообще не запрашивали файлы до тех пор, пока это не станет совершенно необходимо. И для этого нам придется заняться HTTP-кэшированием.
Когда пользовательский агент запрашивает данные с сервера первый раз, он кэширует ответ, чтобы избегать повторных запросов в будущем. Как долго будет храниться этот кэш, зависит от двух факторов — настроек агента и соответствующих заголовков с сервера. Опции настройки агентов имеют незначительные различия, однако большинство из них сохраняет кэш по меньшей мере до окончания сессии, если им прямо не указано обратное.
Вы посылаете заголовки, запрещающие кэширование динамических страниц, чтобы не позволить браузеру кэшировать страницы, которые постоянно изменяются. В PHP это делается с помощью одной строчки:
header(«Cache-Controclass="underline" private»);
Слишком просто, чтобы быть правдой? Ну, в общем-то, да — некоторые агенты порой игнорируют этот заголовок. Чтобы по-настоящему запретить браузеру кэшировать документ, следует быть немного более убедительным: