На протяжении почти десятилетия фронтенд-индустрия жила под влиянием парадигмы CSS-in-JS. Библиотеки вроде styled-components и Emotion обещали идеальную жизнь разработчика: полную изоляцию стилей на уровне компонентов, удобную передачу пропсов в правила CSS и автоматическое удаление неиспользуемых стилей из бандла. Однако по мере роста масштабов веб-сервисов этот комфорт обернулся скрытым налогом на производительность. Инженеры GitHub завершили масштабную трехлетнюю миграцию интерфейсов сайта со styled-components на классические CSS Modules и поделились впечатляющими результатами: серверный рендеринг страниц ускорился более чем в два раза.
Опыт команды дизайн-системы Primer показал, что попытка заставить JavaScript управлять каждой строчкой таблицы стилей во время выполнения запроса наносит сокрушительный удар по быстродействию.
Куда исчезают миллисекунды: цена рантайм-стилизации
В архитектуре styled-components браузер и сервер Node.js вынуждены совершать огромный объем невидимой работы на каждый отрендеренный компонент. Сервер должен распарсить шаблонную строку, вычислить хеш-сумму классов на основе переданных пропсов, сгенерировать уникальные CSS-правила и вставить их в тело ответа.
// Исторический подход: styled-components с вычислением стилей в рантайме
import styled from 'styled-components';
interface ButtonProps {
$variant?: 'primary' | 'danger';
}
export const ActionButton = styled.button<ButtonProps>`
padding: 8px 16px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
background-color: ${props => props.$variant === 'danger' ? 'var(--color-danger-fg)' : 'var(--color-btn-bg)'};
border: 1px solid var(--color-btn-border);
&:hover {
filter: brightness(0.95);
}
`;
При рендеринге страницы с тысячами элементов (коммиты, диффы кода, списки задач) этот механизм превращается в тяжелую нагрузку на процессор. На клиенте ситуация повторяется: при гидратации JavaScript тратит драгоценное процессорное время на регистрацию виртуальных таблиц стилей и инъекцию тегов <style> в DOM-дерево.
Переход на CSS Modules: статика против динамики
Решением стал переход на CSS Modules — технологию, изолирующую имена классов на этапе предварительной сборки проекта без какого-либо рантайма в Node.js или браузере. Компоненты Primer React были переписаны на статические файлы .module.css.
Вместо интерполяции JavaScript-пропсов прямо в строки стилей команда задействовала нативные механизмы современного CSS: селекторы атрибутов данных и CSS-переменные:
/* ActionButton.module.css: чистая изоляция на этапе компиляции */
.button {
padding: 8px 16px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
border: 1px solid var(--color-btn-border);
background-color: var(--button-bg, var(--color-btn-bg));
}
.button[data-variant="danger"] {
--button-bg: var(--color-danger-fg);
}
.button:hover {
filter: brightness(0.95);
}
// ActionButton.tsx: легковесный компонент без зависимости от CSS-in-JS
import styles from './ActionButton.module.css';
interface ButtonProps {
variant?: 'primary' | 'danger';
children: React.ReactNode;
}
export function ActionButton({ variant = 'primary', children }: ButtonProps) {
return (
<button className={styles.button} data-variant={variant}>
{children}
</button>
);
}
Парадокс объема и победа кэширования
После завершения миграции метрики производительности github.com показали поразительный сдвиг:
- Время серверного рендеринга (SSR) сократилось на 55%, освободив значительные вычислительные ресурсы инфраструктуры.
- Время инициализации компонентов в браузере при гидратации уменьшилось на 25%, обеспечив моментальный отклик интерфейса на клики.
При этом инженеры столкнулись с любопытным парадоксом: суммарный объем доставляемых CSS-файлов немного вырос. Поскольку статический CSS теперь включает селекторы для всех возможных состояний компонентов, а не только для тех, что присутствуют на текущем экране, байтов стилей в бандле стало больше. Однако этот прирост оказался абсолютно безвредным. Статический CSS отдается в сжатом виде (Brotli), кэшируется глобальной сетью CDN один раз на недели вперед и парсится нативным движком браузера на порядки быстрее, чем любой JavaScript.
Опыт GitHub подвел черту под эпохой тяжелого CSS-in-JS: возврат к корням веб-платформы и статическому CSS остается самым эффективным рецептом высокой скорости в веб-разработке.
