Модульный CSS и CSS Modules

При разработке интерфейсов на Mithril стили часто оказываются слабым звеном архитектуры. Классический CSS по умолчанию глобален: любое имя класса доступно везде, легко конфликтует, переопределяется и со временем превращается в трудноуправляемый набор правил. В компонентной модели Mithril, где интерфейс строится из независимых view-функций, глобальные стили противоречат самой идее изоляции.

Модульный CSS решает эту проблему за счёт локализации стилей на уровне компонентов. Каждый компонент получает собственное пространство имён для классов, что делает стили предсказуемыми, масштабируемыми и безопасными.


Суть CSS Modules

CSS Modules — это не отдельный стандарт CSS, а подход к сборке, при котором каждый CSS-файл компилируется в JavaScript-объект с отображением:

локальноеИмяКласса → сгенерированноеУникальноеИмя

Пример результата сборки:

{
  button: "_button_9f3a1",
  active: "_active_2d91c"
}

Исходный CSS:

.button {
  padding: 8px 12px;
}

.active {
  background: #1976d2;
}

В рантайме компонент работает уже не с глобальными классами, а с конкретными, уникальными идентификаторами.


Почему CSS Modules хорошо сочетаются с Mithril

Mithril не навязывает систему стилей и не содержит встроенных механизмов scoped-CSS. Это делает его особенно удобным для интеграции CSS Modules:

  • компоненты — обычные функции, легко принимающие зависимости;
  • отсутствует виртуальная магия шаблонов;
  • классы передаются явно через class или className;
  • сборка полностью контролируется разработчиком.

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


Организация файлов

Типичная структура компонента с модульным CSS:

Button/
  Button.js
  Button.module.css

Каждый компонент владеет своим CSS-файлом. Отсутствует необходимость в общих styles.css, за исключением базовых токенов или reset-стилей.


Импорт CSS Modules в компонент Mithril

При использовании сборщика (Webpack, Vite, Rollup) CSS-модуль импортируется как обычный JavaScript-объект.

import styles from "./Button.module.css";

const Button = {
  view(vnode) {
    const { active } = vnode.attrs;

    return m(
      "button",
      {
        class: active
          ? `${styles.button} ${styles.active}`
          : styles.button
      },
      vnode.children
    );
  }
};

export default Button;

Ключевые моменты:

  • styles — обычный объект;
  • доступ к классам осуществляется через свойства;
  • ошибки в имени класса проявляются сразу на этапе выполнения.

Генерация имён классов

Сборщик отвечает за генерацию уникальных имён. Часто используется шаблон:

[filename]__[classname]__[hash]

Пример:

Button__button__9f3a1

Это:

  • исключает конфликты;
  • упрощает отладку в DevTools;
  • сохраняет читаемость.

В production-сборке имена обычно сокращаются до хешей для уменьшения размера.


Условные стили и композиция классов

В Mithril нет встроенного хелпера для классов, поэтому используются стандартные приёмы JavaScript.

Пример с несколькими состояниями:

const className = [
  styles.button,
  disabled && styles.disabled,
  primary && styles.primary
].filter(Boolean).join(" ");

Такой код остаётся прозрачным и не требует сторонних библиотек.


Композиция стилей внутри CSS Modules

CSS Modules поддерживают директиву composes, позволяющую переиспользовать стили.

.base {
  padding: 8px 12px;
  border-radius: 4px;
}

.primary {
  composes: base;
  background: #1976d2;
  color: white;
}

В Jav * aScript:

m("button", { class: styles.primary })

Композиция происходит на этапе сборки, не создавая вложенных селекторов и не увеличивая специфичность.


Локальные и глобальные стили

Иногда требуется задать глобальные правила: reset, типографика, стили для body. CSS Modules позволяют явно обозначать глобальные селекторы.

:global(body) {
  margin: 0;
  font-family: system-ui, sans-serif;
}

Или:

:global(.container) {
  max-width: 1200px;
}

Это подчёркивает намерение и предотвращает случайное загрязнение глобального пространства.


Передача классов через атрибуты

Компоненты Mithril часто принимают внешние классы через attrs. При использовании CSS Modules важно разделять ответственность:

const Card = {
  view({ attrs, children }) {
    return m(
      "div",
      {
        class: [styles.card, attrs.class].filter(Boolean).join(" ")
      },
      children
    );
  }
};

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


Общие переменные и дизайн-токены

CSS Modules не запрещают использование CSS-переменных. Напротив, они хорошо подходят для вынесения общих значений:

:global(:root) {
  --color-primary: #1976d2;
  --radius: 4px;
}

В модульных стилях:

.button {
  background: var(--color-primary);
  border-radius: var(--radius);
}

Так достигается баланс между локальностью компонентов и единым визуальным языком.


Инкапсуляция состояний вместо каскада

В модульном CSS исчезает необходимость полагаться на вложенные селекторы:

/* плохо */
.card .title {
  font-weight: bold;
}

Предпочтительный вариант:

.card {}
.title {}

Каждый элемент получает собственный класс, явно привязанный в view. Это уменьшает зависимость от структуры DOM и упрощает рефакторинг компонентов Mithril.


Сравнение с inline-стилями и CSS-in-JS

По сравнению с inline-стилями CSS Modules:

  • поддерживают псевдоклассы и медиа-запросы;
  • не пересоздают объекты стилей при каждом рендере;
  • лучше оптимизируются браузером.

По сравнению с CSS-in-JS:

  • не добавляют рантайм-зависимостей;
  • не требуют хаков для SSR;
  • остаются ближе к стандартному CSS.

Для Mithril, ориентированного на производительность и минимальный размер, это особенно важно.


Серверный рендеринг и предсказуемость

При использовании SSR классы CSS Modules генерируются на этапе сборки, а не во время выполнения. Это гарантирует совпадение HTML и CSS между сервером и клиентом без дополнительных механизмов синхронизации.

Mithril, как фреймворк без жёсткой инфраструктуры, выигрывает от такой детерминированности.


Ошибки и анти-паттерны

  • Смешивание глобального CSS и модулей без явных границ.
  • Использование сложных каскадов внутри модульных файлов.
  • Передача строковых имён классов вместо обращения к объекту styles.
  • Хранение нескольких компонентов в одном CSS-модуле.

Каждый из этих пунктов снижает ценность модульного подхода.


Итоговая роль CSS Modules в архитектуре Mithril

Модульный CSS превращает стили в полноценную часть компонентной архитектуры. В сочетании с функциональной моделью Mithril он обеспечивает:

  • изоляцию;
  • предсказуемость;
  • простоту сопровождения;
  • минимальный оверхед.

Стили перестают быть глобальной проблемой и становятся локальным, управляемым ресурсом каждого компонента.