25 - Module CSS в React, clsx, PostCSS vs Lightning CSS

Оценить качество материала и подачу материала автором видео:

Front-end

Трудоустройтесь middle front-end разработчиком на React JS (TypeScript) за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Front-end

Back-end

Трудоустройтесь middle back-end разработчиком за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Back-end

Карьерный бустер

Получите коммерческий опыт на реальных стартапах, прокачайте tech & soft навыки, научитесь работать в команде, проходить собеседования и получите первую работу в IT!

Перейти на курс
Карьерный бустер

Основы Front-end

Сделайте первый шаг в IT, освоив базовые знания разработки и научившись создавать небольшие проекты на JavaScript

Перейти на курс
Основы Front-end

Основы Back-end

Сделайте первый шаг в IT, освоив базовые знания разработки. Без опыта. Без математики. Только практика: JavaScript, SQL, Node JS, база данных

Перейти на курс
Основы Back-end

CSS module, PostCSS vs LightingCSS, clsx

Автор конспекта: Егор Гурский

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

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

Ограничения глобального CSS

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

Основная проблема — конфликт имен. Рассмотрим практический сценарий: в приложении есть два разных компонента — элемент списка треков (Track item) и детальная карточка трека (Track detail).

css
.track {
  border: 1px solid violet;
}
css
.track {
  border: 1px solid darkred;
}

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

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

Концепция CSS-модулей

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

Механизм работы CSS-модулей выглядит следующим образом:

  • Во время сборки, инструмент-трансформатор берет исходное имя класса (например, track), генерирует и добавляет к нему уникальный хэш. Итоговое имя класса может выглядеть так: _track__1OA6W.
  • При импорте файла *.module.css в React-компонент, сборщик предоставляет специальный JavaScript-объект (традиционно называемый styles). Этот объект служит картой сопоставления, где ключами являются исходные имена классов, а значениями — сгенерированные уникальные строки.
🖋

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

hash_algorithm

Инструменты трансформации стилей

PostCSS

PostCSS — это зрелый и чрезвычайно универсальный инструмент для трансформации CSS, написанный на JavaScript. Он зарекомендовал себя как стандарт в современных инструментальных цепочках, эффективно выступая в роли "Babel для CSS", который позволяет разработчикам использовать будущий синтаксис CSS и расширять его возможности далеко за пределы того, что могли предложить традиционные препроцессоры, такие как Sass или Less. Его главная сила заключается в обширной экосистеме плагинов, которые позволяют настроить любой, даже самый сложный, процесс обработки стилей. Механизм его работы основан на парсинге CSS в Abstract Syntax Tree (AST), последовательном применении плагинов к этому дереву и последующей генерации итоговой CSS-строки.

Процесс настройки выглядит следующим образом:

  • Требуется установка двух основных пакетов: postcss (ядро) и postcss-modules (плагин для модулей).
  • PostCSS принимает массив плагинов. Для активации CSS-модулей необходимо подключить и сконфигурировать postcss-modules. Такая архитектура обеспечивает гибкость, но может приводить к усложнению конфигурации и потенциальным конфликтам версий между плагинами.
  • Ключевая опция generateScopedName определяет шаблон для создания уникальных классов. В нем используются плейсхолдеры:
  • [name]: Имя файла CSS без расширения.
  • [local]: Исходное имя класса (например, track).
  • [hash:base64:5]: Хэш, сгенерированный на основе пути к файлу. Суффикс :base64:5 является инструкцией использовать кодировку base64 и усечь результат до 5 символов.
  • Плагин позволяет получить не только трансформированный CSS (асинхронно через Promise), но и JavaScript-объект (JSON) с сопоставлением исходных и сгенерированных имен через колбэк-функцию getJSON.

Lightning CSS

Lightning CSS — это современная и высокопроизводительная альтернатива, написанная на языке Rust. Его появление является частью более широкой тенденции в индустрии по переписыванию основных инструментов веб-разработки (например, сборщиков, транспиляторов) на высокопроизводительных языках системного уровня, таких как Rust и Go, для кардинального ускорения времени сборки. В отличие от PostCSS, Lightning CSS предлагает монолитный подход, где многие популярные функции, включая CSS-модули, встроены в ядро.

