Организация CSS-кода

Как правильно сгенерировать css-файлы, чтобы они положительно влияли на загрузку и при этом с ними удобно было бы работать? Такой вопрос неизбежно задаёт себе каждый верстальщик. Когда мы делаем какой-то свой личный проект, проблема не особо актуальна, но если речь идёт о чем-то публичном, то она обостряется.

За время работы с UniCSS и Berry я постепенно смог сформулировать основные идеи, которые позволяют достаточно неплохо решить данную проблему. Особенно она будет интересна тем, кто использует сторонние css-фреймворки, вроде Bootstrap'а.

Загрузка с точки зрения браузера

Вначале нужно понять что именно мы хотим получить в итоге. Очень важный момент — это то, что в HTML 5 теперь совершенно легально можно загружать css-файлы в конце BODY — это так называемая отложенная или lazy-загрузка.

Таким образом весь css-код делится на две части: обязательная, та которая загружается в HEAD и та, которая может быть подгружена в конце BODY.

Браузер загружает файлы сразу после того, как получит html-код страницы (и это определяется его внутренними алгоритмами). Если какой-то файл уже загружен, то он может взяться из кэша, а значит порядок реальной загрузки не всегда соответствует тому, как он указан в html-коде. Но браузер применяет css-стили только в указанном порядке. То есть когда мы говорим о том, что размер файлов критичен, поскольку влияет на скорость загрузки, то это не всегда соответствует действительности. Может получиться даже так, что css-файл из BODY будет загружен раньше, чем в секции HEAD.

Поэтому размер css-файлов хоть и влияет на общую загрузку страницы, но не так сильно, как это может показаться изначально. Если на сервере включено gzip-сжатие трафика, то в реальности передаётся примерно в 4-5 раз меньше для текстовых файлов. Например, если размер css-файла 50Кб, то реальный трафик будет где-то 10Кб и это очень немного даже для мобильных устройств.

Так что если мы видим на сайте «тормоза», то скорее всего дело здесь не в CSS, а скорее в JS, который блокирует отрисовку и выполнение кода до полной загрузки всех js-файлов.

И именно по этой причине необходимо выделять часть стилей в секцию HEAD, чтобы на момент начальной загрузки посетитель видел страницу в более-менее нормальном варианте.

CSS-каталоги

Всё это приводит нас к простой структуре каталога «css»:

  • lazy — каталог, где файлы будут подключаться в конце BODY
  • style.css — файл, который будет в HEAD
  • прочие каталоги, из которых подключение ситуативное, например в компонентах или блоках.

Файл для секции HEAD по возможности должен быть один, поскольку это сократит количество http-соединений. Тем более, что файл будет небольшой и нет смысла его разделять.

Lazy-файлы лучше всего подключать в определенной последовательности, поскольку это гарантирует нормальную «перезаписываемость» стилей. Покажу пример:

lazy/
    1-bootstrap.css
    2-template.css
    3-colors.css
    4-fontawesome.css
    animate.css
    cssgram.css

Поскольку все файлы компилируются Sass, то у нас нет проблем скомбинировать их так, как нужно.

Sass-каталоги

На всякий случай отмечу, что Sass не компилирует файлы, начинающиеся с символа подчеркивания «_». Это позволяет организовывать «служебные» каталоги, которые не появятся в css-каталоге.

При организации файлов следует учитывать, что мы можем использовать сторонние разработки. Например я использую тот же Bootstrap, свой Berry CSS, animate.css и CSSgram. При этом есть ещё каталог для css-профилей MaxSite CMS и файлы для разных менюшек.

Лучшим вариантом будет размещать все файлы по своим каталогам. Приведу пример:

sass/
    berry/ - Berry CSS
    bootstrap/ - Bootstrap
    fontawesome/ - иконки FA
    fonts/ - подключение шрифтов
    lazy/ - то что будет в css/lazy
    menu/ - менюшки
    profiles/ - css-профили MaxSite CMS
    template/ - стили шаблона
    typography/ - типографика
    
    style.scss - файл для HEAD css/style.css

