
Две крупные революции изменили способ написания и управления CSS: встраивание CSS в JS во время выполнения (runtime CSS-in-JS ) и недавний переход к встраиванию CSS в JS во время сборки (build-time CSS-in-JS). Оба подхода соответствуют требованиям UI-компонуемости, легко интегрируются с современными UI-фреймворками и позволяют использовать паттерны, повышающие удобство сопровождения кода.
Чтобы понять необходимость последнего подхода, рассмотрим сначала преимущества первого.
CSS-in-JS во время выполнения (без извлечения CSS во время сборки)
CSS-in-JS — технология стилизации, при которой CSS пишется с помощью JavaScript, часто в том же файле, что и стилизуемый компонент. Например:
import { css } from '@emotion/css'
const primaryColor = '#4287f5'
const myClass = css`
padding: 32px;
color: ${primaryColor}`
const StyledDiv = () => <div className={myClass} />
Эта технология и поддерживающие ее инструменты стали невероятно популярны по двум основным причинам: они отлично работают с компонуемыми приложениями и значительно облегчают процесс разработки.
Ориентированность на компоненты: масштабируемая, контекстно-зависимая стилизация, поддерживающая повторное использование компонентов
Обычный CSS по своей природе является глобальным. Он эффективен в унаследованных монолитных приложениях, где различные части UI тесно связаны между собой и отличаются «гиперосведомленностью» о своем контексте. В таких приложениях создаются глобальные CSS-файлы, которые применяют стили к различным элементам, часто используя селекторы, предполагающие определенную структуру HTML.
.main-content article h2 {
font-size: 28px;
color: navy;
text-align: center;
}
Современные компонуемые UI, напротив, часто используют компоненты повторно в разных частях приложения или даже в разных приложениях.
Компоненты размещаются в разных местах DOM. Это означает, что их стилизация должна быть независимой от контекста. Правила стилизации не должны определяться глобально, с использованием селекторов, которые ожидают найти элементы в определенном месте дерева DOM (т. е. они не должны выбирать путь, который не будет применяться, если компоненты установлены в другом месте).
Кроме того, компоненты часто создаются и поставляются разными разработчиками. Если правила стилизации не привязаны к компонентам, то наверняка возникнут конфликты имен.
Преимущества использования JS/TS для DevX
Javascript и в большей степени Typescript предоставляют разработчикам более широкие возможности, чем CSS. Проверка типов, завершение кода и оптимизированная поддержка линтинга позволяют разработчикам создавать высоко стандартизированный и поддерживаемый код, который способствует более надежным UI.
Стандартизация на основе типов также играет важную роль в компонуемости. Например, тип темы можно использовать в качестве контракта для команд в организации, чтобы расширять и изменять базовую тему или тему по умолчанию, придерживаясь при этом одних и тех же правил и дизайн-токенов.
import { createTheme, defaultTheme, type ThemeOptions } from '@my-org.design/theme';
export function darkTheme(): ThemeOptions {
return createTheme(defaultTheme(), {
palette: {
type: 'dark',
primary: {
main: '#6580f9',
secondary: '#ab7bb3',
},
background: {
default: '#454546',
paper: '#000000',
},
text: {
primary: '#ffffff',
},
},
});
Интеграция с современными UI-фреймворками
Современные UI реализуются с помощью фреймворков, которые используют абстрактный слой на основе Javascript для выполнения манипуляций в DOM. Разработчик редко напрямую взаимодействует с HTML приложения, что делает решение для стилизации на основе JS более естественным.
Например, состояние компонента или его пропсы, которые «живут в мире JS», можно легко использовать для изменения стиля компонента, который находится в том же мире.
interface ButtonProps {
primary?: boolean;
}
const Button = styled.button<ButtonProps>`
background-color: ${props => props.primary ? 'blue' : 'gray'};
`;
CSS-in-JS во время сборки
Как уже говорилось, библиотеки CSS-in-JS оптимизировали стилизацию, позволив разработчикам писать CSS непосредственно в JavaScript. Однако с ростом приложений появились и недостатки — увеличение размеров пакетов и снижение производительности во время выполнения из-за вычисления стилей на стороне клиента.
Именно здесь важную роль играет извлечение CSS во время сборки. Оно сохраняет преимущества CSS-in-JS, но при этом устраняет его недостатки в плане производительности.
При этом решения CSS-in-JS во время сборки не только решают проблемы производительности, но и учитывают последние тенденции в сфере SSR (server-side rendering — рендеринг на стороне сервера). Хотя CSS-in-JS во время выполнения могут использоваться в SSR, они не оптимизированы для него. Кроме того, некоторые паттерны и технологии, используемые для SSR-приложений, не поддерживаются.
Например, RSC (React Server Components — серверные компоненты React) не могут использовать такие механизмы внедрения стилей во время выполнения, как useContext
и useState
, для работы с CSS, поскольку рендерятся на сервере без механизмов жизненного цикла React, доступных в браузере. Статические стили, не зависящие от состояния приложения, необходимы для модели выполнения RSC только на сервере.
Популярные инструменты и библиотеки CSS-in-JS включают Vanilla Extract, Linaria, Panda CSS, StyleX, а недавно в Material UI появилась экспериментальная поддержка Pigment CSS.
Многие инструменты (хотя и не все) основаны на wyw-in-js («whatever-you-want-in-JS» — «все-что-вам-надо-в-JS») — инструментарии для CSS с нулевым временем выполнения.
Механизм извлечения CSS во время сборки
В разных решениях используются различные технологии и методы оптимизации. Однако каждый способ извлечения CSS во время сборки включает три основных этапа: парсинг объявлений CSS-in-JS, генерацию статического CSS-файла и обновление кода для ссылок на классы в новом статическом файле.
Этап 1: Парсинг исходного кода с помощью статического анализа
Инструмент сканирует кодовую базу, чтобы найти объявления CSS-in-JS. Например:
import { styled } from 'linaria/react';
const Button = styled.button`
background-color: #327fa8;
color: #ffffff;
`;
Этап 2: Генерация статического CSS
Прошедшие парсинг объявления CSS-in-JS преобразуются в стандартные правила CSS. Для предотвращения конфликта имен генерируются уникальные имена классов (часто с использованием хэша).
.styles_button-fhg2jf {
background-color: #327fa8;
color: #ffffff;
}
Шаги по оптимизации часто включаются в CSS-in-JS-решения во время сборки, например объединение дублирующихся CSS-правил, устранение «неживого» кода и т. д.
Этап 3: Обновление кода для ссылок на сгенерированные статические классы
const Button = (props) => <button className="styles_button-fhg2jf" {...props} />;
Поскольку этот процесс происходит на этапе сборки, он достигается путем настройки бандлера с помощью соответствующего плагина. Для примера ниже приведена базовая настройка Webpack для Vanilla Extract:
/**
* @filename: webpack.config.js
*/
const {
VanillaExtractPlugin
} = require('@vanilla-extract/webpack-plugin');
module.exports = {
plugins: [new VanillaExtractPlugin()]
};
Избежание проблем совместимости при повторном использовании компонентов CSS-in-JS во время сборки
В современной веб-разработке компоненты часто используются в разных проектах как Bit-компоненты, чтобы сделать кодовую базу более удобной для сопровождения, ускорить разработку и улучшить общее взаимодействие.
Примером могут служить пользовательские компоненты Shadcn/UI, совместно используемые и поддерживаемые на платформе Bit:

Однако компоненты, как и полноценные приложения, требуют определенного контекста для своей функциональности. Этот контекст можно представить как «среду выполнения компонентов«. Разработчики должны стремиться сделать этот контекст как можно более гибким, чтобы можно было повторно использовать общие компоненты в более широком спектре приложений.
UI-компонент, использующий (в памяти) состояния для стилизации, не сможет работать во всех контекстах. В качестве примера уже приводились React Server Components. Чтобы решить эту проблему, либо воздержитесь от использования состояний во всех компонентах, либо убедитесь в наличии у них резервного варианта для более общих сред выполнения (fallback) в случаях, когда контекст не позволяет использовать состояния.
Читайте также:
- Запуск Puppeteer в Akka.js
- Создание интерактивных аналоговых часов с помощью Vue3
- 12 генераторов CSS для работы в 2023 году
Читайте нас в Telegram, VK и Дзен
Перевод статьи Eden Ella: Build-Time CSS-in-JS Libraries: Here’s Why They Are Trending