← Назад к вопросам

Как сборщик узнает какие CSS классы используются?

2.0 Middle🔥 201 комментариев
#HTML и CSS#Оптимизация и производительность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как сборщик узнает какие CSS классы используются

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

Static Analysis (Статический анализ)

Сборщик анализирует исходный код без его выполнения, ища CSS классы:

// Tailwind CSS использует регулярные выражения для поиска классов
// В конфиге tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{js,jsx,ts,tsx}',  // Пути для сканирования
    './public/index.html'
  ]
};

// Процесс:
// 1. Читает все файлы из указанных путей
// 2. Ищет классы в виде строк, например: class="px-4 py-2"
// 3. Создает CSS только для найденных классов
// 4. Удаляет неиспользуемые классы (tree-shaking)

Как Tailwind находит классы

// РАБОТАЕТ: статические строки
function Button() {
  return <button className="px-4 py-2 bg-blue-500">Click</button>;
}

// РАБОТАЕТ: шаблонные строки с переменными
const size = 'lg';
function Input() {
  return <input className={`input input-${size}`} />;
}

// НЕ РАБОТАЕТ: динамические классы
const buttonClasses = ['px-4', 'py-2']; // Массив теряется
function Button() {
  return <button className={buttonClasses.join(' ')}>Click</button>;
}

// НЕ РАБОТАЕТ: условное объединение
function Badge({ active }) {
  const classStr = active ? 'bg-green' : 'bg-gray';
  return <span className={classStr}>Status</span>;
}

// РАБОТАЕТ: использовать библиотеку clsx или cn()
import { cn } from '@/lib/utils';

function Badge({ active }) {
  return (
    <span className={cn(
      'px-2 py-1',
      active ? 'bg-green-500' : 'bg-gray-300'
    )}>
      Status
    </span>
  );
}

Регулярные выражения для поиска

// Tailwind использует примерно такой regex для поиска классов
const classNameRegex = /[^<>]*(?:(?:<(?!\/|\!)[^>]*>|[^<>]+)*</;[^<>]*)?/g;

// Или проще: ищет паттерны вроде:
// - class="..."
// - className="..."
// - :class="..."
// - @class('...')
// - класс-1234 (UUID или числа)
// - неполные классы: px- не проходит, только px-4

const testCases = [
  'className="px-4 py-2 bg-blue-500"',  // Найдет все три
  'class={`text-lg ${condition && "font-bold"}`',  // Найдет статические
  'className={dynamicClass}',  // НЕ найдет (переменная)
  'data-class="hidden"',  // Может найтись или нет
];

Как работает PurgeCSS и инструменты оптимизации

// В tailwind.config.js или postcss.config.js
module.exports = {
  content: [
    'src/**/*.{js,jsx,ts,tsx}',
    'public/**/*.html'
  ],
  css: ['./src/styles/globals.css'],
  safelist: [
    'text-red-500',  // Гарантировать включение
    'bg-blue-\\[\\d+\\]',  // Regex для динамических
  ],
  blocklist: [
    'hidden',  // Исключить из бандла
  ]
};

// Процесс оптимизации:
// 1. Сканирует все файлы content
// 2. Извлекает CSS классы
// 3. Генерирует CSS только для найденных
// 4. Добавляет safelist (гарантированные)
// 5. Исключает blocklist
// 6. Результат: маленький CSS файл вместо 3-5 MB

Проблемы с динамическими классами

// ПРОБЛЕМА 1: Классы в переменных
const colors = {
  primary: 'text-blue-500',
  danger: 'text-red-500'
};

function Alert({ type }) {
  // Tailwind НЕ найдет text-blue-500 и text-red-500
  return <div className={colors[type]}>Message</div>;
}

// РЕШЕНИЕ: используй статические значения
function Alert({ type }) {
  return (
    <div className={cn(
      type === 'primary' && 'text-blue-500',
      type === 'danger' && 'text-red-500'
    )}>
      Message
    </div>
  );
}

// ПРОБЛЕМА 2: Арифметика с классами
const spacing = 'p-' + (Math.random() * 10); // 'p-3.14...'

