CSS-in-JS без runtime: удобство разработки и максимальная производительность
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:
- Разбирает CSS в шаблонных строках или объектах.
- Генерирует уникальные имена классов.
- Динамически внедряет стили в
<head>документа, в теги<style>.
Этот процесс увеличивает размер JavaScript-бандла и создаёт дополнительные вычислительные расходы при запуске и рендеринге приложения, что влияет на производительность.
2. Выход с помощью «нулевого runtime»
Чтобы решить проблему производительности во время выполнения, сообщество предложило новый подход: CSS-in-JS без runtime (Zero-Runtime).
Основная идея — сохранить удобство разработки CSS-in-JS, но выполнить всю работу во время сборки (build time). Инструмент сборки, обычно плагин Babel или Vite, сканирует код, находит все случаи использования CSS-in-JS, а затем:
- Извлекает текст CSS.
- Создаёт статические файлы
.css. - Заменяет исходный код 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. Он грамотно разделяет «опыт во время разработки» и «производительность во время выполнения», избавляя от необходимости выбирать между ними. Для современных веб-приложений, стремящихся к максимальной производительности и меньшему размеру бандла, это почти идеальное решение.