Каждый (свой) каталог содержит только scss-файлы без деления на подкаталоги.

Особенности использования сторонних библиотек

Некоторые библиотеки поставляются с готовым css-файлом, поэтому его можно сразу разместить в lazy-каталоге. Но, если это css-фреймворк, то возникает серьёзная проблема нарушения последовательности подключения.

Например мы используем Bootstrap. Он содержит всю типографику, сбросы и т.д. Логично его подключать первым. Но часть стилей мы должны переписать, например отступы заголовков (в Bootstrap'е верхний margin фиксированный). Конечно, мы можем поправить его исходный scss-файл, но это приводит к тому, что при обновлении потребуется ещё раз внести правку. Поэтому лучшим вариантом будет перезагрузка стилей своими.

У нас вырисовывается последовательность: вначале Bootstrap, после свои стили. Поскольку Bootstrap достаточно большой файл, то наверное его есть смысл разместить как lazy, а это в свою очередь заставляет и все остальные css-файлы загружать уже после него.

Совершенно очевидно, что такое решение приводит к проблеме начального отображения страницы — она будет жутко «куралеситься», пока не загрузятся все css-файлы.

Получается, что единственным вариантом будет размещение базовой части Bootstrap'а в HEAD, а все его второстепенные части уже в конце BODY. К счастью фреймворк написан на Sass, а значит есть возможность его скомпилировать частями.

Полезность утилитарных классов

Я использую Berry CSS, но это совершенно не принципиально. Обычно такие классы (они ещё называются Atomic CSS) загружают в конце остальных. Но на самом деле они должны загружаться первыми, поскольку используются при верстке большинства блоков. Простой пример.

Пусть есть утилитарные классы .mar20 {margin: 20px}и .w50 {width: 50%}, которые используются в шапке, подвале и какие-то блоки на главной. Если мы загрузим эти классы в самом конце, то все эти блоки будут изначально отображены некорректно. Но, если это будет начальная загрузка, то посетитель сразу увидит корректный вывод.

Утилитарные классы достаточно небольшие по объему, а значит их можно смело разместить в секции HEAD.

Секция HEAD

Что у нас получается в итоге. Первыми используем утилитарные классы. Поскольку они не конфликтуют по именам, то проблем с ними не будет. Дальше в ход идёт css-фреймворк, но только основная часть: сбросы и типографика. И в конце мы подключаем свой файл, который изменяет (если нужно) стили фреймворка.

Всё это размещается в style.scss. Приведу свой вариант одного из шаблонов с использованием Berry CSS и Bootstrap.

// загружаем шрифты
@import "typography/fonts";
 
// Berry CSS без colors
@import 'berry/variables/variables';
@import 'berry/block';
@import 'berry/border';
@import 'berry/flex';
@import 'berry/height';
@import 'berry/layout';
@import 'berry/opacity';
@import 'berry/padding-margin';
@import 'berry/position';
@import 'berry/rounded';
@import 'berry/text';
@import 'berry/visible';
@import 'berry/width';
@import 'berry/links';
@import 'berry/other';
 
// часть Bootstrap
@import "bootstrap/functions";
@import "bootstrap/my_variables"; // файл своих переменных
@import "bootstrap/variables";
@import "bootstrap/mixins";
@import "bootstrap/root";
@import "bootstrap/reboot";
@import "bootstrap/type"; 
 
// то, что хотим переписать от bootstrap/reboot и bootstrap/type
@import 'typography/base';

В итоге получается 45Кб css-файл, что даёт всего 8Кб реального трафика для секции HEAD.

О шрифтах

Сейчас подключать шрифты нужно в секции HEAD. Это большая головная боль, которая благополучно разрешилась за счёт поддержки font-display: swap;. Шрифт — это критически важный элемент дизайна, поэтому будет лучше, если браузер узнает о нём на самых ранних этапах загрузки страницы. Если размер шрифта небольшой (особенно если это woff2), то браузер успевает загрузить его уже к началу просмотра, что позволяет избежать неприятного мелькания.

Lazy Bootstrap

Bootstrap должен загрузиться первым, чтобы можно было бы переписать его стили после. Поэтому используется файл lazy/1-bootstrap.scss:

@import "bootstrap/functions";
@import "bootstrap/my_variables"; // my variables
@import "bootstrap/variables";
@import "bootstrap/mixins";
 
// а это уже загружено раньше
// @import "bootstrap/root";
// @import "bootstrap/reboot";
// @import "bootstrap/type";
 
@import "bootstrap/images";
@import "bootstrap/code";
@import "bootstrap/grid";
... и т.д.

Lazy Template

Дальше мы загружаем lazy/2-template.scss. Именно этот файл и отвечает за нашу вёрстку. Все файлы нашего шаблона делятся на два каталога: template и typography.

В каталоге typography размещаются те стили, которые не имеют привязки к специфичным css-классам. Это верстка блоков (PRE, CODE и т.д.), кнопки BUTTON, элементы форм (INPUT, TEXTAREA, SELECT и т.д.), иконки, изображения, списки, линии, эффекты и т.д. Формально все эти стили можно скомпилировать совершенно отдельно без каких-либо миксинов и прочих зависимостей. Единственная связь может быть с перемененными цветов (от Berry CSS).

И опять же, поскольку у нас уже загружен Bootstrap, то есть смысл в своей типографике переопределять только то, что реально нужно.

Каталог template содержит всё то, что имеет непосредственное отношение к шаблону. Здесь уже идёт привязка к классам MaxSite CMS, виджетам, блокам, плагинам и т.д. Вот примерно так выглядит lazy/2-template.scss:

// переменные Berry CSS могут использоваться в остальных файлах
@import 'berry/variables/variables';  // общие переменные
@import 'berry/variables/all-colors'; // все цвета
 
// аналогично могут использоваться переменные FA
@import 'fontawesome/variables';
@import 'fontawesome/mixin';
 
// @import 'typography/base'; - этот файл уже загружен в HEAD
 
// типографика
@import 'typography/blocks';
@import 'typography/button';
@import 'typography/forms';
@import 'typography/gradients';
@import 'typography/hover-img';
@import 'typography/icons';
... и т.д.
 
// стили шаблона
@import 'template/comments';
@import 'template/components';
@import 'template/forms';
@import 'template/lightslider';
@import 'template/menu-simple';
@import 'template/message';
@import 'template/page-other-pages';
@import 'template/plugins';
... и т.д.

Lazy Colors

Файл lazy/3-colors.scss содержит цвета Berry CSS. Это значит, что пользователь MaxSite CMS может использовать в текстах эти утилитарные классы. Так же эти классы используются при разработке шаблона, например в компонентах шапки/подвала или into-top-файлах. Сейчас в Berry CSS очень много цветов, причём с мелкой градацией (шаг 50 в material design от google). За это приходится платить увеличенным размером css-файла (58Кб). Этот фактор, а также то, что цвет всё-таки вторичен при отрисовки страницы, он загружается как lazy.

При необходимости можно отредактировать файл berry/variables/_colors.scssи указать только избранные цвета. Или использовать _colors-lite.scss, где используется более крупная градация «100». Это уменьшит размер файла примерно в 2 раза.

В любом случае утилитарные классы цветов компилируются как угодно. Стоит отметить еще один момент. Файл _all-colors.scssсодержит sass-переменные всех цветов. И эти переменные могут использоваться в других файлах вместо явного указания цвета.

Остальные файлы

Они подключаются уже произвольно, хотя чуть больший приоритет отдан иконкам FontAwesome.

Итоги

Данный подход позволяет решить сразу несколько проблем с организацией css-кода. Важно то, что он использует современные возможности браузеров и учитывает требования поисковиков по быстрому отображению сайта, особенно на мобильных устройствах. Особый выигрыш получается от использования утилитарных классов: больше нет необходимости ждать их позднюю загрузку и блоки на странице сразу отображаются корректно.

Оставьте комментарий!

Комментарий будет опубликован после проверки. Вы соглашаетесь с правилами сайта.

(обязательно)