Разбор задачи с конкурса Яндекса по ускорению загрузки страницы
Описание, условие и код: https://github.com/yandex-cs/yac2013
Результаты (я семнадцатый): http://cs.yandex.net/ratings
Первый вгляд на состояние вещей:
Тяжелые картинки, множество шрифтов, тонны комментариев, много сриптов при бедной функциональности самой страницы. Страница рендерится за 9-12ms
Как я работал:
Накомитил множество изменений, и мой первый пуш отреджектили, потому что не нашли тег footer. Я удалил тег для того, чтобы избавиться от html5shiv.js, так как footer был единственным html5 тегом на странице. Прикольно, что этот тег не нашелся роботом даже когда я его вернул. Потом я откатился на самое начало, и начал вносить изменения пачками, чтобы проще было понять какие мои агрессивные изменения были непровалидированы роботом. На седьмой пачке изменений, я понял, что все самые влиятельные изменения, которые я могу провести, уже проведены, при этом до вершины олимпа еще далеко. Я успокоился, смирившись с тем, что фронтенд разработчик и ускоритель страниц из меня не тот. Разгадка оказалась проста, она будет ближе к концу.
Детальный взгляд:
Промахи не отсортированы в порядке важности и влиянию на загрузку страницы.
Красивые комментарии в index.html
Выкидываем.
90кб фавиконка.
https://github.com/yandex-cs/yac2013/blob/gh-pages/index.html#L9
https://github.com/yandex-cs/yac2013/blob/gh-pages/assets/img/favicon.ico
Явно не то. Ужимаем до 16x16 пикселей. Не знаю, может какие браузеры понимают большие фавиконки. Быстрый поиск в интернете никаких подсказок не дал. Размер ужатой фавиконки: 0.5 кб.
Множество неиспользуемых скриптов.
https://github.com/yandex-cs/yac2013/blob/gh-pages/index.html#L24
То, что они не используются станет ясно позже. jQuery используется в 1 месте и через жопу, поэтому все скрипты в итоге будут удалены.
SVG в формате base64 в фоне body
Превращаю в png base 64, размер уменьшается с 16 до 5 кб. Нахожу первый ключ:
Огромное изображение (460кб) для хедера позиционируется фоном, показывая только часть.
https://github.com/yandex-cs/yac2013/blob/gh-pages/assets/img/photo_head.jpg
Изображение режим, сохраняем в jpeg с 51% сжатием. Это тот момент, когда фотка максимально пожата, при этом максимально хорошо выглядит (при значении ниже начинают ползти артефакты). Обрезанная фотка: 23 кб. Можно было заморачиваться дальше, но овчинка выделки не стоит пока.
Единственное место в коде, где используется jQuery
https://github.com/yandex-cs/yac2013/blob/gh-pages/index.html#L65
При этом только для того, чтобы изменить разметку и подгрузить другое изображение в меню. Выкидываем скрипт, источник изображения меняем вручную.
Иконки меню. Много, тяжелые, уменьшенные стилями.
https://github.com/yandex-cs/yac2013/blob/gh-pages/index.html#L87
Имеют неоригинальные размеры, и подозрительный вес в 9кб каждая. Изменяем в тот размер, что используется, прогоняем через pngout, получаем 0.5кб каждая. Для того, чтобы не делать 6 лишних запросов, инлайню изображения через base64.
base64 увеличивает объем изображения примерно на 33%, но при этом браузеру не надо слать дополнительный запрос на сервер. К тому же, если страница загзиплена, то base64 не так сильно влияет на итоговый объем данных.
Подгрузка скрипта для вставки баннера
https://github.com/yandex-cs/yac2013/blob/gh-pages/index.html#L128
Выкидываем чепуху со скриптом, вставляем баннер прямо на страницу. Попутно заменяем png изображение на jpg, уменьшая размер файла вдвое.
В удаленном скрипте нас ждет второй ключ – голубой. Замка к нему не нашел:
if (0 == 1) { document.write('<scr' + 'ipt type="text/javascript" src="HTTP://about:blank.js?plugin="></scr' + 'ipt><nosc' + 'ript>Blue KEY!</no' + 'script>'); }
img не оригинального размера, и веса (1700кб)
https://github.com/yandex-cs/yac2013/blob/gh-pages/index.html#L153
https://github.com/yandex-cs/yac2013/blob/gh-pages/assets/img/photo_02.jpg
Изображение ресайзится буквально на пару пикселей относительно оригинального. Это дает дополнительную нагрузку на проц. Обрезаем изображение. Попутно обращаем внимение, что изображение весит подозрительно много. Прогоняем через минификатор джипегов, получаем итоговый размер в 11.2кб.
Найдя два ключа, предполагаю, что есть третий и он зелен. Туточки, среди мусора метаинформации, где и ожидалось.
Предполагаю, но не проверяю, что третий ключ находился в метаинформации этого изображения (всего было замечено два ключа). Спрашиваем.
Стили с описанием шрифтов грузятся динамически. и весят по 200кб.
https://github.com/yandex-cs/yac2013/blob/gh-pages/assets/css/bootstrap.css#L1
Тут интуресный момент. Если браузер видит, что для текста определен нестандартный шрифт, и есть font-face для этого шрифта, браузер скрывает текст до тех пор, пока шрифт не прогрузится. Даже при динамическом добавлении определений шрифтов после готовности документа сначала показывается текст, потом скрывается и браузер ждет догрузки шрифтов.
Я хотел добиться результата, когда страницу можно читать до того, как шрифты прогрузились (в идеале шрифты надо прогружать с таймаутом, чтобы отмести тех, у кого интернет медленен, и не подгружать файлы шрифтов при последующих загрузках страницы). Иду на хитрость: аяксом https://github.com/podgorniy/yac2013/blob/gh-pages/index.html#L185 шрифты, и только после того, как все прогрузились, объявляю @font-face. Протестировал с фиддлером на хроме-фаерфоксе-ие9 – все ок: страница читабельна, шрифты появляются позже. Единственно, что хром иногда пробовал запросить один шрифт еще раз.
Похоже, что такие оптимизации шутилка не меряет, потому как после этого, как мне казалось, шикарного решения, я поднялся в рейтинге совсем чуть.
140кб бутстрапных стилей мозолят глаза.
https://github.com/yandex-cs/yac2013/blob/gh-pages/assets/css/bootstrap.css
Анализ хромовскими инструментами показал, что 93% селекторов из этого файла не используются. Поиск инструментов, которые бы почистили файл от неиспользуемых селекторов не увенчался успехом. Все найденные инструменты только давали список неиспользуемых селекторов. Поэтому я написал штуку, получающую на вход список селекторов и css файл, а на выходе дает чистый файл. Конечный размер файла стилей 14кб. Удаление мертвых селекторов так же уменьшило время отрисовки страницы до 7мс.
Тени, уголки в хедере. Из за них страница отрисовывалась не за 2-3мс, а за 7.
Заменяю тени на изображение – профит. Время отрисовки уменьшилось до 2-3мс.
Надо понимать, что эта оптимизация никак не про ускорение начальной отрисовки страницы. Это оптимизация больше влияет на то, как ощущается пользователем скорость работы со страницей.
Всего выше не оказалось достаточным. Главный вес, и, соответственно проблема, заключалась в шрифтах. Топовые ребята инлайнят шрифты через data-uri, и используют за-gzip-ленные статические файлы. Так как я со шрифтами де-факто ничего не сделал, их вес сыграл свою роль.
День добрый.
Есть вопрос на счет cleaner.js, как с вами можно связаться?