Настройка CSS-модулей через css.modules

CSS Modules в Vite обеспечивают изоляцию стилей на уровне компонентов за счёт автоматической генерации уникальных классов. Механизм встроен в сборщик и не требует подключения дополнительных плагинов. Конфигурация осуществляется через поле css.modules в vite.config.js или vite.config.ts.


Базовая работа CSS Modules в Vite

При импорте CSS-файла с суффиксом .module.css Vite автоматически включает модульный режим:

/* button.module.css */
.button {
  padding: 10px 16px;
  background: black;
  color: white;
}
import styles from './button.module.css'

export function Button() {
  const el = document.createElement('button')
  el.className = styles.button
  el.textContent = 'Click'
  return el
}

На этапе сборки класс .button преобразуется в уникальный идентификатор, например:

button_button__3x9aK

Конфигурация css.modules в Vite

Основные параметры находятся в объекте css.modules:

// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  css: {
    modules: {
      scopeBehaviour: 'local'
    }
  }
})

scopeBehaviour

Определяет поведение по умолчанию для CSS Modules.

Возможные значения:

  • local — все классы считаются модульными по умолчанию
  • global — классы считаются глобальными, пока не указано иное
css: {
  modules: {
    scopeBehaviour: 'local'
  }
}

При global поведение можно переопределять через :local и :global:

:local(.button) {
  color: red;
}

:global(.reset) {
  margin: 0;
}

generateScopedName

Контролирует формат генерации уникальных имён классов.

Строковый шаблон

css: {
  modules: {
    generateScopedName: '[name]__[local]___[hash:base64:5]'
  }
}

Доступные токены:

  • [name] — имя файла
  • [local] — исходный класс
  • [hash] — хеш содержимого
  • [hash:base64:5] — укороченный вариант хеша

Пример результата:

button__wrapper___aB3xZ

Функция генерации

Позволяет полностью контролировать формат:

css: {
  modules: {
    generateScopedName: (name, filename, css) => {
      return `${name}_${Math.random().toString(36).slice(2, 6)}`
    }
  }
}

Функция получает:

  • name — локальное имя класса
  • filename — путь к файлу
  • css — содержимое CSS

localsConvention

Определяет стиль экспорта имён классов в JavaScript.

css: {
  modules: {
    localsConvention: 'camelCase'
  }
}

Возможные режимы:

  • camelCase — преобразование my-classmyClass
  • camelCaseOnly — доступ только в camelCase форме
  • dashes — сохранение дефисов
  • dashesOnly — только дефисная форма

Пример использования localsConvention

.card-item {
  padding: 10px;
}
import styles from './card.module.css'

styles.cardItem
styles['card-item']

hashPrefix

Добавляет дополнительный префикс в хеширование классов. Используется для предотвращения коллизий при одинаковых именах классов в разных проектах или частях системы.

css: {
  modules: {
    hashPrefix: 'projectA'
  }
}

Даже при одинаковом CSS результат хеша будет отличаться.


globalModulePaths

Позволяет задать файлы, которые будут обрабатываться как глобальные CSS, даже если они имеют расширение .module.css.

import path from 'path'

css: {
  modules: {
    globalModulePaths: [
      path.resolve(__dirname, 'src/styles/global.module.css')
    ]
  }
}

Работа с TypeScript

Vite не генерирует типы для CSS Modules автоматически, но поддержка включается через декларации:

declare module '*.module.css' {
  const classes: { [key: string]: string }
  export default classes
}

Для более строгой типизации часто используется генерация .d.ts файлов:

declare const styles: {
  readonly button: string
  readonly container: string
}
export default styles

Использование CSS Modules в разных типах файлов

JavaScript

import styles from './layout.module.css'

document.body.className = styles.layout

TypeScript

import styles from './layout.module.css'

const className: string = styles.layout

Vue SFC (через Vite)

<template>
  <div :class="$style.container"></div>
</template>

<style module>
.container {
  padding: 20px;
}
</style>

Смешивание глобальных и модульных стилей

CSS Modules в Vite поддерживают совместное использование локальных и глобальных правил:

:global(body) {
  margin: 0;
}

.wrapper {
  padding: 10px;
}

Особенности обработки в Vite

  • CSS Modules компилируются на этапе dev-сервера без полной пересборки проекта
  • Поддерживается HMR для стилей с сохранением состояния компонентов
  • Минификация классов выполняется только в production-режиме
  • Генерация хешей зависит от содержимого файла, а не от порядка импорта

Поведение при импорте нескольких модулей

import a from './a.module.css'
import b from './b.module.css'

Каждый модуль получает собственное пространство имён. Даже одинаковые классы:

.button

будут преобразованы в разные финальные имена в зависимости от файла-источника.


Комбинирование классов

import styles from './card.module.css'

element.className = `${styles.card} ${styles.active}`

Для более сложных сценариев используется clsx или аналог:

import clsx from 'clsx'
import styles from './card.module.css'

element.className = clsx(styles.card, {
  [styles.active]: isActive
})

Производительность и масштабирование

CSS Modules в Vite обрабатываются через esbuild и PostCSS pipeline, что обеспечивает:

  • быстрый старт dev-сервера
  • минимальные задержки при обновлении стилей
  • отсутствие глобального анализа зависимостей CSS
  • локальную переработку только изменённого модуля

Интеграция с PostCSS

CSS Modules не конфликтуют с PostCSS-плагинами:

css: {
  postcss: {
    plugins: [
      require('autoprefixer')
    ]
  },
  modules: {
    generateScopedName: '[hash:base64:6]'
  }
}

Поведение при продакшн-сборке

В production-режиме:

  • имена классов заменяются на хеши
  • удаляются неиспользуемые селекторы
  • минимизируется итоговый CSS
  • объединяются повторяющиеся правила

Ограничения и особенности

  • CSS Modules применяются только к файлам с .module.css
  • глобальные стили требуют явного указания :global
  • динамическая генерация классов возможна только через JS-логику
  • нельзя использовать CSS Modules внутри inline <style> без поддержки фреймворка