Примечание: ниже частичный перевод статьи "Compress JavaScript and CSS without touching your application code", в которой описывается статичное сжатие CSS- и JS-файлов на сервере и корректная выдача их затем клиенту. Далее даны мои комментарии с более комплексным решением. Приношу извинения, если для кого-то тема будет слишком знакома или неинтересна: в Рунете нормальной статьи на данную конкретную тематику обнаружить не удалось.
Для обеспечения корректного архивирования вашего веб-контента, по-видимому, наиболее общий подход будет заключаться в выполнении по порядку следующих пунктов:
.htaccess
, чтобы обеспечить корректный content type.Проблема заключается в том, что все эти шаги вам придется выполнить самостоятельно. Сжатие данных «на лету», возможно, не будет наиболее оптимальным решением, тем более, что я не являюсь фанатом архивации файлов в серверных приложениях — этим должен заниматься сам сервер.
Однако, существует способ обойтись просто парой строчек в конфиге (.htaccess), если потратить пару минут и самому заархивировать все необходимые файлы. Предположим, что у нас есть JS-библиотека prototype.js на сервере. Заархивируйте ее через gzip (при помощи 7-zip или еще чего-нибудь, если вы работаете под Windows). В итоге, у вас должен появиться файл prototype.js.gz. Положите его в ту же директорию на сервере, что и исходный файл. Если вы работаете через командную строку в шеле, то достаточно выполнить:
gzip foo.js -c > foo.js.gz
Теперь добавим следующие строки в .htaccess в корень вашего сайта (прим. лично я бы рекомендовал записать прямо в конфиге апача). При этом нужно будет включить RewriteEngine, если он еще не подключен:
AddEncoding gzip .gz RewriteCond %{HTTP:Accept-encoding} gzip RewriteCond %{HTTP_USER_AGENT} !Safari RewriteCond %{REQUEST_FILENAME}.gz -f RewriteRule ^(.*)$ $1.gz [QSA,L]
В первой строке мы сообщаем серверу, что файлы с расширением .gz нужно отдавать с gzip encoding-type, чтобы браузер понял, что перед ним архив, а не текстовый файл. Второй строкой проверяется, принимает ли браузер архивированные файлы, при этом следующие строки просто не отработают, если этот тест провалится. Далее мы исключаем Safari, у которого проблемы с правильным восприятием архивированного содержимого. На четвертой строке мы проверяем, что архивный файл существует, и если это так, то добавляем .gz к существующему имени файла.
С такой конфигурацией вы можете загружать сжатые версии ваших файлов на сервер и Apache сможет отдавать их вместо обычных, если сможет это сделать, при этом вам не придется менять теги <script> или любые вызовы в веб-приложениях.
Примечание: далее идут мои рассуждения на тему и собственное решение.
Во-первых, тесты под некоторыми Konqueror'ами показали, что он тоже не понимает заархивированных файлов (CSS- и JS-), поэтому ради безопасности десятых долей процента посетителей от сердечного приступа (когда они увидят сайт без соответствующих стилей) в этот набор правил его стоит добавить.
Во-вторых, мне не нравится, что при каждом запросе (ну или при 99%) к статичному ресурсу, Apache должен что-то искать и менять физический адрес, по которому этот файл нужно брать. Я против таких решений, поэтому я предложу другой путь: давайте будем переопределять физический адрес ресурса только для «старых» браузеров, а для всех остальных отдавать рядовой файл.
<IfModule mod_rewrite.c> RewriteEngine On AddEncoding gzip .gz RewriteCond %{HTTP:Accept-encoding} !gzip [OR] RewriteCond %{HTTP_USER_AGENT} Safari [OR] RewriteCond %{HTTP_USER_AGENT} Konqueror RewriteRule ^(.*)\.gz(\?.+)?$ $1 [QSA,L] </IfModule> <IfModule mod_headers.c> Header append Vary User-Agent <FilesMatch .*\.js.gz$> ForceType text/javascript Header set Content-Encoding: gzip Header set Cache-control: private </FilesMatch> <FilesMatch .*\.css.gz$> ForceType text/css Header set Content-Encoding: gzip Header set Cache-control: private </FilesMatch> </IfModule>
В итоге, логика «переворачивается», и вместо набора «И»-условий мы получаем «ИЛИ»-условия. Первые пять строк уже достаточно подробно обсуждены вверху, я только остановлюсь на последней из них
RewriteRule ^(.*)\.gz(\?.+)?$ $1 [QSA,L]
В ней учитывается, что к статичным ресурсам можно добавлять уникальные GET-строки, чтобы перегружать кеш на клиенте (если ресурс поменялся, то стоит обновить его в кеше, иначе клиенты увидят старую версию). Например, foo.css.gz?v1234
.
Следующие строки (ForceType / set Content-Encoding) форсируют для скриптов и файлов стилей соответствующие MIME-типы и Content-Encoding (ибо как удалось нарыть здесь, так браузеры воспринимают ответ лучше). По совету mod_rewrite
и mod_headers
добавил условие его наличия в Apache, если его нет, то браузеры, не поддерживающие gzip'а для CSS/JS-файлов (это, как было заявлено выше, и Safari), не отобразят страницу правильно. Поэтому стоит лишний раз задуматься, применять ли описанную методику в таких условиях.
Единственное неудобство, которое может возникнуть — придется поменять все вызовы JS-/CCS-файлов на аналогичные с .gz, но если изначально работать с ними, то ничего менять и не придется. При изменении самих ресурсов нужно будет снова их заархивировать и поменять строку вызова в HTML-файлах (чтобы избежать кеширования). При промышленном подходе к разработке, все эти действия автоматизируются, а при кустарном — трудозатраты не так существенны по сравнению с увеличением скорости загрузки сайта (если, конечно, не собирать проект прямо на боевом сайте, без конца gzip'я один и тот же файл :).
Старая версия правил (без учета Konqueror'а) тестировалась здесь, полет нормальный.
UPD: по информации, подчерпнутой отсюда, добавлен заголовок Header append Vary User-Agent
, ибо условие для выдачи gzip'ованного содержание принимается в том числе на основе UserAgent. Однако, после этой статьи добавлено Header set Cache-control: private
для предотвращения проблем с IE.
С выходом браузера от Google, который прикидывается Safari, и появлением gzip у последнего ситуация немного изменилась. Поэтому предлагаю следующий набор правил в качестве наиболее актуального:
<IfModule mod_rewrite.c> RewriteEngine On #перенаправляем Konqueror и «старые браузеры» RewriteCond %{HTTP:Accept-encoding} !gzip [OR] RewriteCond %{HTTP_USER_AGENT} Konqueror RewriteRule ^(.*)\.(css|js)$ $1.nogzip.$2 [QSA,L] </IfModule> <IfModule mod_headers.c> Header append Vary User-Agent #выставляем для всех css/js файлов Content-Encoding <FilesMatch .*\.(js|css)$> Header set Content-Encoding: gzip Header set Cache-control: private </FilesMatch> #сбрасываем Content-Encoding в том случае, если отдаем не архив <FilesMatch .*\.nogzip\.(js|css)$> Header unset Content-Encoding </FilesMatch> </IfModule>
В Safari есть небольшой баг: этот браузер не воспринимает файлы стилей/скриптов с расширением .gz. Но это, естественно, обходится. Нам нужно на месте обычных файлов просто иметь архивы (чтобы лишний раз не перенаправлять браузеры), а на месте .nogzip
-файлов (см. чуть выше по коду) должны находиться непожатые версии. Такие вот пироги.
Если что-то осталось неясным после прочтения последнего раздела, то алгоритм действий должен быть следующим (большое спасибо godfather за помощь в формулировке):
.htaccess
gzip
) и кладем на место обычных (расширение у файлов должно остаться прежним, .css
или .js
). Например, вы берете файл anyname.css
, пакуете его 7-zip, у вас получается файл anyname.css.gz
, переименовываем его обратно в anyname.css
и заливаем на сервер. Для gzip
все немного проще:gzip -c -9 -n anyname.css > anyname.css.gz mv anyname.css anyname.nogzip.css mv anyname.css.gz anyname.css
nogzip.css
или nogzip.js
, которые содержат неархивированные копии. Например, после заливки сжатого файла anyname.css
, вы создаете на сервере еще один файл anyname.nogzip.css
, который является копией несжатого файла. Для gzip
это копирование уже производится чуть выше второй строкой в листинге.В некоторых случаях возможны проблемы со внутренними редиректами для Apache (они превращаются во внешние из-за взаимодействия с текущей логикой редиректов для сайта). В таких случаях (если возникла проблема циклического перенаправления запроса к несжатым ресурсам) стоит прописать в конфигурации следующее:
RewriteCond %{REQUEST_FILENAME} !.nogzip RewriteCond %{HTTP:Accept-encoding} !gzip RewriteRule ^(.*)\.(css|js)?$ $1.nogzip.$2 [QSA,L] RewriteCond %{REQUEST_FILENAME} !.nogzip RewriteCond %{HTTP_USER_AGENT} Konqueror RewriteRule ^(.*)\.(css|js)?$ $1.nogzip.$2 [QSA,L]
Таким образом мы запрещаем редиректы для файлов, имеющих .nogzip
в своем имени, и предотвращаем образование циклов. Спасибо