Ну и нахуй всё это?
У меня на сайте sccl.cc есть целая история оптимизаций.
Сначала был переезд с Astro на Zine - об этом отдельный шитпост. Потом впихивание всего в 1 RTT - тут уже навалена глубокая статья.
если кратко: сайт ужат до 13kb gzip, грузится одним TCP-пакетом, один round trip на данные.
Вроде сайт летает. Но хочется добавить 88x31 баннеры.
Ну эти маленькие кнопочки, которые все пихают в футер сайта. Типа “Powered by something”, ссылки на друзей, клубы типа 512kb.club и 14kb.club.
Это ж классика веба. Как без них.
Но есть нюанс: сайт оптимизирован до одного запроса. А тут у тебя дцать баннеров, каждый по 200++ байт но каждый - отдельный HTTP-запрос.
Нужно чота придумывать.
88x31: откуда вообще
Формат 88 на 31 пиксель - это одна из старейших традиций веба.
Началось всё с Netscape. В 1996 году они запустили программу “Netscape Now!” - кнопки которые можно было вставить на сайт чтоб показать что ты юзаешь их браузер.
Потом идею подхватили. Microsoft со своим Internet Explorer. GeoCities для своих пользователей. Потом “Best viewed in…”, “Powered by…” - понеслась.
Эпоха browser wars сделала эти кнопки вездесущими. Это был способ показать принадлежность: я юзаю этот браузер, я хостюсь на этой платформе, я состою в этом веб-ринге.
Сейчас формат переживает второе рождение. Neocities, indie web revival, всякие клубы (512kb.club, 14kb.club, no-js.club) - у каждого свой баннер. И народ массово клеит их на сайты.
neonaut.neocities.org собрал архив из 13 051 баннера с 11 011 неоситизенов. 82 мегабайта чистого 88x31. Есть даже счётчик умерших сайтов ;k
к слову, это тот же neonaut который написал эссе про историю 88x31 - оттуда и факты выше. рекомендую.
Проблема: 67 запросов
Обычно баннеры вставляют <img src="..."> и похуй.
Выглядит это примерно так:

Каждый баннер - отдельный HTTP-запрос. Шрифт, стили, скрипт, 67 картинок.
И каждый запрос это полный цикл: DNS lookup (если холодный), TCP handshake, TLS handshake. Даже если картинка весит жалкие 200 байт - накладные расходы в десятки раз больше самой картинки.
Благо большинство не хотлинкит, а хостит баннеры у себя.
а то ваще пиздец бы был
Но даже с локальным хостингом 67 запросов - это дохуя. Если у тебя HTTP/2 и keep-alive, часть расходов амортизируется. Но первый setup всё равно есть. И каждый запрос это задержка.
А у меня сайт жёстко оптимизирован под 1 RTT. Я не могу просто взять и добавить 67 запросов.
философия не позволяет.
Тем более помимо скорости тут ещё и бонус в виде обхода расиянских ТСПУ - первый пакет не ловится, если влезает в initcwnd.
Чо делаем
Ясен хуй впихнуть все картинки в тот же 1 RTT не получится - слишком жирно.
Инлайнить в HTML тоже не вариант - каждый баннер в base64 добавит ~33% к весу, и мы(я) вылетим за initcwnd.
Но можно сделать по-другому: собрать все баннеры в один CSS-файл base64 и подгрузить его ОДНИМ запросом.
Второй запрос, да. Но зато баннеров может быть хоть 100 - это всё равно один запрос.
Идея: при билде сайта генератор читает конфиг, фетчит актуальные баннеры (или берёт локальные), конвертит в base64, пихает в CSS как background-image.
Как работает
CSS генерится при билде сайта. На входе конфиг:
{
"badges": [
{
"url": "https://example.com/assets/example.png",
"href": "https://example.com",
"alt": "example.com",
"file": "1.png"
},
{
"href": "https://test.com",
"alt": "test.com",
"local": "../img/test.com",
"file": "2.png"
}
]
}Два режима:
url- фетчит баннер с указанного сайтаlocal- берёт локальный файл (если баннер уже скачан и лежит в репе)
Генератор обходит конфиг, скачивает/читает файлы, конвертит в base64, генерит CSS:
.badge{display:inline-block;width:88px;height:31px;image-rendering:pixelated;image-rendering:crisp-edges}
.badge-0{background:url(data:image/gif;base64,R0lGODdhWAAfAHcAACH...) no-repeat 0 0}
.badge-1{background:url(data:image/png;base64,iVBORw0KGgoAAAA...) no-repeat 0 0}
/* и так далее */Дальше в HTML вместо 67 <img> тегов вставляем 67 <a class="badge badge-N"> ссылок с фоновой картинкой из CSS.
Применяется как стиль:

Профит
Вместо 67 отдельных запросов на каждый баннер получаем:
Основной сайт: 1 HTTP-запрос, 12kb gzip, 1 RTT. Ну это как и было - про это я уже писал.
Баннеры: 1 HTTP-запрос, один CSS-файл со всеми картинками в base64.

строки “data:” не являются http-запросами - это браузер декодит base64 в картинку локально.
Итого: ДВА запроса на весь сайт со всеми баннерами. Вместо 68+.
Если баннеров станет сильно больше 10 - CSS-файл может не влезть в один initcwnd и уйти на 2 RTT. Но разница между 1 и 2 RTT не драматична, когда это не основной контент. Баннеры в футере - пользователь их всё равно не видит сразу. к тому же у меня есть fallback с кнопочками без картинки
Из минусов: base64 добавляет ~33% к размеру бинарных данных. Для 88x31 баннеров (обычно < 1kb) это копейки. Но если у тебя сотня баннеров по 5kb каждый - уже начнёт быть заметно.
Ну и картинки не кешируются отдельно. При обновлении страницы - баннеры грузятся заново вместе с CSS. Но CSS тоже кешируется браузером, т.ч. на практике разница не чувствуется.
В моём случае профит очевиден: сайт остаётся быстрым, баннеры есть, всё выглядит как надо.