Что такое CSS-модули и зачем они нам?
В последнее время меня интригуют CSS-модули. Если вы о них не слышали — эта статья для вас. Мы рассмотрим, что это за проект, какие задачи он решает и каковы причины его возникновения. Если вы тоже заинтриговались — не переключайтесь, следующая статья будет о том, как начать их применять. А если вас интересует внедрение в проект или более продвинутое использование CSS-модулей, третья статья в этой серии будет о том, как использовать их c React.
Серия статей
Часть 1: Что такое CSS-модули и зачем они нам? (Вы её читаете!)
Что такое CSS-модули?
Согласно определению из репозитория, CSS-модули — это:
CSS-файлы, в которых все классы и анимации по умолчанию находятся в локальной области видимости.
CSS-модули — это не официальная спецификация, они не имплементированы в браузеры, скорее, это задача, запускаемая на стадии сборки проекта (например, с помощью Webpack или Browserify), в процессе выполнения которой имена классов и селекторы изменяются так, чтобы образовалась своего рода локальная область видимости (что-то вроде пространства имен).
Как это выглядит и зачем нам это? Сейчас расскажу. Во-первых, вспомните как обычно работают HTML и CSS. Класс прописывается в HTML:
h1 class="title">Пример заголовка h1>
И стилизуется в CSS:
.title < background-color: red; >
Пока CSS применяется к HTML-документу, фон будет красным. Нам не нужно как-то обрабатывать CSS или HTML, оба формата понятны браузеру.
CSS-модули используют другой подход. Вместо того, чтобы писать обычный HTML, нам придётся написать разметку в JavaScript-файле, например, в index.js . Вот как это может выглядеть (ниже мы рассмотрим более реальные примеры):
import styles from "./styles.css"; element.innerHTML = `$ "> Пример заголовка `
;
В процессе сборки компилятор проанализирует styles.css , который мы импортировали, потом проанализирует JavaScript и сделает класс .title доступным через styles.title . Затем сгенерирует на их основе новые HTML и CSS-файлы, уже с новыми классами.
Сгенерированный HTML может выглядеть следующим образом:
h1 class="_styles__title_309571057"> Пример заголовка h1>
А вот так может выглядеть CSS:
._styles__title_309571057 < background-color: red; >

