Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Добавляет статью про CSS-in-JS #5670

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions people/ruslanpro01/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
name: 'Руслан Шевченко'
url: https://github.com/RuslanPro01
solarrust marked this conversation as resolved.
Show resolved Hide resolved
---
218 changes: 218 additions & 0 deletions tools/css-in-js/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
---
title: "Обзор CSS-in-JS"
description: "CSS в JavaScript — разберём, как можно описывать компоненты и стили, не создав ни одного CSS-файла"
solarrust marked this conversation as resolved.
Show resolved Hide resolved
authors:
- ruslanpro01
related:
- tools/how-the-browser-creates-pages
- tools/preprocessors
- tools/react-and-alternatives
tags:
- article
---

## Кратко

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

Существуют разные решения, но все они работают схожим образом: стили задаются как [объекты](https://doka.guide/js/object/) или [шаблонные строки](https://doka.guide/js/template-strings/) и привязываются к компонентам через уникальные классы - это обеспечивает их изоляцию и упрощённое управление.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

Основные библиотеки для написания CSS внутри JS — это [Styled Components](https://styled-components.com/), [Emotion](https://emotion.sh/docs/introduction), [Styled JSX](https://github.com/vercel/styled-jsx) и [vanilla-extract](https://vanilla-extract.style/).

## Почему CSS-in-JS?

Главное преимущество использования подхода CSS-in-JS — возможность использовать конструкции JS при описании стилей компонента. В зависимости от проекта, это может помочь описывать динамические стили, которые зависят от условий, а также упростить работу с темизацией.

<aside>

☝️ CSS-in-JS не универсальное решение

Многие задачи, такие как изоляция стилей, динамическая стилизация и управление темами, можно решить с помощью традиционного CSS, препроцессоров или CSS Modules. Выбор подхода зависит от специфики проекта и предпочтений команды. CSS-in-JS предлагает определённые преимущества, но не является универсальным решением для всех случаев 🙂
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

</aside>

## Краткая история CSS-in-JS
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

Тут мы бегло рассмотрим историю появления CSS-in-JS решений.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

<aside>

🏃‍♂️ Краткое содержание блока, для тех, кто спешит
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

CSS зародился в 90-е годы и, будучи относительно простым, долго удовлетворял потребности веб-разработки, однако с появлением реактивных фреймворков и переходом к крупным SPA-приложениям стал очевиден ряд проблем в масштабировании стилей. Чтобы их решить, начиная с 2014 года, разработчики начали экспериментировать с подходом CSS-in-JS, что привело к созданию библиотек вроде [JSS](https://cssinjs.org/) и подобных.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

</aside>

### Становление CSS

CSS (Каскадные таблицы стилей) начал развитие в 90-е годы, был отмечен консорциумом [W3C](https://www.w3.org/) и начал стремительно развиваться. CSS стал методом определения стиля веб-документа. Он был мощным, чтобы удовлетворить потребности проектов тех лет, и был относительно прост, чтобы все желающие могли приступить к работе в короткий срок.

### Появление реактивных фреймворков и SPA

До 2010-х годов, в основном создавались многостраничные приложения с четким разделением ответственности:

- HTML — разметка страницы,
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved
- CSS — описывал, как они будут выглядеть,
- JavaScript — отвечал за интерактивность.

С течением времени развивался не только CSS, но и JavaScript — это приводит к появлению новой экосистемы разработки и современных реактивных фреймворков: Angular (2010), React (2013), Vuejs (2014). Разработка приложений начинает смещаться от многостраничных приложений на разработку одностраничных приложений (SPA), а пользователи стали ожидать от них более динамичного и сложного поведения.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

### Кристофер Шедо, его доклад о проблемах CSS и разработка первых решений CSS-in-JS

В 2014 году Кристофер Шедо (инженер известной социальной сети) [выступает с докладом](https://speakerdeck.com/vjeux/react-css-in-js) о 7 проблемах CSS, которые нужно решить для беспроблемной разработки больших динамичных приложений:

1. **Глобальное пространство имён**
Все CSS-селекторы живут в одной глобальной области, из-за чего стили разных компонентов могут конфликтовать или перезаписывать друг друга.

1. **Зависимости**
При большом числе стилей сложно понять, в каком порядке подключать файлы и какие компоненты от чего зависят, что может привести к нежелательным коллизиям.

1. **Удаление неиспользуемого кода**
В больших проектах часто копится CSS, который больше нигде не применяется, и его непросто безопасно убрать, не зная точного использования.

1. **Минификация**
При сжатии стилей важно сохранять их читабельность для отладки, но и уменьшать размер, чтобы повысить производительность.

1. **Общий доступ к константам**
Трудно централизованно хранить и переиспользовать значения (например, цвета, размеры), чтобы все команды работали с едиными стилевыми параметрами.

1. **Проблемы каскада**
Когда несколько правил стилизуют одни и те же элементы, порядок подключения CSS может приводить к непредсказуемым результатам и конфликтам.

1. **Изоляция**
Нужен способ гарантировать, что стили каждого компонента не затронут стили других, что особенно актуально в больших проектах.

В этом же году начинается работа над CSS-in-JS. Первыми экспериментальными подходами можно считать решения, появившиеся в экосистеме React. Одна из самых ранних и широко известных библиотек — [JSS](https://cssinjs.org/). Чуть позже начали появляться и другие проекты, но именно JSS часто упоминают как одну из первых полноценных реализаций «CSS в JavaScript».

## Runtime / zero runtime

За время развития CSS-in-JS появилось несколько вариантов реализации: от полностью рантайм-решений до почти «нулевого» времени исполнения. Ниже разберём ключевые варианты и их особенности.

<aside>

💡 Рантайм в контексте фронтенд-разработки

обычно означает, что часть логики (в данном случае генерация стилей) выполняется напрямую в браузере, «на лету» при запуске приложения. То есть когда пользователь уже открыл страницу, JS-код просчитывает стили и «вживую» вставляет их в `<style>` или управляет классами.

В отличие от этого, «zero-runtime» решения максимально переносят логику (включая генерацию CSS) на этап сборки, чтобы в браузер попадал уже готовый, статический CSS.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

</aside>

### Runtime подход

#### Суть подхода

Стили формируются и инжектируются в DOM во время исполнения (рантайма) приложения. В процессе рендера или при изменении состояний создаются (или обновляются) соответствующие `<style>` или классы в браузере.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

#### Плюсы

- Легко менять стили на лету в зависимости от состояния, темизации и т. д.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved
- Удобно изолировать логику стилизации в самих компонентах, сохраняя стиль и логику рядом
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

#### Минусы

- Дополнительная нагрузка в рантайме: при каждом рендере может происходить генерация и монтирование новых стилей, что влияет на производительность.
- Возможны сложности и повышенные требования к настройке серверного рендеринга (SSR) и кеширования стилей.

#### Библиотеки, реализующие подход

- [Styled Components](https://styled-components.com/)
Использует шаблонные литералы для описания стилей внутри JS/TS. Генерирует уникальные классы в рантайме и монтирует их в DOM.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved
- [Emotion](https://emotion.sh/docs/styled)
Также генерирует стили во время исполнения. Позволяет писать стили через шаблонные литералы (`styled`) или объектный синтаксис (`css`).
- [JSS](https://cssinjs.org/)
Основан на использовании JS-объектов, которые конвертируются в CSS в рантайме.

### Zero-runtime подход

#### Суть подхода

Стили вычисляются и извлекаются на этапе сборки. В результате в браузер попадает статический (или почти статический) CSS, а логика генерации стилей не исполняется или почти не исполняется.

#### Плюсы

- Нет накладных расходов в браузере (минимальный дополнительный JS-код)
- Благодаря пункту выше обычно получаются улучшенные показатели производительности и метрик загрузки
- Проще реализовать серверный рендеринг, так как стили уже готовы к моменту, когда HTML отдается клиенту

#### Минусы

- Ограничения в динамических стилях: большая часть логики должна быть статически рассчитана во время сборки, поэтому стили, зависящие от состояний, могут быть менее гибкими
- Сложность настройки сборки: обычно требует дополнительной настройки сборщиков (Webpack, Vite и другие)
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

#### Библиотеки, реализующие подход

- [Linaria](https://linaria.dev/)
Анализирует CSS, написанный в виде шаблонных литералов, на этапе сборки и извлекает его в отдельные файлы. В рантайме остаются только классы.
- [Vanilla Extract](https://vanilla-extract.style/)
Пишется на TypeScript/JS, но генерирует классические CSS-файлы во время сборки, практически без JS для стилизации на клиенте.

## Основы синтаксиса

Синтаксис у большинства решений очень похож, мы рассмотрим основы синтаксиса на псевдокоде, за подробностями лучше заглянуть в документацию к конкретной библиотеке.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

### Простейший компонент

Ниже показан пример объявления компонента-кнопки, стили для которого описаны прямо внутри JavaScript-кода. Благодаря функции `styled('button')` создаётся новый компонент, наследующий все свойства обычной кнопки, но обладающий заданными стилями.

```javascript
import {styled} from "css-in-js" // псевдобиблиотека для примера

const Button = styled('button')`
color: white;
background-color: black;
border: none;
`

export default Button;
```

### Динамические стили

Ниже показана функция, которая генерирует динамические стили для фона зависимости от флага `isPrimary`. При `true` функция вернёт синий цвет фона, при `false` — серый.

```javascript
import {css} from "css-in-js" // псевдобиблиотека для примера

function getBackgroundColor(isPrimary) {
return css`
background-color: ${isPrimary ? 'blue' : 'gray'};

&:hover {
background-color: ${isPrimary ? 'darkblue' : 'darkgray'};
}
`;
}

export {getBackgroundColor};
```

В первом фрагменте создаётся функция `getBackgroundColor(isPrimary)`, которая возвращает блок CSS с разными цветами фона и `:hover` в зависимости от аргумента `isPrimary`. Во втором примере мы видим, как этот фрагмент может **переиспользоваться**: в новом компоненте `Button` мы вызываем `getBackgroundColor(true)` и вставляем результат в шаблон стилей, «включая» логику динамической стилизации, уже описанной в функции. Это удобно, если нужно единообразно управлять цветами фона в разных компонентах, сохранив их стили в одном месте.
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

```javascript
import {styled} from "css-in-js" // псевдобиблиотека для примера
import {getBackgroundColor} from "./utils.js"

const Button = styled('button')`
color: white;
border: none;
${getBackgroundColor(true)};
`

export default Button;
```

<aside>

💡 Что такое `const styles = css``;`?
solarrust marked this conversation as resolved.
Show resolved Hide resolved

В основе такого подхода лежит Теговый шаблон, подробнее об этом мы рассказали [в статье про шаблонные строки](https://doka.guide/js/template-strings/#tegovyy-shablon).
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

</aside>

## На практике
RuslanPro01 marked this conversation as resolved.
Show resolved Hide resolved

Чаще всего выбор подхода к CSS-in-JS определяется требованиями к производительности, объёмом динамики в интерфейсе и готовностью настраивать сборку. При этом на практике часто всё сводится к двум простым сценариям:

- Если нужна «минимальная боль» при использовании динамических стилей и важна поддержка большого сообщества — часто берут **Emotion** или **Styled Components**.
- Если же приоритет — производительность и нет потребности в постоянном рендере уникальных стилей, можно обратить внимание на **Linaria** или **Vanilla Extract**.
Loading