// РЕШЕНИЕ: используй существующие значения
const spacingOptions = ['p-1', 'p-2', 'p-3', 'p-4'];
const spacing = spacingOptions[Math.floor(Math.random() * 4)];

Способ 1: Class Name Composition

// lib/utils.ts - используется в проекте PrepBro
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// Компонент
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
}

const baseStyles = 'font-medium rounded-lg transition-colors';
const variantStyles = {
  primary: 'bg-blue-500 text-white hover:bg-blue-600',
  secondary: 'bg-gray-300 text-black hover:bg-gray-400'
};
const sizeStyles = {
  sm: 'px-2 py-1 text-sm',
  md: 'px-4 py-2 text-base',
  lg: 'px-6 py-3 text-lg'
};

export function Button({
  variant = 'primary',
  size = 'md',
  className,
  ...props
}: ButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>) {
  return (
    <button
      className={cn(
        baseStyles,
        variantStyles[variant],
        sizeStyles[size],
        className
      )}
      {...props}
    />
  );
}

// Все классы СТАТИЧЕСКИЕ - Tailwind их найдет

Способ 2: CSS-in-JS (Styled Components)

// styled-components анализирует по-другому
import styled from 'styled-components';

// Инструмент парсит JS код и ищет шаблонные строки
const Button = styled.button`
  padding: 8px 16px;
  background-color: ${props => props.primary ? 'blue' : 'gray'};
  border-radius: 4px;
  
  &:hover {
    opacity: 0.8;
  }
`;

// Стили генерируются в runtime, не требуют static analysis

Способ 3: CSS Modules

// styles.module.css
.button {
  padding: 8px 16px;
  background-color: blue;
  border-radius: 4px;
}

.button:hover {
  opacity: 0.8;
}

// Button.jsx
import styles from './styles.module.css';

export function Button() {
  return <button className={styles.button}>Click</button>;
}

// Сборщик знает о классе через import, не нужен static analysis

Способ 4: Safelist для сложных случаев

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{js,jsx}'],
  safelist: [
    // Гарантировать включение этих классов
    'text-red-500',
    'text-green-500',
    'text-blue-500',
    
    // Или с regex
    {
      pattern: /text-(red|green|blue)-(500|600|700)/,
    },
    {
      pattern: /bg-(red|green|blue)-(100|200|300)/,
    },
  ]
};

Реальный пример из PrepBro

// components/ui/badge.tsx
import { cn } from '@/lib/utils';

interface BadgeProps {
  variant?: 'default' | 'success' | 'warning' | 'error';
  size?: 'sm' | 'md';
  children: React.ReactNode;
}

const variantClasses = {
  default: 'bg-surface-secondary text-content-primary',
  success: 'bg-green-100 text-green-800',
  warning: 'bg-yellow-100 text-yellow-800',
  error: 'bg-red-100 text-red-800'
};

const sizeClasses = {
  sm: 'px-2 py-1 text-xs',
  md: 'px-3 py-1.5 text-sm'
};

export function Badge({
  variant = 'default',
  size = 'sm',
  children
}: BadgeProps) {
  return (
    <span
      className={cn(
        'inline-flex items-center rounded-full font-medium',
        variantClasses[variant],
        sizeClasses[size]
      )}
    >
      {children}
    </span>
  );
}

Инструменты для анализа

# Проверить какие классы будут включены
npm run build

# Или использовать Tailwind CLI
npx tailwindcss -i input.css -o output.css --content 'src/**/*.{js,jsx}'

# Проверить размер CSS
ls -lh dist/styles.css

# Analyzer для webpack
npm install --save-dev webpack-bundle-analyzer

Выводы

  1. Static Analysis — инструменты сканируют исходный код без выполнения
  2. Regex patterns — ищут строковые классы вроде class="..."
  3. Динамические классы НЕ работают — не используй array join
  4. Используй cn() функцию — для безопасного объединения классов
  5. Safelist для сложных — когда нельзя избежать динамики
  6. CSS-in-JS — иначе работает, стили в runtime
  7. CSS Modules — сборщик знает классы через import
  8. Тестируй бандл — проверяй что CSS не раздувается