CSS-in-JS без runtime: удобство разработки и максимальная производительность

2 мин

1. «Обоюдоострый меч» CSS-in-JS

Решения CSS-in-JS, такие как styled-components и Emotion, радикально изменили управление стилями в компонентной разработке, позволив писать CSS в JavaScript- или TypeScript-файлах. Они дают множество преимуществ:

  • Область видимости компонента: Стили по умолчанию привязаны к компоненту, что решает проблему глобальных конфликтов имён CSS.
  • Динамические стили: Динамические стили можно легко создавать на основе props или state компонента.
  • Совместное размещение кода: Стили и логика компонента находятся в одном файле, что упрощает сопровождение и организацию.

Однако за эти удобства приходится платить. Основа традиционных библиотек CSS-in-JS — runtime. Когда компонент монтируется в браузере, JavaScript runtime библиотеки CSS-in-JS:

  1. Разбирает CSS в шаблонных строках или объектах.
  2. Генерирует уникальные имена классов.
  3. Динамически внедряет стили в <head> документа, в теги <style>.

Этот процесс увеличивает размер JavaScript-бандла и создаёт дополнительные вычислительные расходы при запуске и рендеринге приложения, что влияет на производительность.

2. Выход с помощью «нулевого runtime»

Чтобы решить проблему производительности во время выполнения, сообщество предложило новый подход: CSS-in-JS без runtime (Zero-Runtime).

Основная идея — сохранить удобство разработки CSS-in-JS, но выполнить всю работу во время сборки (build time). Инструмент сборки, обычно плагин Babel или Vite, сканирует код, находит все случаи использования CSS-in-JS, а затем:

  1. Извлекает текст CSS.
  2. Создаёт статические файлы .css.
  3. Заменяет исходный код CSS-in-JS сгенерированными уникальными именами классов.

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

3. Основные решения без runtime

a. Linaria

Linaria — один из пионеров CSS-in-JS без runtime. Она позволяет использовать знакомый синтаксис тегированных шаблонных строк styled.

// YourComponent.js
import { styled } from '@linaria/react';

const Title = styled.h1`
  font-size: 2rem;
  color: tomato;
`;

// 构建时,这会被转换为:
// <h1 class="Title_a1b2c3d">...</h1>
//
// 并在一个静态 .css 文件中生成:
// .Title_a1b2c3d {
//   font-size: 2rem;
//   color: tomato;
// }

b. vanilla-extract

Разработанный командой SEEK vanilla-extract идёт дальше и использует TypeScript для создания полностью типобезопасных стилей. Все определения стилей представлены экспортируемыми переменными в файлах .css.ts, что обеспечивает мощный вывод типов и автодополнение.

// styles.css.ts
import { style } from '@vanilla-extract/css';

export const title = style({
  fontSize: '2rem',
  color: 'tomato',
});

c. Panda CSS

Panda CSS — более новый конкурент. Он заимствует идеи Tailwind CSS, предлагает опыт разработки со «Style Props» и при этом гарантирует вывод без runtime.

import { css } from '../styled-system/css';

function MyComponent() {
  return <div className={css({ fontSize: '2rem', color: 'tomato' })} />;
}

4. Преимущества и компромиссы

Преимущества:

  • Максимальная производительность: Конечный результат представляет собой статический CSS без расходов JavaScript runtime.
  • Меньший размер бандла: На клиенте не нужно загружать библиотеку CSS-in-JS.
  • Мощный опыт разработки: Сохраняются область видимости компонента, типобезопасность TypeScript и совместное размещение кода.

Компромиссы:

  • Зависимость от сборки: Решение необходимо интегрировать в процесс сборки, поэтому конфигурация несколько усложняется.
  • Ограниченная динамичность: Нельзя генерировать совершенно новые стили из значений, доступных только во время выполнения, например из данных API. Однако CSS-переменные и другие техники покрывают большинство динамических сценариев.

Заключение

CSS-in-JS без runtime — это корректировка курса традиционной парадигмы CSS-in-JS. Он грамотно разделяет «опыт во время разработки» и «производительность во время выполнения», избавляя от необходимости выбирать между ними. Для современных веб-приложений, стремящихся к максимальной производительности и меньшему размеру бандла, это почти идеальное решение.