CSS и JS в одном файле
Раз уж я затронул тему снижения числа коннектов к серверу при загрузке сайта, то было бы уместо вспомнить незаслуженно забытую технику — объединение JS и CSS в одном файле.
Насколько я знаю, первым эту технику исследовал некий ShivaP в статье «Combine CSS with JS and make it into a single download!» ещё четыре года назад.
Идея такова: в файле специальными ухищрениями помещается и код JavaScript, и код CSS, файл подключается два раза — один раз через тег SCRIPT, второй раз через тег LINK (как CSS), браузер грузит файл один раз, а второй раз берёт его уже из кеша.
Автор проверил свою идею на двух браузерах — IE и FireFox, на том и остановился.
Я проверил эту идею на гораздо бо́льшем числе браузеров (обнаружил проблемы на некоторых «Хромах» и исправил), а так же немного её видоизменил, разрешив использовать многострочный CSS без особых неудобств. Последнее нужно персонально мне, так как у меня нет возможности прогонять свой CSS через оптимизаторы и вытягивать таким образом код CSS в одну строку, а я хочу использовать эту технику у себя на сайте.
У техники есть ограничение, которое не портит много крови, но о нём необходимо знать — в тексте CSS и JS не должна встречаться конструкция «*/». Если в CSS достаточно просто выкусить все комментарии («*/» там, можно считать, нигде больше встретиться не может), то в JS за этим придётся следить, так как такая комбинация вполне может встретиться, в частности, в регулярных выражениях. Например: var regexp = /smth*/g;
В таких случаях звёздочку необходимо прятать (например, заменять её в регулярных выражениях на \x2A или разбивать строку: «smth*» + «/»).
Как ShivaP, первый исследователь этой техники, решил проблему размещения JS и CSS в одном файле можно почитать в оринальной статье, я же поступил несколько иначе, но сходным образом. Конструкции размещаются так, чтобы JS оказывался в комментариях CSS и наоборот. Я похожими вещами уже занимался неоднократно.
Мой код выглядит следующим образом (я поставил рядом две картинки, чтобы можно было сравнить как один и тот же код воспринимается интерпретатором JavaScript и парсером CSS):
Как видите, принцип довольно несложен.
Теперь несколько слов о том как это всё подключается:
<link type="text/css" rel="stylesheet" href="core.jscss" />
<script type="text/javascript" src="core.jscss#2"></script>
Обратите внимание на «#2» после core.jscss, этот хак позволяет обойти странное поведение некоторых версий «Хрома» — без него браузер не делал попыток подгрузить файл ещё раз и JavaScript не выполнялся.
Немаловажно так же, что заголовок «content-type» должен быть выставлен в «*/*», чтобы избежать проблем с FireFox 2.
У себя на сайте я буду использовать скрипт наподобие нижеприведённого, чтобы разом разрешить все проблемы и выставить правильные заголовки.
<?
ob_start();
$dir = 'media/';
$expires = 10 * 365 * 24 * 60 * 60; // 10 years
$files = explode(',', str_replace('..', '', $_SERVER['QUERY_STRING']));
$css = $js = array();
foreach ($files as $file) {
switch (pathinfo($file, PATHINFO_EXTENSION)) {
case 'css':
$css[] = $dir . $file;
break;
case 'js':
$js[] = $dir . $file;
break;
}
}
$css = @array_map('file_get_contents', array_unique($css));
$js = @array_map('file_get_contents', array_unique($js));
$css = implode($css);
$js = implode($js, ';');
// removes /* comments */ from css
$css = preg_replace('!/\*.*?\*/!s', '', $css);
// removes only first /* comment */ from js
$js = preg_replace('!^/\*.*?\*/!s', '', $js);
echo <<<JSCSS
// /*
$js
"*/{"/*"}
$css
*/
JSCSS;
$len = ob_get_length();
$txt = ob_get_clean();
$etag = '"' . sha1($etag) . '"';
header("Content-type: */*; charset=utf-8");
header("Expires: " . gmdate('D, d M Y H:i:s \G\M\T', $_SERVER['REQUEST_TIME'] + $expires));
header("Cache-control: max-age=$expires, public");
header("Content-length: " . $len);
header('ETag: ' . $etag);
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
list ($hash) = explode(';', $_SERVER['HTTP_IF_NONE_MATCH']);
if ($hash == $etag) {
header('HTTP/1.1 304 Not modified, thanx for yr question');
exit;
}
}
echo $txt;
Формат вызова простой: скрипту через запятую перечисляются файлы, которые нужно объединить и подключить (файлы берутся из папки «media», см. переменную «$dir»):
/util/js+css.php?core.js,core.css,another.css,another.js
Евгений, спасибо за статью!
Вы считаете это готовым к использованию в боевых проектах?
Комментарий для Виктор:
Пока не знаю, собираюсь попробовать на блоге и посмотреть не будет ли проблем.
Кстати, есть страничка, где можно проверить как это работает под вашим браузером: http://bolknote.ru/files/exp-jscss/
Комментарий для Евгения Степанищева:
C одной стороны идея неплохая, на один коннект меньше, но с другой стороны — концептуально неверно смешивать оформление и код. Скрипты могут быть разными на разных страницах или не везде вообще нужны. А не проще все держать в отдельных файлах и подключать просто серверным инклюдом — тогда вообще один-единственный запрос будет, если конечно не считать картинок и прочих файлов с контентом?
Комментарий для reon.livejournal.com:
Он и не смешан. Оформление в CSS, код в JavaScript. То, что это приходит в одном файле к клиенту, концептуально ничего не меняет.
Если сайт состоит из одной страницы, так и надо делать, в остальных случаях так делать нельзя. Вынесенный в отдельный файл CSS и JS снижает вес HTML-страницы. Когда клиент перейдёт на неё, ресурсы (JS и CSS) будут загружены из кеша.
А что, если грузить честный js, который бы сам проставил css-свойства? Я понимаю, что минусы есть, но интересен именно твой взгляд.
Интересно, но в некоторых проектах Яндекса (уже не помню где копался), огромные портянки ЦСС шли прямо в коде самой странички, не подключаясь извне.
И еще, кажется у тебя на сайте была статья о подключении дополнительной .js из подключаемого .js файла. Выглядело как обычный вывод строки. Ведь ни что не мешает написать скрипт который оформит два document.write, и модно даже многострочники оформлять, заменяя перевод строки на /n.
Комментарий для kozlov.am:
Мне кажется или у тебя в строчке «header(’HTTP/1.1 304 Not modified, thanx for yr question’);» есть опечатка.
Комментарий для http://orcinus.ru:
Вроде всё верно написано, где опечатка?
Комментарий для http://orcinus.ru:
Я ничего про это сказать не могу, просто не знаю о таких случаях.
Производительность такого решения ниже.
Комментарий для http://orcinus.ru:
Производительность ниже, так как скрипт сначала придётся разместить в DOM, потом проинтерпретировать. Кроме того, это начисто убивает возможность использования defer при необходимости.
Firebug 1.7 в FF4 отказывается показывать текст сценария на странице с примером, т. е. все отрабатывает как надо и случайное число вставляется, но посмотреть и подебажить нельзя (
«Содержимое, находящееся по указанному URL не является текстом: http://bolknote.ru/files/exp-jscss/js%2Bcss.php?1.js%2C1.css%C2%BB пишет.
С CSS-ом все ок.
Включить на сервере поддержку KeepAlive и забыть про эти хаки, как страшный сон.
Комментарий для maxim-zotov:
Keep-Alive включен и он тут совершенно мимо.
Комментарий для maxim-zotov:
Вы статью-то прочитайте внимательно.
Комментарий для Евгения Степанищева:
На сайте bolknote.ru keepalive выключен. Он тут не мимо, поскольку ваша цель уменьшить число соединений. Суть KeepAlive как раз в том, чтобы делать несколько запросов в одном соединении.
Увы, за 12 лет существования HTTP/1.0 производители браузеров и веб-серверов так и не довели до ума http-pipelining ( http://en.wikipedia.org/wiki/HTTP_pipelining ), с ним было бы вообще хорошо, по сетевому обмену это полный аналог вашего извращения, только прямой и стандартный, без головоломок «напиши хело ворлд на 10 языках в одном файле». Как мозговая зарядка нормально, как технический прием в реальной работе — ниже плинтуса.
Комментарий для maxim-zotov:
Опечатка, должно быть HTTP/1.1
Комментарий для maxim-zotov:
Извините, наврал. Включён.
Комментарий для maxim-zotov:
Цель не уменьшить количество соединений, а уменьшить время загрузки — соединения тут лишь постольку поскольку их количество в браузере ограничено.
Комментарий для SiMM:
Ну да, ну да. Подмена отдачи статического файла вызовом php-скрипта это вообще изощрённое убийство сервера.
Что до времени, вот честно скажу, 5+5 миллисекунд значительно меньше 35 миллисекунд.
$ http_ping http://bolknote.ru/files/exp-jscss/js%2Bcss.php?1.js%2C1.css
153 bytes from http://bolknote.ru/files/exp-jscss/js%2Bcss.php?1.js%2C1.css: 35.912 ms (2.01c/33.867r/0.035d)
$ http_ping http://bolknote.ru/pavatar.png
3939 bytes from http://bolknote.ru/pavatar.png: 5.874 ms (2.113c/3.525r/0.236d)
Комментарий для maxim-zotov:
Отдаваемый файл вполне может быть сохранён на сервере и отдаваться статически. Концептуально, решение не заставляет генерировать его на каждый запрос.
Комментарий для maxim-zotov:
В методике нигде не написано, что есть такая необходимость. Ссылка, которую вы используете, описана как «страничка, где можно проверить как это работает под вашим браузером», а не «рабочее решение, которое используется у меня на блоге».
Кстати к вопросу оптимизации времени загрузки — нет ли софтины, которая на автомате оптимизирует все PNG24 в указанной папке, минимизируя число битов на пиксель? Скажем, двухцветная PNG’шка в PNG24 весит заметно больше, чем могла бы в двухбитном варианте.
Комментарий для mr-simm.livejournal.com:
Все известные мне методы оптимизации графики есть в этой книге: http://speedupyourwebsite.ru/books/reactive-websites/
Прочитайте, я уверен, найдёте для себя массу полезного.
Это можно видеть на странице с результататми поиска.
Комментарий для id.rambler.ru/users/16051976:
Вероятно это связано с каким-то исследованиями. Рискну предположить, что скорость загрузки первой страницы поиска очень и очень важна, так как большинство людей пытаются воспользоваться результатами поиска только на ней. Именно поэтому сделали всё, чтобы эта страница грузилась как можно быстрее, жертвуя более быстрой загрузкой остальных страниц поиска.
Комментарий для Евгения Степанищева:
Не рискуйте, Евгений :-)
На самом деле сss и js вшиты в код всех страниц выдачи яндекса..
Видимо SERP формируется допотопным скриптом.
Планируется смена архитектуры, а допиливать существующую никто не хочет.
Комментарий для openid.yandex.ru/nextstation/:
Мне-то хоть немного видно что в SERP происходит, я могу делать какие-то выводы, а у вас-то такое откуда? :)
Там ничего не делается просто так и никто ничего не забрасывает, это «градообразующий» сервис всего Яндекса, наш поиск.
Комментарий для mr-simm.livejournal.com:
mr-simm,
http://rmcreative.ru/blog/post/clio-command-line-image-optimization-1.0
Комментарий для Евгения Степанищева:
Возможно, Яндекс и непрозрачная компания, но не скрывает, что ее архитектура поисковой выдачи устарела.
anatolix.livejournal.com/52547.html
А свои выводы я смогу сделать, просмотрев исходный код страниц выдачи.
О практической реализации: http://bolknote.ru/all/3287
Добрый день! Очень интересный материал. Не могу понять одного — с каким расширением полученный файл — js или css?
Или это .jscss? Как это расширение проставить в имени файла?
Комментарий для Алиса:
Расширение файла ни на что не влияет в данном случае.