Дайджесты новостей
Концептуальная иллюстрация миграции GitHub Primer с динамического CSS-in-JS на статические CSS-модули с ускорением SSR на 55%.

Отказ от CSS-in-JS в GitHub Primer: ускорение SSR на 55% за счет CSS-модулей

Концепция CSS-in-JS годами оставалась отраслевым золотым стандартом для фронтенда на React. Разработчики восторгались удобством: стили инкапсулированы внутри компонентов, динамические переменные прокидываются прямо через JavaScript-пропсы, а универсальный пропс sx позволяет быстро подправить отступ или цвет без создания отдельных классов. Однако по мере роста масштаба веб-приложения за эту иллюзорную простоту приходится расплачиваться миллисекундами времени отклика каждого пользователя.

Инженеры GitHub Джош Блэк (Josh Black) и Мари Лукка (Mari Luuka) подвели итоги масштабной многолетней миграции флагманской дизайн-системы Primer: компания полностью отказалась от библиотеки styled-components и пропса sx, переведя всю кодовую базу GitHub.com на классические статические CSS-модули (CSS Modules).

Архитектурная ловушка рантайм-стилизации

Главным пороком рантайм-подхода CSS-in-JS оказалась его линейная деградация по мере увеличения сложности страницы. При каждом рендеринге компонента библиотека styled-components:

  1. Вычисляет криптографический хэш от объекта переданных стилей.
  2. Парсит правила и преобразует их в строковый CSS.
  3. Динамически инжектирует новые теги <style> в DOM-дерево документа.

В сценарии серверного рендеринга (SSR) на стороне Node.js это создавало колоссальную нагрузку на центральный процессор. Сервер тратил десятки миллисекунд только на то, чтобы собрать и сериализовать стили сотен компонентов интерфейса перед отправкой первого байта (TTFB). А на клиенте браузер тратил ценное процессорное время основного потока на повторный разбор стилей во время гидратации, блокируя отзывчивость кнопок и ссылок.

Сравним устаревший компонент с пропсом sx и его оптимизированную версию.

Раньше верстка компонента выглядела так:

// Устаревший подход Primer: пропс sx парсится в рантайме на каждый рендер
import { Box, Text } from '@primer/react';

export const UserCard = ({ name, role, isHighlighted }: Props) => (
  <Box
    sx={{
      p: 3,
      borderRadius: 2,
      border: '1px solid',
      borderColor: isHighlighted ? 'accent.emphasis' : 'border.default',
      backgroundColor: 'canvas.subtle',
      display: 'flex',
      alignItems: 'center',
      gap: 2,
    }}
  >
    <Text sx={{ fontWeight: 'bold', color: 'fg.default' }}>{name}</Text>
    <Text sx={{ color: 'fg.muted', fontSize: 1 }}>{role}</Text>
  </Box>
);

В новой архитектуре компонент перешел на статичные CSS-модули и стандартные пользовательские переменные (CSS Custom Properties):

// Современный подход Primer: статические CSS-модули без рантайм-оверхеда
import clsx from 'clsx';
import styles from './UserCard.module.css';

export const UserCard = ({ name, role, isHighlighted }: Props) => (
  <div
    className={clsx(styles.card, {
      [styles.highlighted]: isHighlighted,
    })}
  >
    <span className={styles.name}>{name}</span>
    <span className={styles.role}>{role}</span>
  </div>
);

Стили теперь компилируются сборщиком в обычный статический файл UserCard.module.css один раз при билде:

.card {
  padding: var(--base-size-16);
  border-radius: var(--borderRadius-medium);
  border: 1px solid var(--borderColor-default);
  background-color: var(--bgColor-muted);
  display: flex;
  align-items: center;
  gap: var(--base-size-8);
}

.highlighted {
  border-color: var(--borderColor-accent-emphasis);
}

.name {
  font-weight: var(--base-text-weight-semibold);
  color: var(--fgColor-default);
}

.role {
  font-size: var(--primer-font-size-small);
  color: var(--fgColor-muted);
}

Замеры производительности и уроки миграции

Результаты отказа от рантайм-вычислений превзошли ожидания:

  • Сокращение времени SSR на 55%: на тяжелых страницах репозиториев (Pull Requests, код-ревью, списки коммитов) задержка генерации разметки упала более чем вдвое.
  • Ускорение гидратации на 25%: первое монтирование компонентов на клиенте стало происходить на четверть быстрее, мгновенно делая страницу интерактивной.
  • Кэширование на CDN: статические CSS-файлы теперь эффективно кэшируются на периферии сети независимо от обновлений JavaScript-логики.

Для проведения столь масштабного рефакторинга команда GitHub активно привлекала автономных агентов GitHub Copilot, автоматизировавших рутинную замену десятков тысяч вызовов sx по всей кодовой базе.

Опыт GitHub ставит окончательную точку в спорах о CSS-in-JS: для высоконагруженных веб-сервисов с серверным рендерингом будущее лежит в плоскости статических CSS-модулей и нативных возможностей браузерной платформы.