Процесс настройки значительно проще:

  • Требуется установка только одного пакета — lightningcss.
  • Вся функциональность CSS-модулей является встроенной и настраивается через свойство cssModules в объекте конфигурации, передаваемом в основную функцию transform.
  • Синтаксис шаблона в свойстве pattern практически идентичен синтаксису в PostCSS, используя те же плейсхолдеры ([name], [local], [hash]).
  • Функция transform синхронно возвращает объект. Трансформированный CSS-код находится в свойстве code в виде буфера (Buffer) и требует преобразования в строку с помощью метода .toString(). Карта сопоставления классов находится в свойстве exports.

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

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

CLSX

Условное объединение классов с помощью clsx

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

javascript
const className = styles.track + (props.isSelected ? " " + styles.selected : "")

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

Элегантным решением этой проблемы является использование легковесной библиотеки clsx. Чтобы понять ее пользу, рассмотрим, как данные поступают в компонент. При импорте *.module.css мы получаем объект styles:

  1. Мы получаем объект с картой сопоставления
javascript
const styles = { track: "TracksList_track__1OA6W", selected: "TracksList_selected__XYZ78" }

Для clsx нам нужно сформировать конфигурационный объект, где ключами являются сгенерированные имена классов, а значениями — булевы флаги:

javascript
// 2. Цель — создать такой объект для clsx:
const configForClsx = {
  TracksList_track__1OA6W: true,
  TracksList_selected__XYZ78: props.isSelected,
}

Для динамического создания ключей объекта из значений, хранящихся в styles, используется синтаксис вычисляемых имен свойств JavaScript ([expression]). Это позволяет элегантно соединить styles и clsx:

javascript
// 3. Решение с использованием вычисляемых имен свойств
import clsx from "clsx"
// ...
 
const classNames = clsx({
  [styles.track]: true, // Базовый класс, добавляется всегда
  [styles.selected]: props.isSelected, // Добавляется, только если props.isSelected === true
})
 
return <li className={classNames}>...</li>

Этот подход делает код чистым, читаемым и надежным. Утилиты типа clsx являются неотъемлемой частью эффективной работы с CSS-модулями в React-приложениях.


🏠 Домашнее задание

Цель задания: научиться работать с CSS-модулями, заменяя инлайновые стили на модульные, использовать библиотеку clsx для условного объединения классов, а также познакомиться с возможностями Lightning CSS для минификации, автопрефиксинга и оптимизации стилей.

Задание 1

По аналогии, как в видео, необходимо доработать основное приложение Trelly, над которым мы закончили работать в 24 домашнем задании

1.1. MainPage

  • На одном уровне с компонентом MainPage.tsx создай файл MainPage.module.css
  • Замени инлайновые стили из компонента MainPage.tsx на модульные стили в MainPage.module.css

〰️ Инлайновые стили

res25-1-1

Итоговый результат с использованием модульных стилей 🚀

res25-1-2


1.2. TaskDetails

  • В ui директории создай файл TaskDetails.module.css
  • Замени инлайновые стили из компонента TaskDetails.tsx на модульные стили в TaskDetails.module.css
Пример структуры
folder structure
src/
├── ui/
   ├── TaskDetails.tsx
   ├── TaskDetails.module.css
├── App.tsx
└── main.tsx

〰️ Инлайновые стили

res25-2-1

Итоговый результат с использованием модульных стилей 🚀

res25-2-2


1.3. TaskItem

  • В ui директории создай файл TaskItem.module.css
  • Замени инлайновые стили из компонента TaskItem.tsx на модульные стили в TaskItem.module.css
Пример структуры
folder structure
src/
├── ui/
   ├── TaskDetails.tsx
   ├── TaskDetails.module.css
   ├── TaskItem.tsx
   ├── TaskItem.module.css
├── App.tsx
└── main.tsx

〰️ Инлайновые стили

res25-3-1

Итоговый результат с использованием модульных стилей 🚀

res25-3-2


Если в проекте есть компоненты с inline-стилями (стили прямо в JSX), нужно заменить их на модульные CSS-файлы.


Задание 2

Терминал

Установи библиотеку для удобного объединения CSS-классов с условиями clsx

Terminal
pnpm add clsx

TaskItem

Перепиши TaskItem.tsx с использованием clsx

