Погоня за увеличением скорости загрузки страниц продолжается. Представим, что мы всё сжали, минифицировали, объединили в один файл, скрипты "опустили", используем svg-спрайты и всё в этом духе. Что дальше? Одно из трендовых инноваций сегодня (особенно актуально на мобильниках), это размещение стилей верхней части страницы прямо в тег head и асинхронная подгрузка остальных файлов. Данную рекомендацию сплошь и рядом пропагандируют все сервисы тестирования производительности, в том числе и гугловый, так как браузер не начнёт рендерить страницу и не покажет её пользователю, пока не получит все linkи в head. Статья на тему.
В своём примере я не выделяю стили верхней части страницы, а подключаю весь закешированный битриксом файл стилей в теге head. Хочу сразу предупредить, что данный метод стоить тестировать на производительность на конкретном сайте, в некоторых случаях это может увеличить First byte из-за работы с большими строками через регулярные выражения, в некоторых сильно увеличит объём страницы и уменьшит объём кешируемых данных, что приведёт к обратному эффекту.
/* Для замера скорости работы */
$start = microtime(true);
/* Получить пути подключенных файлов стилей */
$css_str = Bitrix\Main\Page\Asset::getInstance()->GetCSS();
preg_match_all('/href="(.*)"\stype/', $css_str, $matches);
/*
* Перебираем результат, так как метод возвращает все подключаемые файлы стилей
* нас интересует только кеш объединённых файлов, содержит в названии 'template_'
*/
foreach($matches[1] as $val)
{
$val = explode('?', $val);
$full_path = $_SERVER['DOCUMENT_ROOT'] . $val[0];
/*
* Производим подмену только для закешированного объединённого файла
* если он существует и не пуст (на пустоту проверка дальше)
*/
if (strpos($full_path, 'template_') !== false && file_exists($full_path))
{
$st yle = trim( file_get_contents($full_path) );
if (!empty($style))
{
$search = '<st yle type="text/css"></style>';
$replace = '<st yle type="text/css">'. $style .'</style>';
// Вставить стили
$content = str_replace($search, $replace, $content);
// Вырезить <li nk>
$content = preg_replace('/<li nk\shref="'. addcslashes($val[0], '/') .'(.*)>/', '', $content);
}
}
}
/* Вставляем время исполнения кода в заранее подготовленное место */
$content = str_replace('#time_replace#', microtime(true) - $start, $content);
Немного истории. Bitrix Framework буфферизирует весь клиентский код. Данный подход делает возможным работу отложенных функций и прочей манипуляции с содержимым сайта «выше», после того как мы находимся уже совершенно в другой части исполнения страницы и не имеем возможности перепроектировать последовательность под конкретный случай.
В своём решении я использую событие OnEndBufferContent (вызывается при выводе буфферизированного контента), у него имеется один аргумент $content – тот самый буфферизированный контент, который мы будем править.
Идеязаключается в том, чтобы найти подключенный закешированный (объединённый и минифицированный) файл стилей, получить его содержимое, вырезать его подключение из кода и вставить содержимое в теге <style> в head. Маркер для вставки в виде пустого блока стилей, на случай если что-то пойдёт не так, страница останется целой и валидной:
<st yle type="text/css"></style>
Метод будет работает стабильно всегда, так как получает имя сгенерированного битриксом файла через специальный api-метод, ищет его в контенте и ещё раз проверяет файл по имени (содержит template_), проверяет его существование на сервере и если он не пустой, вставляет в документ и очищает контент от link’a только вставленного файла.
Результат можно посмотреть здесь, не обещаю, что будет всегда включено Ещё, кстати, убираю css-комментарии и лишние переносы, это просто по фану. Не рекомендую сильно заморачиваться, также не рекомендую заморачиваться по поводу вырезания пробелов или ещё какой-то манипуляции с контентом, так как не стоит забывать, что html-результат сжимается gzip’ом, по хорошему конечно Но когда вы дойдёте до применения вставки стилей в head, то всё остальное по умолчанию у вас будет по феншую.
/*
* Убрать комментарии из стилей и лишние переносы
* можно применить как ко всему контенту, так и только к файлам стилей, но лучше вообще не заморачиваться :)
*/
$arReplace = array(
"/\\/\\*(.*)\\*\\//" => "",
"/\n+/" => "\n",
);
$content = preg_replace(array_flip($arReplace), $arReplace, $content);
Метод протестирован с объединёнными файлами стилей и без, со сжатыми, минифицированными и без. Даже если вдруг произойдёт вставка не того файла, то только его подключение и будет вырезано, но такого замечено не было. Ещё по поводу времени исполнения, решайте сами, что для вас быстро или медленно, смотрите как это влияет на общую скорость отработки страницы в вашем случае. У меня это ~0,0009, но и контента на приведённом в примере сайте очень мало. Также не стоит забывать, что подключенные внешние файлы стилей будут кешироваться браузером и такой прямой подход как здесь увеличит объём страницы и уменьшит кол-во кешируемых данных, так что подходит не везде. Если у вас не много стилей, маленький или среднего размера проект, то скорее всего вы получите прирост. На крупных проектах метод не стоит использовать или стоит дорабатывать.
Ремарка про шрифты,
var resource = document.createElement('link');
resource.setAttribute("rel", "stylesheet");
resource.setAttribute("href","https://fonts.googleapis.com/css?family=Open+Sans+Condensed:700|Open+Sans:400,600&subset=cyrillic,cyrillic-ext");
var head = document.getElementsByTagName('head')[0];
head.appendChild(resource);
Код асинхронно подгружает файлы шрифтов, подключаемых с fonts.googleapis.com. Но это создаёт одну неприятность. После загрузки шрифта, если в этот момент сайт уже отрендерин, страница заметно моргнёт. Проблему наблюдал в chrome, решение искать не стал и от идеи отказался, мигание ушло. Если вас ваше руководство/клиента это устроит, используйте, гугл прибавит вам пару «попугаев» к тесту. Если кому известно в чём причина и как исправить, пишите в комментариях. Наличие или отсутствие data-skip-moving на результат не влияет. При повторном открытие стр. проблемы нет, так как файл шрифтов уже в кеше браузера.
Артемий Зайцев написал: Какая-то фантастическая оптимизация. Чтобы выиграть миллисекунды в одном месте, увеличиваем вес всей страницы и проигрываем в другом.
Все же есть некоторая разница между реально замеренными величинами производительности (вес страницы, количество ресурсов) и впечатлением производительности. Страница, в которой важные стили вынесены в head будет визуально загружаться быстрее, хотя и по факту объем ресурсов окажется больше.
Артемий Зайцев написал: Что если в нашей минифицированной CSS 10000 строк?
Вопросы оптимизации всегда носят частный характер, я бы постарался разделить билд стилей на две части – для head страницы и для подключаемого файла css.
Артемий Зайцев написал: Раньше, когда мы не делали адаптивные сайты, компьютеры были слабые и интернет был медленным, во всех шаблонах писали <img ... widht="<?=$width?>" height="<?=$heigth?>" />. Чтобы при загрузке картинок контент не скакал.
Сейчас в этом тоже есть смысл. Браузер на основе стилей поставит в страницу болванку куда загрузится картинка. Контент подскочит в момент расчета cssom, а если не указать размеры картинки, то в момент получения этой картинки, а это может произойти гораздо позже (особенно с ленивой подгрузкой... помню как-то для мега оптимизации под картинку ставил svg с размерами и респонсив стилями как у картинки, но это уже совсем другая история ).
Артемий, как правильно выразился Олег, оптимизация носит частный характер. Если css будет иметь 10 тыс. строк, не используйте метод. Глубинное понимание процессов работы браузеров, залог профессиональной разработки. В данном случае решается проблема блокировки страницы, до полной загрузки внешних ресурсов в head, особенно это актуально для мобильных телефонов. В итоге стр. будет открываться более приятно. Ещё обратите внимание, на то, что в комментариях написал Sergey, можно допилить код и при следующих посещениях выдача будет немного отличаться, мне эта идея очень понравилась. Может кто-то хочет это реализовать?
Не учтено, что пользователь смотрит на сайте обычно чуть больше 1 страницы. А значит, если на всех этих страницах есть общие стили и js, то браузер их может закешировать. Но только если они подключены отдельно. Подключение "по месту" не закешировать (это же надо ещё как-то сообразить выковырять). Так что если вы не дорвей выигрыш от такой тактики мне кажется сомнительным.
Алексей Задойный, учтено. Об этом есть в статье, что смотреть надо на конкретный сайт. Во многих случаях жертва некеширования внешних стилей оправдана и работает эффективнее, но не всегда, читайте комментарии.
Самый правильный способ такой (ИМХО): 1. Грамотно верстаем. Выделяем стили, отвечающие за скелет страницы. 2. Все эти стили в хеадер пихаем. Можно сделать проверку по кукам и сессии и только при первом посещении в хеадере выдавать эти стили. Причем они же должны дублироваться в основных файлах стилей, которые закэшируются браузером и не будут грузиться второй раз.
Но по оптимизации гораздо больше работ, чем просто стили подключить. 1. Если шрифты неприятно дергают страницу после загрузки - подбираем штатный шрифт с похожими размерами. Тогда при загрузке шрифта разница будет не столь заметна. 2. Грузим шрифты, например, с гугла, если это популярные бесплатные шрифты. Тогда есть вероятность, что у пользователя в браузере уже есть нужный закэшированый скрипт. 3. Не забываем про изображения. Это главный бич на данный момент. Гуглспид стал анализировать всю страницу, а не только верхнюю часть. А для всей сраницы гораздо сложнее заставить грузить оптимизированный контент - поэтому юзаем ленивую загрузку. 4. Ленивая загрузка частично решает проблему картинок для гуглспида, но не решает проблему трафика. a) Битрикс штатно не умеет подсовывать изображения нужного разрешения (да и не должен уметь в общем-то). В итоге грузим банер для разрешения в 2560px шириной и потом все это отображаем на мобильном телефоне. б) Если вы думаете, что скрыв блок баннеров адаптивом (display: none) проблема решается - я вас разочарую. Браузеры грузят это все, чтобы "сайт работал быстро" (раньше мы для этого грузили нужные картинки в блок 1*1пиксел). в) Та же беда с обычными картинками по контенту, только решается чуть сложнее, чем с баннерами. г) И, да. PHP не умеет качественно жать картинки. Гугл все равно ткнет носом, что можно бы ещё несколько кб с каждой картинки срезать, даже если вы уже поставили сжатие "хуже некуда"
Ага, достаточно выделить grid и цвета, фоны элементов.
Sergey Emelyanov написал: 2. Все эти стили в хеадер пихаем. Можно сделать проверку по кукам и сессии и только при первом посещении в хеадере выдавать эти стили. Причем они же должны дублироваться в основных файлах стилей, которые закэшируются браузером и не будут грузиться второй раз.
Куку будет сложно с композитом подружить, особенно, если композитный кеш отдается ngix-ом.
Точно, про композит забыл. Теоретически, если грамотно выделить нужные стили - это 2-3 кб дополнительно к странице (если жмется сервером страница и того меньше). Думаю это не столь критично.
Группы на сайте создаются не только сотрудниками «1С-Битрикс», но и партнерами компании. Поэтому мнения участников групп могут не совпадать с позицией компании «1С-Битрикс».