Долгие годы шли споры, как лучше именовать классы - по бэму, по целям, по компонентам или как угодно, но с добавлением хеша. И это действительно важный вопрос, какой способ будет комфортен в разработке большого и развивающегося проекта. Но, что эти способы означают для пользователя, нужны ли ему эти классы и как они связанны с его опытом?
Порою, заходя в стили проектов, я невольно пугаюсь сложившейся длине имён - модуль, блок, элемент, подэлемент, модификатор 1, модификатор 2. БЭМ действительно великолепен и я не собираюсь его отрицать, но его размеры оставляют желать лучшего.
Длинные классы увеличивают вес страницы, это в свою очередь означает увеличение времени загрузки самого главного для рендера страницы - документа и файла стилей, от которых напрямую зависят метрики FCP, LCP.
Это стало одной из причин почему я долгое время засматриваюсь на модули (в дополнение к изоляции стилей и их хранении там, где они используется).
Модули дают возможность называть классы короче, только под текущий компонент, сохраняя при этом удобство разработки. Но теперь к классам добавляются хеши, делая их длиннее, а значит преимущество не такое, как хотелось бы. Поэтому, наконец, к теме статьи.
Сжатие имён классов
Итак, какие есть методы сократить классы:
Называть классы короче (Спасибо, Кэп, да);
Называть классы полноценно, но при сборке оставлять только хеш;
Называть имена по определённому правилу или алгоритму.
Первый способ не подходит для чего-то большего, чем to-do лист - сделав классы слишком короткими мы либо теряем DX, либо рискуем сделать пересечения.
Для второго и третьего же способа, css-loader предлагает свойство localIdentName
для модулей.
localIdentName: "[path][name]__[local]--[hash:base64:5]"
localIdentName: "[hash:base64]"
Самое оптимальное сжатие
Подобрав правило можно значительно сократить размер классов, но всё же, значительно не равно максимально. Максимальным уменьшением имён классов будет сохращение до символов - .a
, .b
, .c
, …, etc.
Таким подходом пользуются, например, Google, Facebook, Instagram
Чтобы реализовать такое решение нас интересует свойство getLocalIdent
, которое позволяет передавать функцию для генерирования имени. Также можно использовать такие пакеты как posthtml-minify-classnames или mangle-css-class-webpack-plugin.
На этом можно было бы завершать статью, если бы не одна деталь. Я использую next.js.
Решение
Next.js имеет несколько особенностей, которые не позволяют использовать эти решения. Самой очевидной особенностью является то, что он не даёт возможности настроить getLocalIdent снаружи.
Именно поэтому я, 3 года назад сделал пакет - next-classnames-minifier
. В нём я реализовал алгоритм подбора имён и настроил встраивание getLocalIdent
в нужные правила в вебпаке. За последующие годы пакет незначительно обновлялся, но было в нём то, что не позволяло мне назвать его завершённым и готовым для использования в коммерческих проектах.
Главной проблемой была необходимость каждый раз удалять папку собранного приложения и кеша в ci, что, безусловно, очень сказывалось на удобстве разработки. И в этом вторая особенность Некста - его система кеширования.
Если компонент собрался, то при следующем запуске режима разработки или сборки - он может не пересобираться. То есть при перезапуске, алгоритм начинал работать с чистого листа и генерировал классы с самых простых имён (.a
, .b
, .c
), но в ряде непересобираемых компонент и стилей такие имена были добавлены при прошлом запуске.
По этой причине решение и не включено в next.js
Подружиться с Next.js
Очевидно, критично важной задачей было избавить от проблемы очистки кеша. И решение нашлось. Теперь пакет, подобно next.js, кеширует результаты - сгенерированные имена - и при каждом запуске восстанавливает их из кеша, анализируя их и проверяя их актуальность.
При этом, скорость сборки не стала дольше, ведь пакет использует всё тотже оптимизированный алгоритм подбора имени, а за счёт кеширования пакет работает ещё быстрее [чем базовое создание имён с генерированием хеша].
Эффективность
Можно найти статьи с эффективностью сжатия 30%, 50% и даже 70%. В реальности же всё очень индивидуально. Например, если у вас был класс:
.postsListItemTitle {
font-size: 24px;
}
Из него получится:
.j1 {
font-size: 24px;
}
21 символ (.j1{font-size: 24px;}
) вместо 44 (.postsListItemTitle__h53_j{font-size: 24px;}
) - экономия 52%. Этот класс используется в 20 карточках на странице, что уменьшает вес и html.
В среднем же, пожалуй, можно говорить о уменьшении веса css на 10-20%.
next-classnames-minifier - давайте сделаем веб Ещё быстрее.
Комментарии (23)
DarthVictor
16.01.2024 10:54В среднем же, пожалуй, можно говорить о уменьшении веса css на 10-20%.
До сжатия gzip/brotli или после?
Vordgi Автор
16.01.2024 10:54И до, и после (но в меньшем соотношении, да). Видел примеры, где эффект в сжатом виде стремительно приближался к нулю.
В самом худшем случае это небольшая разгрузка сервера и клиента, так как алгоритмам нужно проходить меньше символов. В лучшем - оптимизация. Вероятно будут страницы, где эффект хороший, а будут, где достаточно незаметный.
Если есть безобидный способ, пусть даже незначительно, сократить вес страницы, то почему бы и да.
dom1n1k
16.01.2024 10:54А что значит "именование классов по целям"? Гугл первой ссылкой выдает вот эту самую статью.
Vordgi Автор
16.01.2024 10:54Да, плохая формулировка, спасибо!
Имелось ввиду форматы, когда классы добавляются не по полноценному БЭМ-у (в котором есть сам класс и его модификаторы), а просто общие классы, то есть когда может быть:<div class="card section box pt-20 mb-8" />
Это bootstrap и tailwind, например. Но если bootstrap устроен всё-же по БЭМ-у, то tailwind просто огромный набор атомарок, который у меня язык не поворачивается назвать БЭМ-ом (и выглядит это порою очень страшно).
Кто-нибудь, объясните мне, как это вообще выжило...finkrer
16.01.2024 10:54Выжило исключительно потому, что так удобнее)
Vordgi Автор
16.01.2024 10:54+1Удобство ломается с диким грохотов об блоки чуть сложнее карточки с закруглениями.
Когда для инпута нужно писать 10 классов на стили, по 5 классов на каждое состояние (hover, visites, disabled, ..., error), 15 классов на адаптив и ещё на всякий случай снаружи принимаешь классы, а то мало ли чего, ещё 20 классов нужно будет передать...
finkrer
16.01.2024 10:54Эта гипотетическая ситуация меня бы запугала, если бы я ее не встречал в работе) Хотите верьте, хотите нет, но на практике это нормально воспринимается. Выглядит — да, страшно.
If you can suppress the urge to retch long enough to give it a chance, I really think you'll wonder how you ever worked with CSS any other way.
Это автор фреймворка. И я с ним согласен. Почему это удобнее — может как-нибудь статью напишу про это, там много причин, но проще всего попробовать сделать проект на теилвинде и прочувствовать.
Vordgi Автор
16.01.2024 10:54Я делал относительно нормальный пет проект на нём. И первое время как-то нравилось - буквально plug-n-play - бери и делай, но вот с инпутом, кнопками и прочим с большим количеством условий (когда для элемента в css было бы 40 строк, а здесь это 40 классов) - как-то прям оттолкнуло.
DmitryKazakov8
16.01.2024 10:54+3На мой взгляд, сокращение имен классов - это экономия на спичках в ущерб DX.
При сжатии gzip/brotli экономия на большой html будет в байтах, максимум - в паре килобайт. Это не повлияет на оценку Lighthouse и на пользовательский опыт, если вы не гонитесь за тысячными долями процента перфоманса
-
При чтении дерева элементов в инспекторе видим несемантичные
j1 g83
классы, соответственно как найти место в коде, в котором нужно поправить стиль? Можно переключиться на React Dev Tools, выбрать элемент, потом обратно в инспектор, проверить там новые стили, и дальше - копать код проекта, перебирая папки и файлы в рандомном порядке. Проблема усугубляется тем больше, чем больше разработчиков в компании, и чем хуже знаешь кодовую базу проекта. Частично проблема решится CSS maps, но тоже много лишних телодвижений.При этом семантичные CSS Modules сразу покажут где находится файл -
src-components-layouts-userMenu src-pages-user
. При регистрации ошибки в Sentry например, разработчик смотрит путь, который пользователь накликал (в Сентри это логируется). И в случае сокращенных имен он увидит
click[.j1]
- как с этим работать? Скорость дебага значительно увеличится.Для автотестов стабильные имена
[path][name]__[local]--[hash:base64:5]
можно использовать как уникальные идентификаторы элементов в ряде случаев и обойтись без data-test-id или id (если не учитывать в селекторе hash). Также можно дать не сильно разбирающемуся в автотестах сотруднику визуальный инструмент накликивания пользовательских сценариев. Тогда он передаст разработчику семантичный список действийclick[.src-components-layouts-userMenu]
и разработчик при необходимости очень быстро добавит id либо оставит селекторы в оригинальном виде, если эти классы достаточно специфичны.
Итого - сокращением названий классов мы улучшили пользовательский опыт на неизмеримо малую долю, при этом кардинально усложнили работу верстальщикам, разработчикам, тестировщикам и в целом тратим больше денег компании каждый день.
Vordgi Автор
16.01.2024 10:54+1При сжатии gzip/brotli экономия на большой html будет в байтах, максимум - в паре килобайт. Это не повлияет на оценку Lighthouse и на пользовательский опыт, если вы не гонитесь за тысячными долями процента перфоманса
Да, при весе в 100кб сжатыми уменьшит на 5кб (если стили вшиты, например, а может и до 10кб, а может и 1 с трудом накапает, зависит). Это не меняет кардинально метрики, но, например, освобождается 5мб под прочее, может спрайт добавить внутрь? Да и 5кб + доли секунды в упрощении компрессии-декомпрессии сыграют в 1-2 балла (мы всё-таки ориентируемся на мобильные устройства).
При чтении дерева элементов в инспекторе видим несемантичные
j1 g83
классы, соответственно как найти место в коде, в котором нужно поправить стиль? "А зачем их читать? В режиме разработки можно настраивать генерирование классов по другому (пакет это поддерживает из коробки). В продакшене ты залезаешь обычно просто конкретный элемент подправить, скорее ради интереса.
Частично проблема решится CSS maps, но тоже много лишних телодвижений.
С картами и сентри очень хороший пункт, Спасибо! Вдруг тут кто-то обходил подобное и может поделиться решением?
А для автотестов классы больно и ненадёжно, атрибут всё же несравним. Мне больше всего нравится подход, когда этот атрибут добавляется во время сборки только в одном окружении, не в проде. Но такое не всем годится. Для накликивания можно было бы тоже сжимать классы только в проде, а все эти махинации проводить на тестовых стендах...
Стоит ли оно того, если есть другие минусы? Пожалуй нет;
Стоит ли это попробовать? Почему бы и да. 1 день на аб тесте или ручное сравнение на тестовом стенде даст ответы, есть ли достаточный эффект.P.S. Как не плюсануть коммент выше за хороший развёрнутый коммент?
Ilusha
16.01.2024 10:54Сентри умеет в is-сорсмапы, но не знаю, умеет ли в css-сорсмапы. Но в реплеях должно.
gmtd
16.01.2024 10:54100kb стилей? Вы серьезно?
Vordgi Автор
16.01.2024 10:54Бывает и такое, конечно, но имелось ввиду html документ со вшитыми стилями (т.е. когда стили не в отдельных файлах, а в теге <style />, в самой странице)
(контекст был про "экономия на большой html")gmtd
16.01.2024 10:54Если вы делаете сайты куда пользователь заходит не больше одного раза, то тогда в ssr есть смысл. Если же вы делаете нормальные сайты, куда пользователь заходит более чем один раз, то pwa с кэшированием сервис worker-om решает все проблемы с большими CSS и не только CSS не уродуя DX.
Vordgi Автор
16.01.2024 10:54Про SSR речи и не идёт - да и там другие проблемы бы были важнее, вроде долгого ожидания начала загрузки документа, что при хорошем интернете обычно даже дольше, чем сама загрузка.
А сайты продумываются для всех - многие заходят на страницу лишь единожды. А первый вход важен для маркетинговых задач. Если пользователь первый раз грузил страницу 5 секунд, устал и ушёл, то уже неважно, что данные у него закешировались и дальше всё будет грузиться быстро.
(Когда речь идёт об оптимизациях таких мелочей, обычно это не про веб-приложения, а про маркетинговые сайты)Vordgi Автор
16.01.2024 10:54Facebook, вероятно, это сделал, чтоб трафик сэкономить, т.к. экономия 2кб со страницы на миллионы (или миллиарды?) пользователей и переходов - весомая экономия на серверах.
Вряд ли там вообще упоминались улучшение метрик и SEO-оптимизация.
gmtd
16.01.2024 10:54У вас next.js и не ssr? А что у вас тогда?
И зачем врать про SPA в 5 секунд супротив вашего на полсекунды?
Vordgi Автор
16.01.2024 10:54Next.js уже лет 5 как не только про SSR, сейчас в первую очередь это про SSG с инкрементальной пересборкой.
А про 5 секунд действительно приукрасил, да. Знаете за сколько загружается эта страница на медленном 3g (что всё ещё является обыденным в мире и Европе)? 20 секунд, из которых 15 до загрузки LCP.
Быстрый 3g грузит страницу 5-10 секунд.
Остальной мир это не про гигабит в секунду.
Dartess
16.01.2024 10:54Как раз сейчас занимаюсь оптимизацией проекта, идеи что можно ужать подиссякли, попробую этот пакет.
fire_engel
Длинные классы увеличивают вес страницы, это в свою очередь означает увеличение времени загрузки самого главного для рендера страницы - документа и файла стилей
ну да, виноваты классы, а не фреймворки, генерирующие титаническое количество js и виртуальное дом-дерево. просто не используйте препроцессоры и всё, можно и без них добиться того же функционала.
Vordgi Автор
Провожу много времени в попытках оптимизации сайтов. Пробовал svelte, шаблонизаторы, статику, и... они не дали явных преимуществ (особенно по сравнению с next.js v14 и серверными компонентами).
Всё это - виртуальный дом, множество логики, доп. библиотеки, в контексте next.js не влияют на FCP и LCP, так как Next в первую очередь выдаёт статику, и уже потом на клиенте, грубо говоря, виртуальный дом свяжется с настоящим дом-ом, а это произойдёт уже после подсчёта FCP и LCP или параллельно.
Увы, это может (может != обязательно будет) влиять на TBT - total blocking time, которая сейчас становится в общем-то основной метрикой для гугла.
Собственно, раз он сразу выдаёт документ со статикой - то именно от размера самого этого документа и будет зависеть FCP, а не от дополнительных скриптов. Вдвойне это чувствуется, когда классы вшивают в документ.
P.S. конечно мы не говорим об ошибках проектирования, например, когда из-за разницы дом-ов нужно перестраивать реальный или LCP картинка грузится не в приоритете или из клиентской логики.
vmkazakoff
Если вообще не брать фреймворк, а писать просто классы к элементам, то они, о чудо, отлично применятся браузером. Проблема когда это, CSS in JS и оно тянет за собой ещё десяток node module и несколько мегабайт трэша в браузер. Но писать простой CSS уже разучились (
Веб переусложнили. И если для каких-то сложных приложений с огромным функционалом это ещё полезно (отдельный вопрос полезно ли их делать в браузере), то когда фреймворки и сотню модулей пихают в любой сайт - это жесть.
Vordgi Автор
CSS in JS ужасен, да, поэтому речь только о CSS-модулях. Сам Next.js тоже перестал рекомендовать css-in-js решения и сейчас везде говорят только про css/scss/sass и css-модули (ну ещё бывает tailwind, но с кем не бывает).
А сайты бывают и сложнее приложений, современным компаниям для продвижения и анализа нужно куда больше, чем просто текст и красивая анимация.