Значение атрибута class и селектор .title заменены на новые. Исходный CSS в браузер вообще не попадает.
Как сказал Хьюго Жироде́ль (Hugo Giraduel) в своей статье по этому поводу:
[классы] генерируются автоматически, они уникальны и привязаны к исходным стилям.
Вот это и называется поместить стили в локальную область видимости. Они находятся в области видимости определенного шаблона. Если у нас есть файл buttons.css , он будет импортирован только в шаблон buttons.js , и класс .btn , который он содержит, будет недоступен для других шаблонов (например, forms.js ), пока мы не импортируем его и туда тоже.
Зачем нам устраивать всю эту канитель с CSS и HTML? Зачем нам это вообще сдалось, ради всего святого?!
Зачем нам нужно использовать CSS-модули?
CSS-модули гарантируют, что все стили одного компонента:
- Находятся в одном месте
- Применяются только к этому компоненту и никакому другому
Кроме того, каждый компонент может иметь настоящие зависимости, например:
import buttons from "./buttons.css"; import padding from "./padding.css"; element.innerHTML = `$ $ ">`;
Этот подход был разработан, что бы решить проблему глобальной области видимости в CSS
Вы когда-нибудь испытывали соблазн в условиях нехватки времени или ресурсов просто писать CSS так быстро, как можете, не думая о последствиях?
Пихали ли в конец таблицы стилей какой-нибудь мусор, собираясь потом его отрефакторить, и так никогда этого и не сделали?
Бывало ли такое, что вы просматривали стили, не до конца понимая что они делают и используются ли они вообще?
Задумывались ли вы, получится ли избавиться от некоторых стилей, ничего при этом не сломав? Не приходилось ли гадать, эти стили работают сами по себе или зависят от других? Случалось ли вам перезаписывать стили?
Это вопросы, которые могут привести к серьезной головной боли, пропущенным дедлайнам и грустным взглядам в окно.
С CSS-модулями и концепцией использования локальной области видимости по умолчанию таких проблем можно избежать. Вам всегда приходится думать о последствиях, когда вы пишите стили.
Например, если вы используете в HTML класс random-gross-class , не обработав его как класс CSS-модуля, стили не применятся, так как CSS-селектор превратится во что-то вроде ._style_random-gross-class_0038089 .
Ключевое слово composes
Допустим, у нас есть модуль под названием type.css , содержащий стили для текста. В этом файле может быть, например, такое:
.serif-font < font-family: Georgia, serif; > .display < composes: serif-font; font-size: 30px; line-height: 35px; >
Один из этих классов мы используем в шаблоне:
import type from "./type.css"; element.innerHTML = `$ "> Пример заголовка `
;
В результате получится такая разметка:
h1 class="_type__display_0980340 _type__serif_404840"> Пример заголовка h1>
Оба класса связаны с элементом через использование ключевого слова composes , решая таким образом некоторые проблемы, которые есть в похожих решениях, например в @extend Sass.
Так можно даже подставлять данные из отдельного CSS-файла:
.element < composes: dark-red from "./colors.css"; font-size: 30px; line-height: 1.2; >
БЭМ не нужен
Нам не нужен БЭМ, если мы используем CSS-модули. По двум причинам:
- Простота чтения. Код вроде type.display так же понятен для разработчика, как и .font-size__serif—large из БЭМ. Его даже проще читать, чем разросшиеся БЭМ-селекторы.
- Локальная область видимости. Допустим, в одном из модулей у нас есть класс .big и он увеличивает font-size на некоторую величину. В другом модуле у нас есть точно такой же класс .big , который увеличивает padding и font-size на другую величину. И это не имеет никакого значения! Они не будут конфликтовать, так как у стилей различаются области видимости. Даже если модуль импортирует обе таблицы стилей, у классов будет своё уникальное имя, созданное в процессе сборки специально для них. Другими словами, при использовании CSS-модулей проблемы специфичности селекторов просто исчезают.
И это только некоторые из достоинств использования CSS-модулей.
Если вам интересно узнать больше, Глен Маден (Glen Madden) много пишет на эту тему.
В следующей статье из этой серии мы рассмотрим как поднять проект используя Webpack и CSS-модули. Мы будем использовать для этого новейшие возможности ES2015 и рассмотрим в процессе несколько примеров, чтобы в полной мере разобраться в происходящем.
Материалы для дальнейшего изучения
- CSS-модули: Добро пожаловать в будущее
- Руководство по CSS-модулям Хьюго Жирауделя (Hugo Giraudel) на Sitepoint
- Разбираемся с модулями в ES6
- Учим ES2015
- Знакомимся с синтаксисом модулей ES6
CSS модули
CSS модули — это популярная система для модульности и компоновки CSS. vue-loader предоставляет первоклассную интеграцию с CSS модулями как возможную альтернативу эмулируемого scoped CSS.
Использование
Просто добавьте атрибут module к тегу :
style module> .red < color: red; > .bold < font-weight: bold; > style>
Это включит режим CSS-модулей в css-loader , и полученный индентификатор объекта класса будет внедрен в компонент как вычисляемое свойство с именем $style . Вы можете использовать его в своих шаблонах для динамического добавления классов:
template> p :class="$style.red"> Этот текст будет красным p> template>
Поскольку это вычисляемое свойство, оно также будет работать с объектом/массивом в :class :
template> div> p :class="< [$style.red]: isRed >"> Буду ли я красным? p> p :class="[$style.red, $style.bold]"> Красный и жирный p> div> template>
Вы также можете получить доступ в JavaScript:
script> export default < created () < console.log(this.$style.red) // -> "_1VyoJ-uZOjlOxP7jWUy19_0" // идентификатор генерируется на основе имени файла и className. > > script>
Указание внедряемого имени модуля
У вас может быть более одного тега в одном *.vue компоненте. Во избежание перезаписи внедряемых стилей вы можете указать имя внедряемого вычисляемого свойства в значении атрибута module :
style module="a"> /* идентификатор будет внедрён как a */ style> style module="b"> /* идентификатор будет внедрён как b */ style>
Настройка параметров css-loader
CSS-модули обрабатываются с помощью css-loader. При использовании настройки css-loader по умолчанию будут такими:
< modules: true, importLoaders: 1, localIdentName: '[hash:base64]' >
Вы можете использовать в vue-loader опцию cssModules чтобы добавить дополнительные параметры для css-loader :
module: < rules: [ < test: '\.vue$', loader: 'vue-loader', options: < cssModules: < localIdentName: '[path][name]---[local]---[hash:base64:5]', camelCase: true > > > ] >
results matching » «
No results matching » «
Методология CSS-модулей
В постоянно меняющемся мире фронтенд разработки очень сложно найти действительно оригинальную концепцию и правильно ее представить, вызвав у других людей желание попробовать ее.
Если взглянуть на CSS, то, на мой взгляд, последней значительной инновацией в нашем подходе к написанию стилей стало появление CSS-препроцессоров, в первую очередь это касается Sass, как наиболее известного из них. Также существует PostCSS, в котором реализован несколько иной подход, но общий принцип действия тот же — в постпроцессор передается неподдерживаемый браузерами синтаксис, а возвращается поддерживаемый синтаксис.
Сейчас у нас появилась очередная интересная новинка — CSS-модули. В этой статье я представлю эту новую технику, расскажу о ее сильных сторонах и о том, как начать с ней работать.
Что такое CSS-модуль ?
Начнем с определения из официального репозитория проекта:
CSS-модуль это файл CSS, в котором названия классов и анимаций по умолчанию заданы локально.
На самом деле все немного сложне. Так как названия классов заданы локально, это требует определенной настройки, сборочного процесса и немного магии, которую иногда сложно понять.
Но в конце концов, мы можем ценить CSS-модули за то, что они реализуют, а это способ ограничивать контекст CSS одним компонентом и возможность избежать ада глобального пространства имен. Больше нет необходимости искать методику именования компонентов, ведь этот шаг делается за вас на стадии сборки!
Как это работает
CSS-модули надо подключать в сборочный процесс, это значит, что они не работают сами по себе. Плагины есть для webpack или Browserify. На базовом уровне это работает так: при импорте файла в модуль JavaScript (например, в компонент React), CSS-модули переопределят объект с названиями классов из файла в динамически задаваемые пространства имен классов, которые можно использовать как строки в JavaScript. Проиллюстрируем это на примере:
Ниже показан очень простой файл CSS. Класс .base не является уникальным в проекте и это не то название класса, которое будет присвоено элементу. Это своего рода псевдоним внутри таблицы стилей, который будет использован в модуле JavaScript.
.base
И вот его использование в тестовом компоненте JavaScript:
import styles from './styles.css'; element.innerHTML = ` CSS Modules are fun. `;
В итоге у нас будет сгенирировано что-то вроде следующего кода (это пример использования CSS-модулей в Webpack с дефолтными настройками):
CSS Modules are fun.
._20WEds96_Ee1ra54-24ePy
Можно настроить генерацию более коротких названий классов или названий, следующих определенному паттерну. Это совершенно не важно (разве что более короткие названия классов дадут небольшой выигрыш в размере таблиц стилей), ведь главное, что они динамически генерируются, уникальны и связаны с правильными стилями.
Возможные возражения
Итак, мы разобрались, как это работает. И теперь вы думаете: “Что же с этим делать? Ведь это…”. ОК, я вас понимаю. Сейчас мы разберем все возражения по отдельности.
Это выглядит ужасно!
Это правда. Но имена классов не должны быть красивыми — их предназначение в применении стилей к элементу. И это именно то, что они делают, поэтому такой аргумент нельзя назвать веским.
Их трудно отлаживать!
Как только в вашей сборке производится какая-либо обработка таблиц стилей, их отладка становится болью. С Sass в этом плане не легче. Поэтому у нас есть sourcemaps, которые также можно настроить и для CSS-модулей.
Я на самом деле утверждаю, что непредсказуемость названий классов не влечет серьезных проблем с отладкой, так как классы по умолчанию специфичны для модулей. И если вы знаете, какой модуль вы сейчас проверяете в инструментах разработчика, то вы знаете, где искать соответствующие стили.
Это препятствует многократному использованию стилей
Да и нет. С одной стороны — да, но это на самом деле и есть цель: мы привязываем стили к компоненту, чтобы избежать конфликтов глобальных стилей. И согласитесь, что это хорошо.
С другой стороны, мы можем создавать глобальные классы (с помощью :global() ), например, вспомогательные классы, которые сохраняются в том же виде после сборки и позволяют абстрагировать стили также просто как и в обычных условиях. Эти классы можно использовать в ваших компонентах JavaScript.
:global(.clearfix::after)
В 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-модулей:
С такой настройкой полученные стили будут размещаться внутри элемента на странице. Это не самое лучшее решение, поэтому мы сконфигурируем вывод итоговых стилей в отдельный файл благодаря плагину извлечения текста для webpack:
Этого достаточно для работы с webpack.
Browserify
Я всегда использовал Browserify только через командную строку, так что это оказалось немного сложнее. Я добавил скрипт npm в файл package.json :
Эта строчка сообщает Browserify, что надо трансформировать src/index.js , в dist/index.js и скомпилировать файл dist/main.css с помощью плагина css-modulesify. Если вы хотите добавить автопрефиксер, то вы можете завершить команду так:
Как видите, вы можете использовать опцию —after для обработки стилей после их компиляции.
Заключение
По состоянию на сегодня, система CSS-модулей еще сыровата, как вы можете заметить по конфигурации в Browserify. Но я убежден, что она будет совершенствоваться, поскольку все большее число людей понимает, что это устойчивое решение для больших и малых проектов.
Я думаю, что идея в основе CSS-модулей это направление, по которому надо двигаться. Я не скажу, что эта библиотека является лучшим из возможных решений, но в ней однозначно есть возможности реализующие то, какими должны быть стили: модульные, изолированные и при этом многократно используемые.
В качестве дальнейшего чтения по теме я рекомендую введение в CSS-модули от Глена Мэддерна, автора этого проекта.
# CSS модули
— это популярная система для модульности и компоновки CSS. vue-loader предоставляет первоклассную интеграцию с CSS модулями как возможную альтернативу эмулируемого локального (scoped) CSS.
# Использование
Во-первых, CSS модули нужно явно включить, передав опцию modules: true в css-loader :
// webpack.config.js module: rules: [ // . другие правила опущены test: /\.css$/, use: [ 'vue-style-loader', loader: 'css-loader', options: // включаем CSS модули modules: true, // настраиваем генерируемое имя класса localIdentName: '[local]_[hash:base64:8]' > > ] > ] > >
Затем, добавляем атрибут module к тегу секции :
style module> .red color: red; > .bold font-weight: bold; > style>
Атрибут module подскажет Vue Loader о необходимости внедрить CSS модуль в компонент в качестве вычисляемого свойства с именем $style . Вы можете использовать его в шаблонах для динамического добавления классов:
template> p :class="$style.red"> Текст должен быть красным p> template>
Поскольку это вычисляемое свойство, оно будет работать с объектом/массивом в :class :
template> div> p :class="< [$style.red]: isRed >"> Буду ли я красным? p> p :class="[$style.red, $style.bold]"> Красный и жирный p> div> template>
Вы также можете получить доступ в JavaScript:
script> export default created () console.log(this.$style.red) // -> "red_1VyoJ-uZ" // идентификатор генерируется на основе имени файла и className. > > script>
для получения информации о глобальных исключениях
# Опциональное использование
Если вы хотите использовать CSS модули только в некоторых компонентах Vue, вы можете использовать правило oneOf и проверять наличие строки module внутри resourceQuery :
// webpack.config.js -> module.rules test: /\.css$/, oneOf: [ // это соответствует `` resourceQuery: /module/, use: [ 'vue-style-loader', loader: 'css-loader', options: modules: true, localIdentName: '[local]_[hash:base64:5]' > > ] >, // это соответствует простому `` или `` use: [ 'vue-style-loader', 'css-loader' ] > ] >
# Использование с пре-процессорами
CSS модули могут быть использованы вместе с другими пре-процессорами:
// webpack.config.js -> module.rules test: /\.scss$/, use: [ 'vue-style-loader', loader: 'css-loader', options: modules: true > >, 'sass-loader' ] >
# Указание имени внедряемого модуля
У вас может быть несколько тегов в одном компоненте *.vue . Во избежание перезаписи внедряемых стилей вы можете указать имя внедряемого вычисляемого свойства в значении атрибута module :
style module="a"> /* идентификатор будет внедрён как a */ style> style module="b"> /* идентификатор будет внедрён как b */ style>