CSS Modules в экосистеме Webpack позволяют изолировать стили на уровне компонентов, исключая глобальные конфликты классов и обеспечивая предсказуемость каскада. Однако при практической разработке неизбежно возникает необходимость сочетать локальную область видимости с глобальными стилями: reset/normalize, типографика, переменные темы, utility-классы, сторонние библиотеки. В результате формируется гибридная модель, в которой CSS Modules и глобальные стили сосуществуют в одном проекте.
CSS Modules преобразуют каждый CSS-класс в уникальный идентификатор
на этапе сборки. Это происходит через loader в Webpack, чаще всего через
связку css-loader с включённой опцией
modules.
Исходный CSS:
.button {
padding: 10px;
background: blue;
}
После обработки:
.button__3x9aK {
padding: 10px;
background: blue;
}
JS-код получает доступ к сгенерированному имени через объект:
import styles from './button.module.css';
element.className = styles.button;
Таким образом, локальные стили перестают участвовать в глобальном каскаде и становятся привязанными к конкретному модулю.
Несмотря на локализацию, CSS Modules не запрещают создание глобальных
правил. В css-loader предусмотрена директива
:global, которая отключает модульное преобразование для
выбранных селекторов.
:global(.container) {
max-width: 1200px;
margin: 0 auto;
}
Такой класс будет доступен во всём приложении без изменения имени.
Также возможно глобализировать вложенные блоки:
.wrapper {
padding: 20px;
}
.wrapper :global(.external-class) {
color: red;
}
Здесь .wrapper остаётся локальным, а
.external-class — глобальным.
В большинстве архитектур Webpack глобальные стили выделяются в
отдельные файлы, которые не проходят через modules: true.
Обычно это:
Пример конфигурации Webpack:
module.exports = {
module: {
rules: [
{
test: /\.module\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
}
]
},
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
Такое разделение формирует две параллельные системы:
*.module.css → локальная область видимости*.css → глобальная область видимостиОсновная сложность возникает при пересечении глобального и локального каскада. Несмотря на изоляцию имён, CSS остаётся каскадным языком, и специфичность сохраняется.
Глобальный стиль:
.button {
background: red;
}
Локальный стиль:
.button {
background: blue;
}
После трансформации локальный класс становится уникальным:
.button__a1b2c {
background: blue;
}
Глобальный .button уже не влияет на локальный компонент,
однако если локальный класс применяется совместно с глобальным, возможны
конфликты:
<div className={`button ${styles.button}`} />
В этом случае применяется каскад, и итоговый результат зависит от порядка подключения стилей в DOM.
Глобальные стили часто структурируются как фундамент, подключаемый первым:
html {
font-size: 16px;
}
body {
margin: 0;
font-family: system-ui;
}
Эти правила не должны зависеть от компонентов и не должны переопределяться локальными стилями.
Глобальные utility-классы используются для повторяемых мелких задач:
.mt-10 {
margin-top: 10px;
}
.text-center {
text-align: center;
}
Они остаются глобальными по дизайну, так как их задача — перекрёстное использование.
Глобальные переменные позволяют разделить логику и оформление:
:root {
--primary-color: #1e90ff;
--font-size-base: 16px;
}
В модулях они используются без утраты локальности:
.title {
color: var(--primary-color);
font-size: var(--font-size-base);
}
В реальных проектах CSS Modules почти никогда не используются изолированно. Типичная архитектура включает гибридный подход:
Пример компонента:
import styles from './card.module.css';
export function Card() {
return (
<div className={`card ${styles.card}`}>
<h2 className="text-center">Title</h2>
</div>
);
}
Здесь:
card — глобальный класс (например, layout или
reset-стиль)styles.card — локальная стилизация компонентаtext-center — utility-классКаскад становится многослойным:
Однако фактический порядок может меняться в зависимости от порядка
импорта в Webpack и подключения через style-loader.
Важно учитывать, что CSS Modules не изменяют каскадность языка, они только модифицируют селекторы.
Типичная структура проекта:
styles/
base.css
reset.css
variables.css
utilities.css
components/
Button/
button.module.css
Button.jsx
Подключение глобальных стилей обычно выполняется один раз в entry-файле:
import './styles/reset.css';
import './styles/base.css';
import './styles/variables.css';
import './styles/utilities.css';
CSS Modules позволяют частично эмулировать глобальные стили через
composes:
.base {
padding: 10px;
border-radius: 4px;
}
.primary {
composes: base;
background: blue;
}
Это создаёт локальную композицию без выхода в глобальный scope, сохраняя изоляцию.
Многие UI-библиотеки используют глобальные классы. При интеграции с CSS Modules возникает необходимость явного взаимодействия:
<div className={`${styles.wrapper} external-lib-class`} />
Иногда требуется переопределение:
:global(.external-lib-class) {
margin: 0;
}
Такой подход используется осторожно, так как он нарушает изоляцию модулей.
Частые проблемы возникают при неправильном распределении областей видимости:
:globalКаждая из этих проблем приводит к потере предсказуемости каскада и усложняет поддержку системы стилей.
Корректная архитектура строится на чётком разделении:
:global применяется только для интеграции, а не для
основной логикиТакая структура позволяет сохранять предсказуемость каскада при одновременном использовании глобальных и локальных стилей в Webpack-проектах.