В постоянно меняющемся мире фронтенд разработки очень сложно найти действительно оригинальную концепцию и правильно ее представить, вызвав у других людей желание попробовать ее.

Если взглянуть на CSS, то, на мой взгляд, последней значительной инновацией в нашем подходе к написанию стилей стало появление CSS-препроцессоров, в первую очередь это касается Sass, как наиболее известного из них. Также существует PostCSS, в котором реализован несколько иной подход, но общий принцип действия тот же — в постпроцессор передается неподдерживаемый браузерами синтаксис, а возвращается поддерживаемый синтаксис.

Сейчас у нас появилась очередная интересная новинка — CSS-модули. В этой статье я представлю эту новую технику, расскажу о ее сильных сторонах и о том, как начать с ней работать.

Что такое CSS-модуль ?

Начнем с определения из официального репозитория проекта:

CSS-модуль это файл CSS, в котором названия классов и анимаций по умолчанию заданы локально.

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

Но в конце концов, мы можем ценить CSS-модули за то, что они реализуют, а это способ ограничивать контекст CSS одним компонентом и возможность избежать ада глобального пространства имен. Больше нет необходимости искать методику именования компонентов, ведь этот шаг делается за вас на стадии сборки!

Как это работает

CSS-модули надо подключать в сборочный процесс, это значит, что они не работают сами по себе. Плагины есть для webpack или Browserify. На базовом уровне это работает так: при импорте файла в модуль JavaScript (например, в компонент React), CSS-модули переопределят объект с названиями классов из файла в динамически задаваемые пространства имен классов, которые можно использовать как строки в JavaScript. Проиллюстрируем это на примере:

Ниже показан очень простой файл CSS. Класс .base не является уникальным в проекте и это не то название класса, которое будет присвоено элементу. Это своего рода псевдоним внутри таблицы стилей, который будет использован в модуле JavaScript.

.base {
  color: deeppink;
  max-width: 42em;
  margin: 0 auto;
}

И вот его использование в тестовом компоненте JavaScript:

import styles from './styles.css';

element.innerHTML = `<div class="${styles.base}">
  CSS Modules are fun.
</div>`;

В итоге у нас будет сгенирировано что-то вроде следующего кода (это пример использования CSS-модулей в Webpack с дефолтными настройками):

<div class="_20WEds96_Ee1ra54-24ePy">CSS Modules are fun.</div>
._20WEds96_Ee1ra54-24ePy {
  color: deeppink;
  max-width: 42em;
  margin: 0 auto;
}

Можно настроить генерацию более коротких названий классов или названий, следующих определенному паттерну. Это совершенно не важно (разве что более короткие названия классов дадут небольшой выигрыш в размере таблиц стилей), ведь главное, что они динамически генерируются, уникальны и связаны с правильными стилями.

Возможные возражения

Итак, мы разобрались, как это работает. И теперь вы думаете: “Что же с этим делать? Ведь это…”. ОК, я вас понимаю. Сейчас мы разберем все возражения по отдельности.

Это выглядит ужасно!

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

Их трудно отлаживать!

Как только в вашей сборке производится какая-либо обработка таблиц стилей, их отладка становится болью. С Sass в этом плане не легче. Поэтому у нас есть sourcemaps, которые также можно настроить и для CSS-модулей.

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

Это препятствует многократному использованию стилей

Да и нет. С одной стороны — да, но это на самом деле и есть цель: мы привязываем стили к компоненту, чтобы избежать конфликтов глобальных стилей. И согласитесь, что это хорошо.

С другой стороны, мы можем создавать глобальные классы (с помощью :global()), например, вспомогательные классы, которые сохраняются в том же виде после сборки и позволяют абстрагировать стили также просто как и в обычных условиях. Эти классы можно использовать в ваших компонентах JavaScript.

:global(.clearfix::after) {
  content: '';
  clear: both;
  display: table;
}

В CSS-модулях также есть способ расширения стилей из другого модуля, работающий аналогично директиве @extend в Sass. Он не копирует стили, а складывает селекторы для расширения стилей.

.base {
  composes: appearance from '../AnoherModule/styles.css';
}

Они требуют webpack, Browserify или другие инструменты?

Точно также как требуется Sass для компиляции .scss в обычный CSS, PostCSS необходим для обработки стилей, чтобы сделать их понятными браузеру. От этапа сборки никуда не деться.

Почему мы вообще это обсуждаем?

Хм, я не совсем уверен, что в будущем CSS-модули останутся такими же как сейчас, но я думаю, что это разумный способ написания стилей. Глобальная массивная таблица стилей не слишком подходит к большим сайтам, разбитым на мелкие компоненты.

Уникальность пространства имен CSS делает его одновременно мощным и уязвимым. Такие решения как модули CSS или любой будущий инструмент, основанный на этой идее, позволяют нам сохранять силу глобальных стилей и в то же время избегать конфликтов внутри изолированных областей стилей. Это вин!

Начало работы с CSS-модулями

Как уже было сказано, нам нужен webpack или Browserify для работы CSS-модулей.

Webpack

Начнем с версии для webpack. В файл webpack.config.js добавьте следующую конфигурацию, чтобы webpack обрабатывал файлы CSS с помощью CSS-модулей:

{
  test: /\.css$/,
  loader: 'style-loader!css-loader?modules'
}

С такой настройкой полученные стили будут размещаться внутри элемента <style> на странице. Это не самое лучшее решение, поэтому мы сконфигурируем вывод итоговых стилей в отдельный файл благодаря плагину извлечения текста для webpack:

{
  test: /\.css$/,
  loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules')
}

Этого достаточно для работы с webpack.

Browserify

Я всегда использовал Browserify только через командную строку, так что это оказалось немного сложнее. Я добавил скрипт npm в файл package.json:

{
  "scripts": {
    "build": "browserify -p [ css-modulesify -o dist/main.css ] -o dist/index.js src/index.js"
  }
}

Эта строчка сообщает Browserify, что надо трансформировать src/index.js, в dist/index.js и скомпилировать файл dist/main.css с помощью плагина css-modulesify. Если вы хотите добавить автопрефиксер, то вы можете завершить команду так:

{
  "scripts": {
    "build": "browserify -p [ css-modulesify --after autoprefixer -o dist/main.css ] -o dist/index.js src/index.js"
  }
}

Как видите, вы можете использовать опцию --after для обработки стилей после их компиляции.

Заключение

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

Я думаю, что идея в основе CSS-модулей это направление, по которому надо двигаться. Я не скажу, что эта библиотека является лучшим из возможных решений, но в ней однозначно есть возможности реализующие то, какими должны быть стили: модульные, изолированные и при этом многократно используемые.

В качестве дальнейшего чтения по теме я рекомендую введение в CSS-модули от Глена Мэддерна, автора этого проекта.