TaskItem.tsx
export function TaskItem(props: Props) {
  /*...*/
 
  const taskClassName = clsx(/*...*/)
 
  const titleClassName = clsx(/*...*/)
 
  return (
    <div key={task.id} className={taskClassName} onClick={() => taskSelectedHandler(task)}>
      <div>
        <p>
          <b>Заголовок</b>: <span className={titleClassName}>{task.attributes.title}</span>
        </p>
        <b>Статус</b>: <input type="checkbox" checked={task.attributes.status === 2} />
        <p>
          <b>Дата создания задачи</b>: {new Date(task.attributes.addedAt).toLocaleDateString()}
        </p>
      </div>
    </div>
  )
}

Итоговый результат с использованием clsx 🚀

res25-4


Задание 3 ⭐

⭐ Дополнительные задания со звёздочкой. Проделывать по желанию.

💡

Lightning CSS обычно настраивается в сборщике (Vite/Webpack) и работает под капотом.

3.1 - CSS modules

  • Создай директорию с понятным для тебя названием. Например, vanilla-css
  • Чтобы инициализировать проект и создать package.json, пропиши команду
Terminal
npm init -y
  • Установи библиотеку для быстрой обработки CSS Lightning CSS
Terminal
pnpm add lightningcss -D
  • Создай файл build-lightning.js и повтори код из урока
build-lightning.js
import { transform } from "lightningcss"
 
const css = `
.button {
  border-radius: 4px;
}
`
 
const result = transform({
  filename: "file.module.css",
  code: Buffer.from(css),
  cssModules: {
    pattern: "[name]__[local]__[hash]",
  },
})
 
console.log(result)
console.log(result.code.toString())
  • Запусти файл командой: node build-lightning.js

Как итоговый результат ты должен увидеть преобразование стиля согласно заданному паттерну 🚀

css
.file-module__button__AyEoSq {
  border-radius: 4px;
}

3.2 - Minification

Научимся сжимать CSS-код для продакшена.

Практика

  • В файле build-lightning.js замени CSS-код
build-lightning.js
/*...*/
 
const css = `
.card {
  background-color: #ffffff;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
 
.card__title {
  font-size: 24px;
  color: #333333;
  margin-bottom: 10px;
}
`
 
const result = transform({
  filename: "file.module.css",
  code: Buffer.from(css),
  cssModules: {
    pattern: "[name]__[local]__[hash]",
  },
  // Минификация
})
 
console.log("\n=== Минифицированный CSS ===")
console.log(result.code.toString())
  • Изучи документацию и напиши код, который будет минифицировать (сжимать) CSS
  • Запусти файл командой node build-lightning.js

Как итоговый результат ты должен увидеть сжатый CSS без пробелов и переносов строк 🚀

.file-module__card__AyEoSq{background-color:#fff;border-radius:8px;padding:20px;box-shadow:0 2px 4px #0000001a}.file-module__card__title__AyEoSq{color:#333;margin-bottom:10px;font-size:24px}

💡

Обрати внимание: Lightning CSS также оптимизирует цвета (#ffffff#fff) и значения!


3.3 - Автопрефиксинг для старых браузеров

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

Практика

  • В файле build-lightning.js замени CSS-код
build-lightning.js
/*...*/
 
const css = `
.container {
  display: flex;
  user-select: none;
  transition: all 0.3s ease;
}
 
.box {
  transform: rotate(45deg);
  backdrop-filter: blur(10px);
}
`
 
const result = transform({
  filename: "file.module.css",
  code: Buffer.from(css),
  cssModules: {
    pattern: "[name]__[local]__[hash]",
  },
  // Поддержка браузеров: Chrome 80, Safari 13
})
  • Изучи документацию и напиши код для автоматического добавления префиксов
  • Запусти файл командой node build-lightning.js

Как итоговый результат ты должен увидеть, как Lightning CSS добавит необходимые вендорные префиксы 🚀

css
.container {
  display: flex;
  -webkit-user-select: none;
  user-select: none;
  transition: all 0.3s ease;
}
 
.box {
  transform: rotate(45deg);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
💡

Что происходит: Lightning CSS анализирует, какие свойства нуждаются в префиксах для указанных версий браузеров.


💡

🔶 CSS-модули решают проблему конфликтов имён классов — каждый класс получает уникальное имя, что делает стили изолированными и предсказуемыми. Это особенно важно в больших проектах, где работают несколько разработчиков.

🔶 Условная стилизация становится проще с clsx — вместо сложных тернарных операторов и конкатенации строк ты можешь элегантно управлять классами в зависимости от состояния компонента. Это делает код чище и понятнее.

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

Боевой маршрут (React Путь Самурая: без альтернатив)

Видеоурок - 27 видео из 30