- architecture.md разбит на architecture/index.md + reference/layers.md, modules.md, segments.md - добавлена вложенность в сайдбар (Слои, Модули, Сегменты) - обновлён fileOrder в concat-md.js для 4 файлов - исправлены dead links на /basics/architecture (добавлен слеш) - перегенерированы RULES.md и README
2089 lines
81 KiB
Markdown
2089 lines
81 KiB
Markdown
<!-- /index -->
|
||
# NextJS Style Guide
|
||
|
||
Правила и стандарты разработки на NextJS и TypeScript: архитектура, типизация, стили, компоненты, API и инфраструктурные разделы.
|
||
|
||
## Для ассистентов
|
||
|
||
Полная документация в одном MD файле: https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/RULES.md
|
||
|
||
## Структура документации
|
||
|
||
### Workflow
|
||
|
||
**Что делать и в каком порядке** — пошаговые инструкции.
|
||
|
||
| Раздел | Отвечает на вопрос |
|
||
|--------|-------------------|
|
||
| Начало работы | Что нужно знать перед началом разработки? |
|
||
| Создание проекта | Как начать новый проект? |
|
||
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
|
||
| Добавление страницы | Как добавить новую страницу в проект? |
|
||
| Добавление UI-модуля | Как создать компонент, бизнес-модуль, виджет или layout? |
|
||
| Стилизация | Как стилизовать компоненты в проекте? |
|
||
| Получение данных | Как получать данные с сервера? |
|
||
| Управление состоянием | Как работать с состоянием? |
|
||
| Локализация | Как добавлять переводы и подключать локализацию? |
|
||
|
||
### Базовые правила
|
||
|
||
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
|
||
|
||
| Раздел | Отвечает на вопрос |
|
||
|--------|-------------------|
|
||
| Технологии и библиотеки | Какой стек используем? |
|
||
| Архитектура | Как устроены слои SLM, зависимости, публичный API? |
|
||
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
||
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
||
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||
|
||
### Прикладные разделы
|
||
|
||
**Как это настроить и использовать** — конфигурация, структура и примеры кода для конкретных областей.
|
||
|
||
| Раздел | Отвечает на вопрос |
|
||
|--------|-------------------|
|
||
| Настройка VS Code | Как настроить редактор для проекта? |
|
||
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
|
||
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
||
| Изображения | _(не заполнен)_ |
|
||
| SVG-спрайты | _(не заполнен)_ |
|
||
| Видео | _(не заполнен)_ |
|
||
| API | _(не заполнен)_ |
|
||
| Stores | _(не заполнен)_ |
|
||
| Хуки | _(не заполнен)_ |
|
||
| Шрифты | _(не заполнен)_ |
|
||
| Локализация | _(не заполнен)_ |
|
||
|
||
<!-- /workflow -->
|
||
## Workflow
|
||
|
||
Порядок действий при разработке — от создания проекта до реализации фич.
|
||
|
||
### Создание проекта
|
||
|
||
Инициализация нового проекта из готового шаблона.
|
||
|
||
1. Создать проект из шаблона:
|
||
```bash
|
||
npx tiged git@gromlab.ru:templates/nextjs.git my-app
|
||
cd my-app
|
||
npm install
|
||
```
|
||
2. Проект готов к разработке — стек, структура SLM, конфигурация
|
||
редактора и шаблоны генерации уже настроены.
|
||
|
||
### Генерация кода
|
||
|
||
Создание модулей из шаблонов `.templates/` вместо ручного создания файлов.
|
||
|
||
1. Определить тип модуля и соответствующий шаблон:
|
||
|
||
| Модуль | Слой | Шаблон |
|
||
|------------|--------------|-------------|
|
||
| Компонент | `ui/` | `component` |
|
||
| Бизнес-модуль | `business/` | `module` |
|
||
| Виджет | `widgets/` | `widget` |
|
||
| Layout | `layouts/` | `layout` |
|
||
| Экран | `screens/` | `screen` |
|
||
| Стор | `stores/` | `store` |
|
||
|
||
2. Сгенерировать модуль из шаблона.
|
||
3. Если подходящего шаблона нет — сначала создать шаблон, затем использовать.
|
||
|
||
Ручное создание файловой структуры модулей запрещено.
|
||
|
||
### Добавление страницы
|
||
|
||
Создание нового маршрута: экран + точка входа для роутинга.
|
||
|
||
1. Сгенерировать экран из шаблона `screen` в `src/screens/`.
|
||
2. Заполнить экран логикой и стилями.
|
||
3. Создать `page.tsx` в нужном маршруте `src/app/`.
|
||
|
||
`page.tsx` — тонкая обёртка: только `metadata` и рендер экрана.
|
||
Логика, стили и хуки размещаются в экране, не в `page.tsx`.
|
||
|
||
### Добавление UI-модуля
|
||
|
||
Создание компонента, бизнес-модуля, виджета или layout.
|
||
|
||
1. Сгенерировать модуль из соответствующего шаблона в целевой слой.
|
||
2. Заполнить модуль логикой и стилями.
|
||
3. Дочерние компоненты — генерировать из шаблона `component` в папку `ui/`
|
||
внутри родителя.
|
||
|
||
Дочерние компоненты не экспортируются через `index.ts` родителя.
|
||
|
||
### Стилизация
|
||
|
||
Выбор инструмента стилизации по приоритету.
|
||
|
||
1. Использовать Mantine-компоненты и их пропсы.
|
||
2. Если Mantine не покрывает — использовать CSS-токены
|
||
(`--color-*`, `--space-*`, `--radius-*`).
|
||
3. Если нужна кастомная стилизация — PostCSS Modules.
|
||
|
||
Инлайн-стили (`style`), магические значения и глобальные стили
|
||
вне `app/styles/` запрещены.
|
||
|
||
### Получение данных
|
||
|
||
*Раздел в разработке* — SWR, генерация API-клиентов, сокеты.
|
||
|
||
### Управление состоянием
|
||
|
||
*Раздел в разработке* — когда создавать стор, что хранить локально и глобально.
|
||
|
||
### Локализация
|
||
|
||
*Раздел в разработке* — переводы и i18next.
|
||
|
||
<!-- /basics/tech-stack -->
|
||
## Технологии и библиотеки
|
||
|
||
Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте.
|
||
|
||
### Что используем
|
||
|
||
#### Стек
|
||
- `React` / `TypeScript` — основной стек для UI и приложения.
|
||
- `Next.js` — для продуктовых сайтов.
|
||
|
||
#### Архитектура
|
||
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/basics/architecture/).
|
||
|
||
#### UI компоненты
|
||
- `Mantine UI` — базовые UI-компоненты.
|
||
|
||
#### Работа с данными (API)
|
||
- `@gromlab/api-codegen` — генерация API‑клиентов и типов.
|
||
- `SWR` — получение, кеширование, ревалидация, дедубликация.
|
||
- `SWR (useSWRSubscription)` — сокеты, реалтайм подписки.
|
||
|
||
#### Store
|
||
- `Zustand` — глобальное состояние.
|
||
|
||
#### Локализация
|
||
- `i18next (i18n)` — локализация всех пользовательских текстов.
|
||
|
||
#### Тестирование
|
||
- `Vitest` — тестирование.
|
||
|
||
#### Стили
|
||
- `PostCSS Modules` — изоляция стилей.
|
||
- `Mobile First` — подход к адаптивной верстке.
|
||
- `clsx` — конкатенация CSS‑классов.
|
||
|
||
#### Генерация
|
||
- `@gromlab/create` — шаблонизатор для создания слоёв и других файлов из шаблонов.
|
||
|
||
<!-- /basics/naming -->
|
||
## Именование
|
||
|
||
Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту.
|
||
|
||
### Базовые правила
|
||
|
||
| Что | Рекомендуется |
|
||
| ---------------- | ---------------------- |
|
||
| Папки | `kebab-case` |
|
||
| Файлы | `kebab-case` |
|
||
| Переменные | `camelCase` |
|
||
| Константы | `SCREAMING_SNAKE_CASE` |
|
||
| Классы | `PascalCase` |
|
||
| React-компоненты | `PascalCase` |
|
||
| Хуки | `useSomething` |
|
||
| CSS классы | `camelCase` |
|
||
| Ключи enum | `SCREAMING_SNAKE_CASE` |
|
||
|
||
|
||
### Именование файлов
|
||
|
||
Суффикс обозначает роль или тип файла. Пишется в единственном числе.
|
||
Формат: `name.<suffix>.ts`.
|
||
|
||
**Хуки**
|
||
- `use-name.hook.ts` — файл хука, функция именуется `useName`
|
||
|
||
**Логика**
|
||
- `.store.ts` — стор
|
||
- `.service.ts` — сервис
|
||
|
||
**Типы и контракты**
|
||
- `.type.ts` — типы и интерфейсы
|
||
- `.interface.ts` — интерфейсы
|
||
- `.enum.ts` — enum
|
||
- `.dto.ts` — внешние DTO
|
||
- `.schema.ts` — схемы валидации
|
||
- `.constant.ts` — константы
|
||
- `.config.ts` — конфигурация
|
||
|
||
**Утилиты**
|
||
- `.util.ts` — утилиты
|
||
- `.helper.ts` — вспомогательные функции
|
||
- `.lib.ts` — библиотечный код
|
||
|
||
**Тесты**
|
||
- `.test.ts` — тесты
|
||
- `.mock.ts` — моки
|
||
|
||
**Хорошо**
|
||
```text
|
||
business/
|
||
└── auth-by-email/
|
||
├── ui/
|
||
│ └── login-form.tsx
|
||
├── hooks/
|
||
│ └── use-auth.hook.ts
|
||
├── stores/
|
||
│ └── auth.store.ts
|
||
├── types/
|
||
│ └── auth.type.ts
|
||
├── auth-by-email.tsx
|
||
└── index.ts
|
||
```
|
||
|
||
**Плохо**
|
||
```text
|
||
business/
|
||
└── authByEmail/
|
||
├── LoginForm.tsx
|
||
├── useAuth.ts
|
||
├── authStore.ts
|
||
└── index.ts
|
||
```
|
||
|
||
### Булевы значения
|
||
|
||
- Использовать префиксы `is`, `has`, `can`, `should`.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
const isReady = true;
|
||
const hasAccess = false;
|
||
const canSubmit = true;
|
||
const shouldRedirect = false;
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: неясное булево значение без префикса.
|
||
const ready = true;
|
||
const access = false;
|
||
const submit = true;
|
||
```
|
||
|
||
### События и обработчики
|
||
|
||
- Обработчики начинать с `handle`.
|
||
- События и колбэки начинать с `on`.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
const handleSubmit = () => { ... };
|
||
const onSubmit = () => { ... };
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: неочевидное назначение имени.
|
||
const submitClick = () => { ... };
|
||
```
|
||
|
||
### Коллекции
|
||
|
||
- Для массивов использовать имена во множественном числе.
|
||
- Для словарей/мап — использовать суффиксы `ById`, `Map`, `Dict`.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
const users = [];
|
||
const usersById = {} as Record<string, User>;
|
||
const userIds = ['u1', 'u2'];
|
||
const ordersMap = new Map<string, Order>();
|
||
const featureFlagsDict = { beta: true, legacy: false } as Record<string, boolean>;
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: имя не отражает, что это коллекция.
|
||
const user = [];
|
||
// Плохо: словарь назван как массив.
|
||
const usersMap = [];
|
||
// Плохо: по имени непонятно, что это словарь.
|
||
const users = {} as Record<string, User>;
|
||
```
|
||
|
||
<!-- /basics/architecture/index -->
|
||
## SLM Design
|
||
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
||
|
||
### Преимущества
|
||
|
||
#### Вертикальная организация домена
|
||
|
||
Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы.
|
||
|
||
#### Dependency Injection без фреймворков
|
||
|
||
Cross-domain зависимости в бизнес-слое реализуются через фабрики — модуль декларирует что ему нужно, а точка использования предоставляет зависимости. Домены изолированы без DI-контейнеров, провайдеров и шин событий.
|
||
|
||
#### Разделение ответственности без перегрузки слоёв
|
||
|
||
Сервисы приложения (`infrastructure/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
|
||
|
||
#### Горизонтальная инкапсуляция
|
||
|
||
Вложенные модули (`parts/`) и направление зависимостей позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга.
|
||
|
||
#### Колокация по умолчанию
|
||
|
||
Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями.
|
||
|
||
#### Явное разделение каркаса и контента
|
||
|
||
Каркас группы маршрутов (`layouts/`) и контент конкретной страницы (`screens/`) — независимые слои с собственной ответственностью.
|
||
|
||
#### Масштабирование через группировку
|
||
|
||
При росте проекта слои не теряют структуру — модули группируются по естественным признакам: бизнес-домены по субдоменам, страницы по разделам, UI-компоненты по уровню абстракции (примитивы и композиции).
|
||
|
||
### Происхождение
|
||
|
||
SLM Design вырос на основе:
|
||
|
||
- **Feature-Sliced Design** — слоистая структура, публичный API модуля, направление зависимостей
|
||
- **Vertical Slice Architecture** — модуль как вертикальный срез, содержащий всё необходимое
|
||
- **Screaming Architecture** — структура проекта «кричит» о назначении: открыл `business/auth` — видишь авторизацию
|
||
- **Colocation Principle** — код живёт рядом с местом использования
|
||
|
||
### Пример структуры проекта
|
||
|
||
```text
|
||
src/
|
||
├── app/
|
||
│
|
||
├── layouts/
|
||
│ ├── main/
|
||
│ └── dashboard/
|
||
│
|
||
├── screens/
|
||
│ ├── home/
|
||
│ ├── products/
|
||
│ ├── product-detail/
|
||
│ └── about/
|
||
│
|
||
├── widgets/
|
||
│ ├── page-heading/
|
||
│ ├── hero-section/
|
||
│ └── promo-banner/
|
||
│
|
||
├── business/
|
||
│ ├── auth/
|
||
│ ├── catalog/
|
||
│ ├── orders/
|
||
│ └── chat/
|
||
│
|
||
├── infrastructure/
|
||
│ ├── theme/
|
||
│ ├── i18n/
|
||
│ ├── backend-api/
|
||
│ └── logger/
|
||
│
|
||
├── ui/
|
||
│ ├── button/
|
||
│ ├── input/
|
||
│ ├── modal/
|
||
│ ├── toast/
|
||
│ └── dropdown/
|
||
│
|
||
└── shared/
|
||
├── lib/
|
||
├── types/
|
||
└── styles/
|
||
```
|
||
|
||
### Принципы
|
||
|
||
- **Домен — единое целое.** Всё, что относится к домену, живёт в одном модуле.
|
||
- **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости.
|
||
- **Зависимости однонаправлены.** Импорты только сверху вниз, только через публичный API.
|
||
- **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.
|
||
|
||
<!-- /basics/architecture/reference/layers -->
|
||
## Слои
|
||
|
||
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
|
||
|
||
### Определение
|
||
|
||
**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.**
|
||
|
||
### Группы слоёв
|
||
|
||
Слои делятся на три группы:
|
||
|
||
| Группа | Слои | Описание |
|
||
|--------|------|----------|
|
||
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
|
||
| Ядро | `business`, `infrastructure`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
|
||
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
|
||
|
||
### Направление зависимостей
|
||
|
||
Любой импорт между модулями — только через публичный API.
|
||
|
||
```
|
||
app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared
|
||
```
|
||
|
||
- `layouts` и `screens` — параллельные слои, не импортируют друг друга
|
||
- Модули одного слоя в группе «Композиция» изолированы друг от друга
|
||
- Модули одного слоя `infrastructure` и `ui` могут импортировать друг друга через публичный API
|
||
- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую
|
||
- Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях
|
||
|
||
|
||
### Слой App
|
||
|
||
Точка входа приложения. Отвечает за запуск, роутинг и композицию маршрутов из layout и screen.
|
||
|
||
В отличие от остальных слоёв, `app/` не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации.
|
||
|
||
#### Требования
|
||
|
||
- Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация
|
||
- Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов
|
||
- Провайдеры и гарды — только подключает готовые из нижних слоёв, не реализует
|
||
- Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы
|
||
- Никем не импортируется
|
||
|
||
### Слой Layouts
|
||
|
||
Каркас страницы: общие элементы, одинаковые для группы маршрутов (header, footer, sidebar).
|
||
|
||
```text
|
||
src/layouts/
|
||
├── main/
|
||
├── dashboard/
|
||
└── auth/
|
||
```
|
||
|
||
#### Требования
|
||
|
||
- Содержит только модули
|
||
- Не содержит бизнес-логику
|
||
- Контекстно-зависимые блоки принимает через пропсы от `app`, не импортирует напрямую
|
||
|
||
### Слой Screens
|
||
|
||
Контент конкретной страницы: собирает её из модулей нижних слоёв.
|
||
|
||
```text
|
||
src/screens/
|
||
├── home/
|
||
├── products/
|
||
├── product-detail/
|
||
├── about/
|
||
└── contacts/
|
||
```
|
||
|
||
Когда количество страниц затрудняет навигацию — вводится группировка по разделам. Группа — папка для организации, не модуль (без `index.ts`).
|
||
|
||
```text
|
||
src/screens/
|
||
├── shop/
|
||
│ ├── home/
|
||
│ ├── products/
|
||
│ ├── product-detail/
|
||
│ └── cart/
|
||
├── account/
|
||
│ ├── profile/
|
||
│ ├── settings/
|
||
│ └── order-history/
|
||
└── info/
|
||
├── about/
|
||
├── contacts/
|
||
└── faq/
|
||
```
|
||
|
||
#### Требования
|
||
|
||
- Содержит только модули
|
||
- Не содержит бизнес-логику
|
||
- Локальные одноразовые секции живут внутри screen-модуля, не выносятся в `widgets`/`business`
|
||
|
||
### Слой Widgets
|
||
|
||
Составной блок интерфейса, который компонует модули ядра, но не принадлежит конкретному бизнес-домену. Widget появляется когда блок используется в нескольких screens или layouts.
|
||
|
||
Если блок принадлежит домену — он живёт в `business/{area}/`, даже если переиспользуется. Если блок нужен только в одном месте — это `screens/{name}/parts/` или `layouts/{name}/parts/`, а не widget.
|
||
|
||
```text
|
||
src/widgets/
|
||
├── page-heading/
|
||
├── hero-section/
|
||
├── onboarding-checklist/
|
||
├── promo-banner/
|
||
└── error-boundary/
|
||
```
|
||
|
||
#### Требования
|
||
|
||
- Не принадлежит конкретному бизнес-домену. Если блок доменный — он живёт в `business/`
|
||
- Используется в нескольких screens или layouts
|
||
|
||
### Слой Business
|
||
|
||
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
|
||
|
||
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Cross-domain зависимости по коду реализуются через фабрику. `import type` между доменами разрешён напрямую.
|
||
|
||
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки.
|
||
|
||
```text
|
||
src/business/
|
||
├── auth/
|
||
├── catalog/
|
||
├── orders/
|
||
├── checkout/
|
||
└── chat/
|
||
```
|
||
|
||
Когда количество доменов затрудняет навигацию — вводится группировка по субдоменам. Группа — папка для организации, не модуль (без `index.ts`).
|
||
|
||
```text
|
||
src/business/
|
||
├── commerce/
|
||
│ ├── catalog/
|
||
│ ├── cart/
|
||
│ ├── orders/
|
||
│ └── checkout/
|
||
└── communication/
|
||
├── chat/
|
||
└── notifications/
|
||
```
|
||
|
||
#### Требования
|
||
|
||
- Один модуль = один бизнес-домен
|
||
- Циклические зависимости между доменами запрещены
|
||
- Импорт кода между доменами — через фабрику. `import type` — напрямую
|
||
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
|
||
|
||
### Слой Infrastructure
|
||
|
||
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
|
||
|
||
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`.
|
||
|
||
Отличие от `shared/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||
|
||
```text
|
||
src/infrastructure/
|
||
├── theme/
|
||
├── i18n/
|
||
├── backend-api/
|
||
├── maps-api/
|
||
├── logger/
|
||
├── feature-flags/
|
||
└── realtime/
|
||
```
|
||
|
||
#### Требования
|
||
|
||
- Один модуль = один техсервис
|
||
- Импортирует `infrastructure/`, `ui/`, `shared/`
|
||
|
||
### Слой UI
|
||
|
||
UI-кит без бизнес-логики: button, carousel, toast, modal.
|
||
|
||
Слой входит в группу «Ядро». Импортирует `ui/` и `shared/`.
|
||
|
||
Компоненты строятся друг на друге: `button` использует `icon`, `carousel` использует `button`.
|
||
|
||
```text
|
||
src/ui/
|
||
├── button/
|
||
├── input/
|
||
├── icon/
|
||
├── carousel/
|
||
├── modal/
|
||
├── toast/
|
||
├── dropdown/
|
||
├── tabs/
|
||
└── tooltip/
|
||
```
|
||
|
||
Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (`button`, `icon`, `input`) не импортируют композиции. Композиции (`carousel`, `modal`, `dropdown`) строятся на примитивах.
|
||
|
||
```text
|
||
src/ui/
|
||
├── primitives/
|
||
│ ├── button/
|
||
│ ├── input/
|
||
│ ├── icon/
|
||
│ └── badge/
|
||
└── composites/
|
||
├── carousel/
|
||
├── modal/
|
||
├── dropdown/
|
||
├── tabs/
|
||
└── tooltip/
|
||
```
|
||
|
||
#### Требования
|
||
|
||
- Не содержит бизнес-логику
|
||
- Импортирует только `ui/` и `shared/`
|
||
|
||
### Слой Shared
|
||
|
||
Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене.
|
||
|
||
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
|
||
|
||
Отличие от `infrastructure/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||
|
||
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
|
||
|
||
```text
|
||
src/shared/
|
||
├── lib/
|
||
├── types/
|
||
├── styles/
|
||
└── sprites/
|
||
```
|
||
|
||
#### Требования
|
||
|
||
- Не имеет runtime-состояния
|
||
|
||
<!-- /basics/architecture/reference/modules -->
|
||
## Модули
|
||
|
||
Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом.
|
||
|
||
### Определение
|
||
|
||
**Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.**
|
||
|
||
### Модуль vs компонент
|
||
|
||
**Компонент** — один `.tsx` файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или `ui/` сегменте модуля.
|
||
|
||
**Модуль** — папка, которая может содержать корневой компонент, сегменты (`hooks/`, `types/`, `styles/`, `ui/`, `parts/` и т.д.) и публичный API (`index.ts`).
|
||
|
||
```text
|
||
auth/
|
||
├── ui/
|
||
│ ├── auth-guard.tsx
|
||
│ └── logout-button.tsx
|
||
├── parts/
|
||
│ ├── login-form/
|
||
│ ├── registration-form/
|
||
│ └── restore-form/
|
||
├── hooks/
|
||
├── stores/
|
||
├── types/
|
||
├── auth.tsx # корневой компонент (опционален)
|
||
└── index.ts
|
||
```
|
||
|
||
### Структура
|
||
|
||
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного `index.ts` с реэкспортом типов.
|
||
|
||
```text
|
||
{module-name}/
|
||
├── {module-name}.tsx # корневой компонент (опционален)
|
||
├── ui/ # компоненты модуля (только .tsx)
|
||
├── parts/ # вложенные модули (со своими сегментами)
|
||
├── hooks/ # хуки
|
||
├── stores/ # сторы состояния
|
||
├── services/ # внешние источники данных
|
||
├── mappers/ # трансформация данных между форматами
|
||
├── types/ # типы
|
||
├── styles/ # стили
|
||
├── lib/ # утилиты модуля
|
||
├── config/ # константы
|
||
└── index.ts # публичный API
|
||
```
|
||
|
||
Подробное описание каждого сегмента — в разделе [Сегменты](/basics/architecture/reference/segments).
|
||
|
||
### Публичный API
|
||
|
||
Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее.
|
||
|
||
```ts
|
||
// business/auth/index.ts
|
||
export type { User, Session } from './types/user.types'
|
||
export { useAuth } from './hooks/use-auth.hook'
|
||
export { AuthGuard } from './ui/auth-guard'
|
||
```
|
||
|
||
Импорт в обход `index.ts` запрещён:
|
||
|
||
```ts
|
||
// Плохо
|
||
import { validateToken } from '@/business/auth/lib/tokens'
|
||
|
||
// Хорошо
|
||
import { useAuth } from '@/business/auth'
|
||
```
|
||
|
||
### Фабрика
|
||
|
||
Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове.
|
||
|
||
Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — `import type` не является runtime-зависимостью.
|
||
|
||
#### Модуль без зависимостей — прямой экспорт:
|
||
|
||
```ts
|
||
// business/auth/index.ts
|
||
export { useAuth } from './hooks/use-auth'
|
||
export { useCurrentUser } from './hooks/use-current-user'
|
||
export type { User, Session } from './types'
|
||
```
|
||
|
||
#### Модуль с зависимостями — фабрика:
|
||
|
||
```ts
|
||
// business/chat/types/deps.ts
|
||
import type { User } from '@/business/auth'
|
||
|
||
export interface ChatDeps {
|
||
useCurrentUser: () => User | null
|
||
}
|
||
```
|
||
|
||
```ts
|
||
// business/chat/index.ts
|
||
import type { ChatDeps } from './types/deps'
|
||
|
||
export function chatFactory(deps: ChatDeps) {
|
||
return {
|
||
useMessages: (roomId: string) => {
|
||
const user = deps.useCurrentUser()
|
||
// ...
|
||
},
|
||
useSendMessage: (roomId: string) => {
|
||
const user = deps.useCurrentUser()
|
||
return (text: string) => { /* ... */ }
|
||
},
|
||
useChatRooms: () => {
|
||
const user = deps.useCurrentUser()
|
||
// ...
|
||
},
|
||
ChatBadge: ({ count }: { count: number }) => { /* ... */ },
|
||
}
|
||
}
|
||
|
||
export type { Message, ChatRoom } from './types'
|
||
export type { ChatDeps } from './types/deps'
|
||
```
|
||
|
||
#### Использование на странице:
|
||
|
||
```tsx
|
||
// screens/support/support.tsx
|
||
import { useCurrentUser } from '@/business/auth'
|
||
import { chatFactory } from '@/business/chat'
|
||
|
||
const chat = chatFactory({ useCurrentUser })
|
||
|
||
export function SupportScreen() {
|
||
const { useMessages, useSendMessage, ChatBadge } = chat
|
||
const messages = useMessages('support')
|
||
const sendMessage = useSendMessage('support')
|
||
|
||
return (
|
||
<div>
|
||
<ChatBadge count={messages.length} />
|
||
{messages.map(m => <MessageBubble key={m.id} {...m} />)}
|
||
<MessageInput onSend={sendMessage} />
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Жизненный цикл
|
||
|
||
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
|
||
|
||
- Нужен на одной странице → `screens/{name}/parts/`
|
||
- Появился в 2+ местах → поднимается по природе:
|
||
- абстрактный UI → `ui/`
|
||
- блок с данными/логикой → `widgets/`
|
||
- представление бизнес-домена → `business/{area}/parts/`
|
||
|
||
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
|
||
|
||
<!-- /basics/architecture/reference/segments -->
|
||
## Сегменты
|
||
|
||
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
|
||
|
||
### Определение
|
||
|
||
**Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.**
|
||
|
||
### Обзор
|
||
|
||
| Сегмент | Содержимое |
|
||
|---------|------------|
|
||
| `ui/` | Компоненты модуля — только `.tsx` файлы |
|
||
| `parts/` | Вложенные модули со своими сегментами |
|
||
| `hooks/` | React-хуки |
|
||
| `stores/` | Сторы состояния |
|
||
| `services/` | Работа с внешними источниками данных |
|
||
| `mappers/` | Трансформация данных между форматами |
|
||
| `types/` | TypeScript-типы и интерфейсы |
|
||
| `styles/` | Стили |
|
||
| `lib/` | Утилиты и хелперы модуля |
|
||
| `config/` | Константы и конфигурация |
|
||
|
||
### Сегмент ui/
|
||
|
||
Компоненты, принадлежащие модулю. Содержит только `.tsx` файлы — без своих сегментов, стилей, типов, хуков. Использует сегменты родительского модуля.
|
||
|
||
```text
|
||
auth/
|
||
├── ui/
|
||
│ ├── auth-provider.tsx
|
||
│ ├── auth-guard.tsx
|
||
│ └── logout-button.tsx
|
||
├── types/
|
||
├── hooks/
|
||
└── index.ts
|
||
```
|
||
|
||
Если компоненту нужны собственные сегменты — это уже не `ui/`, а `parts/`.
|
||
|
||
### Сегмент parts/
|
||
|
||
Вложенные модули со своими сегментами. Каждый элемент `parts/` — полноценный модуль: папка с компонентом, хуками, стилями, типами и т.д.
|
||
|
||
```text
|
||
home/
|
||
├── parts/
|
||
│ ├── hero-section/
|
||
│ │ ├── hero-section.tsx
|
||
│ │ ├── styles/
|
||
│ │ └── parts/
|
||
│ │ └── top-banner/
|
||
│ │ └── top-banner.tsx
|
||
│ └── features-section/
|
||
│ ├── features-section.tsx
|
||
│ └── hooks/
|
||
├── home.screen.tsx
|
||
└── index.ts
|
||
```
|
||
|
||
Отличие от `ui/`: элемент `parts/` — модуль со своими сегментами. Элемент `ui/` — компонент, один `.tsx` файл.
|
||
|
||
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
|
||
|
||
Если вложенный модуль обрастает своими `parts/` — это сигнал, что он достаточно самостоятельный для подъёма на уровень выше.
|
||
|
||
### Сегмент hooks/
|
||
|
||
React-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты.
|
||
|
||
```text
|
||
hooks/
|
||
├── use-auth.hook.ts
|
||
├── use-session.hook.ts
|
||
└── use-permissions.hook.ts
|
||
```
|
||
|
||
### Сегмент stores/
|
||
|
||
Сторы состояния модуля. Конкретная реализация зависит от выбранного стейт-менеджера (Zustand, MobX, Redux и т.д.).
|
||
|
||
```text
|
||
stores/
|
||
├── auth.store.ts
|
||
└── session.store.ts
|
||
```
|
||
|
||
### Сегмент services/
|
||
|
||
Работа с внешними источниками данных: API-вызовы, запросы, подписки.
|
||
|
||
```text
|
||
services/
|
||
├── auth.service.ts
|
||
└── token.service.ts
|
||
```
|
||
|
||
### Сегмент mappers/
|
||
|
||
Функции трансформации данных из одного формата в другой: DTO в доменный тип, доменный тип в DTO, доменный тип в ViewModel.
|
||
|
||
```text
|
||
mappers/
|
||
├── map-user.ts
|
||
├── map-product.ts
|
||
└── map-order-to-dto.ts
|
||
```
|
||
|
||
### Сегмент types/
|
||
|
||
TypeScript-типы и интерфейсы модуля. Доменные типы, DTO, пропсы компонентов.
|
||
|
||
```text
|
||
types/
|
||
├── user.type.ts
|
||
└── session.type.ts
|
||
```
|
||
|
||
### Сегмент styles/
|
||
|
||
Стили модуля. Формат зависит от выбранного подхода (CSS Modules, SCSS, CSS-in-JS и т.д.).
|
||
|
||
```text
|
||
styles/
|
||
├── auth.module.css
|
||
└── login-form.module.css
|
||
```
|
||
|
||
### Сегмент lib/
|
||
|
||
Утилиты и хелперы, специфичные для модуля. Чистые функции без побочных эффектов.
|
||
|
||
```text
|
||
lib/
|
||
├── validate-email.ts
|
||
└── format-phone.ts
|
||
```
|
||
|
||
Отличие от `shared/lib/`: здесь лежат утилиты, нужные только этому модулю. Общие утилиты — в `shared/lib/`.
|
||
|
||
### Сегмент config/
|
||
|
||
Константы и конфигурация модуля: маршруты, лимиты, дефолтные значения.
|
||
|
||
```text
|
||
config/
|
||
├── routes.ts
|
||
└── constants.ts
|
||
```
|
||
|
||
<!-- /basics/code-style -->
|
||
## Стиль кода
|
||
|
||
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
|
||
|
||
### Отступы
|
||
|
||
- 2 пробела (не табы).
|
||
|
||
### Длина строк
|
||
|
||
- Ориентироваться на 100 символов, но превышение допустимо, если строка читается легко.
|
||
- Переносить выражение на новые строки, когда строка становится плохо читаемой.
|
||
- Не переносить строку внутри строковых литералов без необходимости.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
const config = createRequestConfig(
|
||
endpoint,
|
||
{
|
||
headers: {
|
||
'X-Request-Id': requestId,
|
||
'X-User-Id': userId,
|
||
},
|
||
params: {
|
||
page,
|
||
pageSize,
|
||
sort: 'createdAt',
|
||
},
|
||
},
|
||
timeoutMs,
|
||
);
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: длинная строка с вложенными структурами плохо читается.
|
||
const config = createRequestConfig(endpoint, { headers: { 'X-Request-Id': requestId, 'X-User-Id': userId }, params: { page, pageSize, sort: 'createdAt' } }, timeoutMs);
|
||
```
|
||
|
||
### Кавычки
|
||
|
||
- В JavaScript/TypeScript использовать одинарные кавычки.
|
||
- В JSX/TSX для атрибутов использовать двойные кавычки.
|
||
- Шаблонные строки использовать только при интерполяции или многострочном тексте.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
const label = 'Сохранить';
|
||
const title = `Привет, ${name}`;
|
||
```
|
||
|
||
```tsx
|
||
<input type="text" placeholder="Введите имя" />
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: двойные кавычки в TS и конкатенация вместо шаблонной строки.
|
||
const label = "Сохранить";
|
||
const title = 'Привет, ' + name;
|
||
```
|
||
|
||
```tsx
|
||
// Плохо: одинарные кавычки в JSX-атрибутах.
|
||
<input type='text' placeholder='Введите имя' />
|
||
```
|
||
|
||
### Точки с запятой и запятые
|
||
|
||
- Допускаются упущения точки с запятой, если код остаётся читаемым и однозначным.
|
||
- В многострочных массивах, объектах и параметрах функции запятая в конце допускается, но не обязательна.
|
||
|
||
### Импорты
|
||
|
||
- В именованных импортах использовать пробелы внутри фигурных скобок.
|
||
- Типы импортировать через `import type`.
|
||
- `default` экспорт избегать, использовать именованные. `default` импорт допустим (например, стили CSS Modules, сторонние библиотеки).
|
||
- Избегать импорта всего модуля через `*`.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
import { MyComponent } from 'MyComponent';
|
||
import type { User } from '../model/types';
|
||
import styles from './styles/button.module.css';
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: отсутствие пробелов в именованном импорте.
|
||
import type {User} from '../model/types';
|
||
// Плохо: default экспорт.
|
||
export default MyComponent;
|
||
```
|
||
|
||
### Ранние возвраты (early return)
|
||
|
||
- Использовать ранние возвраты для упрощения чтения.
|
||
- Избегать `else` после `return`.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
const getName = (user?: { name: string }) => {
|
||
if (!user) {
|
||
return 'Гость';
|
||
}
|
||
|
||
return user.name;
|
||
};
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: лишний else после return усложняет чтение.
|
||
const getName = (user?: { name: string }) => {
|
||
if (user) {
|
||
return user.name;
|
||
} else {
|
||
return 'Гость';
|
||
}
|
||
};
|
||
```
|
||
|
||
### Форматирование объектов и массивов
|
||
|
||
- В многострочных объектах каждое свойство на новой строке.
|
||
- В многострочных массивах каждый элемент на новой строке.
|
||
- Объекты и массивы можно писать в одну строку, если длина строки не превышает 100 символов.
|
||
- В однострочных объектах и массивах использовать пробелы после запятых.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
const roles = ['admin', 'editor', 'viewer'];
|
||
const options = { id: 1, name: 'User' };
|
||
|
||
const config = {
|
||
url: '/api/users',
|
||
method: 'GET',
|
||
params: { page: 1, pageSize: 20 },
|
||
};
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: нет пробелов после запятых и объект слишком длинный для одной строки.
|
||
const roles = ['admin','editor','viewer'];
|
||
const options = { id: 1,name: 'User' };
|
||
const config = { url: '/api/users', method: 'GET', params: { page: 1, pageSize: 20 } };
|
||
```
|
||
|
||
<!-- /basics/documentation -->
|
||
## Документирование
|
||
|
||
Этот раздел описывает правила документирования кода: когда и как писать
|
||
комментарии к компонентам, функциям, типам и интерфейсам.
|
||
|
||
### Общие правила
|
||
|
||
- Документировать публичные функции, компоненты, типы, интерфейсы и enum.
|
||
- Не документировать очевидное — если название говорит само за себя, комментарий не нужен.
|
||
- Не документировать параметры, возвращаемые значения и типы пропсов — они видны из сигнатуры.
|
||
- Описание через пользу и назначение, а не через внутреннюю реализацию.
|
||
- Описание завершается точкой.
|
||
|
||
### Функции
|
||
|
||
Для документирования функций используется шаблон. Описание механики опционально —
|
||
добавляется когда логика нетривиальна.
|
||
|
||
**Шаблон**
|
||
```ts
|
||
/**
|
||
* <Что делает функция в 1 строке>.
|
||
*
|
||
* <Опционально: описание сложной механики или важных нюансов>.
|
||
*/
|
||
```
|
||
|
||
**Хорошо**
|
||
```ts
|
||
/**
|
||
* Форматирует цену с символом валюты.
|
||
*/
|
||
export const formatPrice = (value: number): string => { ... }
|
||
|
||
/**
|
||
* Рекурсивно собирает дерево категорий из плоского списка.
|
||
*
|
||
* Группирует элементы по parentId, начиная с корневых (parentId = null).
|
||
* Категории без родителя попадают в корень дерева.
|
||
*/
|
||
export const buildCategoryTree = (categories: Category[]): CategoryTree[] => { ... }
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: дублирует сигнатуру.
|
||
/**
|
||
* @param value - число
|
||
* @returns строка с ценой
|
||
*/
|
||
```
|
||
|
||
### Компоненты
|
||
|
||
Компонент описывает своё **назначение** и **сценарии применения** — это помогает понять, когда и где его использовать, без необходимости читать реализацию.
|
||
|
||
**Шаблон**
|
||
```ts
|
||
/**
|
||
* <Назначение компонента в 1 строке>.
|
||
*
|
||
* Используется для:
|
||
* - <сценарий 1>
|
||
* - <сценарий 2>
|
||
* - <сценарий 3>
|
||
*/
|
||
```
|
||
|
||
**Хорошо**
|
||
```tsx
|
||
/**
|
||
* Контейнер с адаптивной максимальной шириной.
|
||
*
|
||
* Используется для:
|
||
* - обёртки контента страниц с ограничением ширины
|
||
* - центрирования блоков в лейауте
|
||
*/
|
||
export const Container = (props: ContainerProps) => { ... }
|
||
```
|
||
|
||
**Плохо**
|
||
```tsx
|
||
// Плохо: описывает реализацию, а не назначение.
|
||
/**
|
||
* Рендерит div с className и htmlAttr.
|
||
*/
|
||
|
||
// Плохо: нет описания вообще.
|
||
export const Container = (props: ContainerProps) => { ... }
|
||
```
|
||
|
||
### Типы, интерфейсы, enum
|
||
|
||
Документируются назначение сущности и каждое её поле.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
/**
|
||
* Фильтры списка задач.
|
||
*/
|
||
export enum TodoFilter {
|
||
/** Все задачи. */
|
||
ALL = 'all',
|
||
/** Только активные. */
|
||
ACTIVE = 'active',
|
||
/** Только завершённые. */
|
||
COMPLETED = 'completed',
|
||
}
|
||
|
||
/**
|
||
* Задача пользователя.
|
||
*/
|
||
export interface TodoItem {
|
||
/** Уникальный идентификатор задачи. */
|
||
id: string;
|
||
/** Текст задачи. */
|
||
text: string;
|
||
/** Статус выполнения. */
|
||
completed: boolean;
|
||
}
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: описывает очевидное.
|
||
export interface TodoItem {
|
||
/** id — это id */
|
||
id: string;
|
||
}
|
||
```
|
||
|
||
<!-- /basics/typing -->
|
||
## Типизация
|
||
|
||
Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с `any`/`unknown`.
|
||
|
||
### Общие правила
|
||
|
||
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
|
||
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
|
||
- Избегать `any` и `unknown` без необходимости.
|
||
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
|
||
|
||
### Функции
|
||
|
||
- Для публичных функций указывать возвращаемый тип.
|
||
- Не полагаться на неявный вывод для важных API.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
export const formatPrice = (value: number): string => {
|
||
return `${value} ₽`;
|
||
};
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: нет явного возвращаемого типа.
|
||
export const formatPrice = (value: number) => {
|
||
return `${value} ₽`;
|
||
};
|
||
```
|
||
|
||
### Работа с any/unknown
|
||
|
||
- `any` использовать только для временных заглушек.
|
||
- `unknown` сужать через проверки перед использованием.
|
||
|
||
**Хорошо**
|
||
```ts
|
||
const parse = (value: unknown): string => {
|
||
if (typeof value === 'string') {
|
||
return value;
|
||
}
|
||
|
||
return '';
|
||
};
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: any отключает проверку типов.
|
||
const parse = (value: any) => value;
|
||
```
|
||
|
||
<!-- /applied/project-structure -->
|
||
## Структура проекта
|
||
|
||
Раздел описывает расположение файлов и папок в проекте Next.js (App Router).
|
||
|
||
### Корень репозитория
|
||
|
||
```text
|
||
project-root/
|
||
├── .templates/ # Шаблоны для генерации модулей
|
||
├── .vscode/ # Настройки и рекомендуемые расширения VS Code
|
||
├── public/ # Статика, доступная по прямому URL
|
||
├── src/ # Исходный код приложения
|
||
├── .env.example # Переменные окружения проекта (шаблон)
|
||
├── .env # Переменные окружения проекта (не коммитить)
|
||
├── .gitignore
|
||
├── AGENTS.md # Инструкции для AI-агентов
|
||
├── biome.json # Линтер и форматтер (вместо ESLint + Prettier)
|
||
├── next.config.ts # Конфигурация Next.js
|
||
├── package.json # Зависимости и скрипты
|
||
├── postcss.config.mjs # Конфигурация PostCSS
|
||
└── tsconfig.json # Конфигурация TypeScript
|
||
```
|
||
|
||
### Папка `public/`
|
||
|
||
Хранит статические файлы, которые отдаются по прямому URL без обработки сборщиком:
|
||
|
||
```text
|
||
public/
|
||
└── og-image.png
|
||
```
|
||
|
||
Компоненты, стили и другой исходный код здесь не размещаются.
|
||
|
||
### Папка `src/`
|
||
|
||
```text
|
||
src/
|
||
├── app/ # Роутинг Next.js, провайдеры, глобальные стили
|
||
├── layouts/ # Каркасы страниц (header, footer, sidebar)
|
||
├── screens/ # Контент конкретной страницы
|
||
├── widgets/ # Составные блоки интерфейса, не привязанные к домену
|
||
├── business/ # Бизнес-домены (auth, catalog, orders)
|
||
├── infrastructure/ # Техсервисы (theme, i18n, API-адаптеры)
|
||
├── ui/ # UI-кит без бизнес-логики
|
||
└── shared/ # Общие ресурсы (утилиты, типы, стили)
|
||
```
|
||
|
||
Принципы организации слоёв описаны в разделе [Архитектура](../basics/architecture/).
|
||
|
||
#### Папка `app/`
|
||
|
||
Точка входа приложения: инициализация (провайдеры, глобальные стили) и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты).
|
||
|
||
```text
|
||
src/app/
|
||
├── providers/ # Провайдеры приложения
|
||
├── styles/ # Глобальные стили
|
||
├── layout.tsx # Корневой layout
|
||
└── page.tsx # Главная страница
|
||
```
|
||
|
||
### Папка `.templates/`
|
||
|
||
Содержит шаблоны для генерации кода. Каждый подкаталог — шаблон отдельного типа модуля:
|
||
|
||
```text
|
||
.templates/
|
||
├── component/ # Шаблон компонента
|
||
├── screen/ # Шаблон экрана
|
||
├── layout/ # Шаблон layout
|
||
├── widget/ # Шаблон виджета
|
||
├── module/ # Шаблон бизнес-модуля
|
||
└── store/ # Шаблон стора
|
||
```
|
||
|
||
Подробнее о генерации описано в разделе [Шаблоны и генерация кода](./templates-generation).
|
||
|
||
### Конфигурационные файлы
|
||
|
||
| Файл | Назначение |
|
||
|---|---|
|
||
| `next.config.ts` | Настройки Next.js: редиректы, переменные окружения, webpack |
|
||
| `tsconfig.json` | Настройки TypeScript: пути, строгость, таргет |
|
||
| `biome.json` | Правила линтера и форматтера Biome |
|
||
| `postcss.config.mjs` | Подключение PostCSS-плагинов (CSS Modules, custom media) |
|
||
| `package.json` | Зависимости, версии, npm-скрипты |
|
||
| `AGENTS.md` | Инструкции для AI-агентов, работающих в проекте |
|
||
|
||
### Переменные окружения
|
||
|
||
- `.env` — переменные окружения проекта, запрещено коммитить
|
||
- `.env.example` — шаблон, коммитится в репозиторий
|
||
|
||
Переменные с префиксом `NEXT_PUBLIC_` доступны в клиентском коде. Остальные доступны только на сервере.
|
||
|
||
<!-- /applied/components -->
|
||
## Компоненты
|
||
|
||
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
|
||
|
||
Архитектурные слои и их назначение описаны в разделе [Архитектура](/basics/architecture/).
|
||
|
||
|
||
### Правила организации
|
||
|
||
1. Один компонент — один файл.
|
||
2. Компонент не содержит бизнес-логики — логика и сайд-эффекты выносятся в хуки или сторы.
|
||
3. Дочерние компоненты размещаются в сегменте `ui/` и подчиняются тем же правилам структуры.
|
||
4. Публичный API модуля — только `index.ts`. Прямые импорты внутренних файлов запрещены.
|
||
|
||
### Базовая структура компонента
|
||
|
||
Минимальный набор файлов: компонент, стили, типы и публичный экспорт.
|
||
|
||
```text
|
||
container/
|
||
├── styles/
|
||
│ └── container.module.css
|
||
├── types/
|
||
│ └── container.type.ts
|
||
├── container.tsx
|
||
└── index.ts
|
||
```
|
||
|
||
### Именования
|
||
|
||
- Имя корневого css класса всегда `.root`
|
||
- Тип пропсов именуется `{ComponentName}Props`.
|
||
- Тип пользовательских параметров именуется `{ComponentName}Params`.
|
||
|
||
### Типизация
|
||
|
||
Структура типов компонента показана в [примере](#пример). Ниже — обоснования ключевых решений.
|
||
|
||
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
|
||
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
|
||
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
|
||
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/basics/typing).
|
||
|
||
### Реализация
|
||
|
||
- Пропсы деструктурируются в теле компонента, не в параметрах.
|
||
- Порядок: пользовательские → системные (`children`, `className`) → `...htmlAttr`.
|
||
- `className` объединяется с корневым классом через `cl()`: `cl(styles.root, className)`.
|
||
- `...htmlAttr` прокидывается на корневой элемент.
|
||
|
||
### Пример
|
||
|
||
`container/types/container.type.ts`
|
||
|
||
```ts
|
||
import type { HTMLAttributes } from 'react'
|
||
|
||
/**
|
||
* Параметры компонента Container.
|
||
*/
|
||
export type ContainerParams = {}
|
||
|
||
/** HTML-атрибуты корневого элемента. */
|
||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||
|
||
export type ContainerProps = RootAttrs & ContainerParams
|
||
```
|
||
|
||
`container/styles/container.module.css`
|
||
|
||
```css
|
||
.root {
|
||
max-width: var(--content-width);
|
||
margin: 0 auto;
|
||
padding: 0 var(--spacing-4);
|
||
}
|
||
```
|
||
|
||
`container/container.tsx`
|
||
|
||
```tsx
|
||
import cl from 'clsx'
|
||
import type { ContainerProps } from './types/container.type'
|
||
import styles from './styles/container.module.css'
|
||
|
||
/**
|
||
* Контейнер с адаптивной максимальной шириной.
|
||
*
|
||
* Используется для:
|
||
* - обёртки контента страниц с ограничением ширины
|
||
* - центрирования блоков в лейауте
|
||
*/
|
||
export const Container = (props: ContainerProps) => {
|
||
const { children, className, ...htmlAttr } = props
|
||
|
||
return (
|
||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||
{children}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
`container/index.ts`
|
||
|
||
```ts
|
||
export { Container } from './container'
|
||
```
|
||
|
||
<!-- /applied/page-level -->
|
||
## Файлы роутинга
|
||
|
||
Правила для специальных файлов App Router (`page.tsx`, `layout.tsx`, `error.tsx`, `not-found.tsx` и др.) — чем наш подход отличается от дефолтного.
|
||
|
||
### Организация
|
||
|
||
- `page.tsx` — тонкий файл: только `metadata` и рендер экрана. Логика, стили и зависимости живут в экране, не в `page.tsx`.
|
||
- `error.tsx` и `not-found.tsx` делегируют разметку экранам по тому же принципу.
|
||
- `layout.tsx` — точка подключения провайдеров и глобальных стилей. Вёрстка layout-обёрток выносится в слой `layouts/`.
|
||
- Стили в файлах роутинга не используются — стилизация только внутри вызываемых компонентов.
|
||
|
||
### Реализация
|
||
|
||
- Каждый `page.tsx` экспортирует `metadata` с `title` — он подставляется в шаблон корневого layout (`%s | App`).
|
||
- Корневой `layout.tsx` задаёт `metadata` с `title.template`, `description`, `metadataBase` и OpenGraph-настройками.
|
||
|
||
### Примеры
|
||
|
||
`src/app/profile/[id]/page.tsx`
|
||
|
||
```tsx
|
||
import type { Metadata } from 'next'
|
||
import { ProfileScreen } from '@/screens/profile'
|
||
|
||
export const metadata: Metadata = {
|
||
title: 'Профиль',
|
||
description: 'Страница профиля пользователя',
|
||
}
|
||
|
||
type ProfilePageProps = {
|
||
params: Promise<{ id: string }>
|
||
}
|
||
|
||
export default async function ProfilePage({ params }: ProfilePageProps) {
|
||
const { id } = await params
|
||
|
||
return <ProfileScreen id={id} />
|
||
}
|
||
```
|
||
|
||
`src/app/error.tsx`
|
||
|
||
```tsx
|
||
'use client'
|
||
|
||
import { ErrorScreen } from '@/screens/error'
|
||
|
||
type ErrorPageProps = {
|
||
error: Error & { digest?: string }
|
||
reset: () => void
|
||
}
|
||
|
||
const ErrorPage = ({ error, reset }: ErrorPageProps) => {
|
||
return <ErrorScreen error={error} reset={reset} />
|
||
}
|
||
|
||
export default ErrorPage
|
||
```
|
||
|
||
<!-- /applied/templates-generation -->
|
||
<!-- @formatter:off -->
|
||
::: v-pre
|
||
|
||
## Шаблоны и генерация кода
|
||
|
||
Как работают шаблоны, как их создавать, синтаксис переменных и как генерировать код с помощью расширения VS Code и CLI.
|
||
|
||
### Структура шаблонов
|
||
|
||
Все шаблоны лежат в `.templates/` в корне проекта. Каждая папка — отдельный шаблон.
|
||
|
||
```text
|
||
.templates/
|
||
├── component/ # шаблон компонента
|
||
│ └── {{name.kebabCase}}/
|
||
│ ├── styles/
|
||
│ │ └── {{name.kebabCase}}.module.css
|
||
│ ├── types/
|
||
│ │ └── {{name.kebabCase}}.type.ts
|
||
│ ├── {{name.kebabCase}}.tsx
|
||
│ └── index.ts
|
||
└── store/ # шаблон Zustand стора
|
||
└── {{name.kebabCase}}/
|
||
├── {{name.kebabCase}}.store.ts
|
||
├── {{name.kebabCase}}.type.ts
|
||
└── index.ts
|
||
```
|
||
|
||
### Синтаксис шаблонов
|
||
|
||
Переменные работают в именах файлов/папок и внутри файлов. Базовая переменная — `name`.
|
||
|
||
```text
|
||
{{variable}}
|
||
```
|
||
|
||
Модификаторы меняют регистр и формат записи:
|
||
|
||
```text
|
||
{{name.pascalCase}} → MyButton
|
||
{{name.camelCase}} → myButton
|
||
{{name.kebabCase}} → my-button
|
||
{{name.snakeCase}} → my_button
|
||
{{name.screamingSnakeCase}} → MY_BUTTON
|
||
```
|
||
|
||
### Как создать новый шаблон
|
||
|
||
1. Создать папку в `.templates/` с именем шаблона (например `hook`).
|
||
2. Внутри разместить файлы и папки, используя `{{name}}` и модификаторы в именах и содержимом.
|
||
3. Шаблон сразу доступен и в расширении VS Code, и в CLI.
|
||
|
||
Пример — создание шаблона для хука:
|
||
|
||
```text
|
||
.templates/
|
||
└── hook/
|
||
└── {{name.kebabCase}}/
|
||
├── {{name.kebabCase}}.hook.ts
|
||
└── index.ts
|
||
```
|
||
|
||
```ts
|
||
// .templates/hook/{{name.kebabCase}}.hook.ts
|
||
export const {{name.camelCase}} = () => {
|
||
|
||
}
|
||
```
|
||
|
||
```ts
|
||
// .templates/hook/index.ts
|
||
export { {{name.camelCase}} } from './{{name.kebabCase}}.hook'
|
||
```
|
||
|
||
### Примеры шаблонов
|
||
|
||
#### Шаблон компонента
|
||
|
||
```ts
|
||
// .templates/component/index.ts
|
||
export { {{name.pascalCase}} } from './{{name.kebabCase}}'
|
||
```
|
||
|
||
```ts
|
||
// .templates/component/types/{{name.kebabCase}}.type.ts
|
||
import type { HTMLAttributes } from 'react'
|
||
|
||
/**
|
||
* Параметры {{name.pascalCase}}.
|
||
*/
|
||
export type {{name.pascalCase}}Params = {}
|
||
|
||
/** HTML-атрибуты корневого элемента. */
|
||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||
|
||
export type {{name.pascalCase}}Props = RootAttrs & {{name.pascalCase}}Params
|
||
```
|
||
|
||
```tsx
|
||
// .templates/component/{{name.kebabCase}}.tsx
|
||
import cl from 'clsx'
|
||
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
|
||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||
|
||
/**
|
||
* {{name.pascalCase}}.
|
||
*/
|
||
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
|
||
const { children, className, ...htmlAttr } = props
|
||
|
||
return (
|
||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||
{children}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
```css
|
||
/* .templates/component/styles/{{name.kebabCase}}.module.css */
|
||
.root {
|
||
|
||
}
|
||
```
|
||
|
||
### Генерация через VS Code
|
||
|
||
Template File Generator | gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
|
||
|
||
1. ПКМ на целевой папке в проводнике VS Code.
|
||
2. **Generate from template** → выбрать шаблон.
|
||
3. Ввести имя (например `button`) — расширение подставит его во все переменные `{{name}}`.
|
||
|
||
### Генерация через CLI
|
||
|
||
[@gromlab/create](https://www.npmjs.com/package/@gromlab/create) — CLI для генерации из тех же шаблонов. Используется через npx, глобальная установка не требуется.
|
||
|
||
```bash
|
||
npx @gromlab/create <шаблон> <имя> <путь>
|
||
```
|
||
|
||
| Команда | Что создаёт |
|
||
|---|---|
|
||
| `npx @gromlab/create component button src/shared/ui` | Компонент |
|
||
| `npx @gromlab/create module auth src/business` | Бизнес-модуль |
|
||
| `npx @gromlab/create widget header src/widgets` | Виджет |
|
||
| `npx @gromlab/create layout admin src/layouts` | Layout |
|
||
| `npx @gromlab/create store auth src/business/auth/stores` | Стор |
|
||
|
||
:::
|
||
|
||
<!-- /applied/styles -->
|
||
## Стили
|
||
|
||
Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование.
|
||
|
||
### Общие правила
|
||
|
||
- Только **PostCSS** и **CSS Modules** для кастомной стилизации.
|
||
- Подход **Mobile First** — стили пишутся от мобильных к десктопу.
|
||
- Именование классов — `camelCase` (`.root`, `.buttonNext`, `.itemTitle`).
|
||
- Модификаторы — отдельный класс с `_`, применяется через `&._modifier`.
|
||
|
||
**Хорошо**
|
||
```css
|
||
.submitButton {
|
||
padding: 8px 16px;
|
||
|
||
&._disabled {
|
||
opacity: 0.5;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Плохо**
|
||
```css
|
||
/* Плохо: kebab-case и вложенный элемент вместо отдельного класса. */
|
||
.submit-button {
|
||
padding: 8px 16px;
|
||
|
||
&__icon {
|
||
margin-right: 8px;
|
||
}
|
||
}
|
||
```
|
||
|
||
### Вложенность
|
||
|
||
- Вложенность селекторов запрещена.
|
||
- Исключения:
|
||
- Псевдоклассы: `&:hover`, `&:active`, `&:focus`, `&:disabled` и т.д.
|
||
- Псевдоэлементы: `&::before`, `&::after`.
|
||
- Медиа-запросы: `@media`.
|
||
- Модификаторы: `&._active`, `&._disabled`.
|
||
- Каждый вложенный блок отделяется пустой строкой от предыдущих свойств.
|
||
|
||
**Хорошо**
|
||
```css
|
||
.card {
|
||
padding: 16px;
|
||
background-color: var(--color-bg);
|
||
|
||
&:hover {
|
||
background-color: var(--color-bg-hover);
|
||
}
|
||
|
||
&::after {
|
||
content: '';
|
||
display: block;
|
||
}
|
||
|
||
&._highlighted {
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
@media (--md) {
|
||
padding: 24px;
|
||
}
|
||
}
|
||
|
||
.cardTitle {
|
||
font-size: 16px;
|
||
|
||
@media (--md) {
|
||
font-size: 20px;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Плохо**
|
||
```css
|
||
/* Плохо: вложенность селекторов, нет пустых строк между блоками. */
|
||
.card {
|
||
padding: 16px;
|
||
.cardTitle {
|
||
font-size: 16px;
|
||
}
|
||
&:hover {
|
||
background-color: var(--color-bg-hover);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Медиа-запросы
|
||
|
||
- Только **Custom Media Queries**: `@media (--md) {}`.
|
||
- Запрещены произвольные breakpoints: `@media (min-width: 768px)`.
|
||
- `@media` пишется только **внутри** селектора.
|
||
- Запрещено писать `@media` на верхнем уровне с селекторами внутри.
|
||
|
||
**Хорошо**
|
||
```css
|
||
.sidebar {
|
||
display: none;
|
||
|
||
@media (--md) {
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
.sidebarTitle {
|
||
font-size: 14px;
|
||
|
||
@media (--md) {
|
||
font-size: 18px;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Плохо**
|
||
```css
|
||
/* Плохо: @media на верхнем уровне с селекторами внутри. */
|
||
@media (--md) {
|
||
.sidebar {
|
||
display: block;
|
||
}
|
||
|
||
.sidebarTitle {
|
||
font-size: 18px;
|
||
}
|
||
}
|
||
|
||
/* Плохо: произвольный breakpoint вместо custom media. */
|
||
.sidebar {
|
||
@media (min-width: 992px) {
|
||
display: block;
|
||
}
|
||
}
|
||
```
|
||
|
||
### CSS-переменные
|
||
|
||
- Цвета (`--color-*`), отступы (`--space-*`), скругления (`--radius-*`) определяются в `app/styles/variables.css` через `:root`.
|
||
- Файл переменных подключается один раз в корневом layout/entry point — после этого переменные доступны глобально через каскад.
|
||
- Не дублировать магические значения в компонентах.
|
||
|
||
**Хорошо**
|
||
```css
|
||
/* app/styles/variables.css */
|
||
:root {
|
||
--color-primary: #3b82f6;
|
||
--color-bg: #ffffff;
|
||
--color-bg-hover: #f5f5f5;
|
||
--space-1: 4px;
|
||
--space-2: 8px;
|
||
--space-3: 12px;
|
||
--radius-1: 4px;
|
||
--radius-2: 8px;
|
||
}
|
||
```
|
||
|
||
```css
|
||
/* компонент */
|
||
.card {
|
||
padding: var(--space-3);
|
||
border-radius: var(--radius-2);
|
||
background-color: var(--color-bg);
|
||
}
|
||
```
|
||
|
||
**Плохо**
|
||
```css
|
||
/* Плохо: магические значения вместо переменных. */
|
||
.card {
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
background-color: #ffffff;
|
||
}
|
||
```
|
||
|
||
### Custom Media
|
||
|
||
- Breakpoints определяются через Custom Media Queries в `app/styles/media.css`.
|
||
- Custom media подключаются глобально через конфиг PostCSS (плагин `postcss-custom-media`) — не импортировать в файлы стилей.
|
||
|
||
```css
|
||
/* app/styles/media.css */
|
||
@custom-media --sm (min-width: 36em);
|
||
@custom-media --md (min-width: 62em);
|
||
@custom-media --lg (min-width: 82em);
|
||
```
|
||
|
||
### Импорт стилей
|
||
|
||
- Стили компонента импортируются только внутри своего компонента.
|
||
- Запрещено импортировать стили одного компонента в другой.
|
||
- Custom media не импортируются в файлы стилей — они подключаются глобально через конфиг PostCSS.
|
||
|
||
### Форматирование
|
||
|
||
- Пустая строка между селекторами верхнего уровня.
|
||
- Пустая строка перед каждым вложенным блоком (медиа, псевдокласс, модификатор).
|
||
|
||
**Хорошо**
|
||
```css
|
||
.userBar {
|
||
display: none;
|
||
color: var(--color-text);
|
||
|
||
@media (--md) {
|
||
display: flex;
|
||
}
|
||
}
|
||
|
||
.userBarButton {
|
||
background-color: var(--color-bg);
|
||
|
||
&:hover {
|
||
background-color: var(--color-bg-hover);
|
||
}
|
||
|
||
&._active {
|
||
background-color: var(--color-primary);
|
||
}
|
||
}
|
||
```
|
||
|
||
**Плохо**
|
||
```css
|
||
/* Плохо: нет пустых строк между селекторами и вложенными блоками. */
|
||
.userBar {
|
||
display: none;
|
||
color: var(--color-text);
|
||
@media (--md) {
|
||
display: flex;
|
||
}
|
||
}
|
||
.userBarButton {
|
||
background-color: var(--color-bg);
|
||
&:hover {
|
||
background-color: var(--color-bg-hover);
|
||
}
|
||
&._active {
|
||
background-color: var(--color-primary);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Единицы измерения
|
||
|
||
- `px` — основная единица измерения.
|
||
- Остальные (`em`, `rem`, `%`, `vh`/`vw`) — допускаются по необходимости дизайна.
|
||
|
||
### Порядок CSS-свойств
|
||
|
||
В стилях рекомендуется придерживаться логического порядка свойств:
|
||
|
||
1. Позиционирование (`position`, `top`, `left`, `z-index`).
|
||
2. Блочная модель (`display`, `width`, `height`, `margin`, `padding`).
|
||
3. Оформление (`background`, `border`, `box-shadow`, `border-radius`).
|
||
4. Текст (`font`, `color`, `text-align`, `line-height`).
|
||
5. Прочее (`transition`, `animation`, `opacity`, `cursor`).
|
||
|
||
### Комментарии
|
||
|
||
- Желательно не писать комментарии в CSS.
|
||
- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.
|
||
|
||
<!-- /applied/svg-sprites -->
|
||
## SVG-спрайты
|
||
|
||
<!-- /applied/vscode -->
|
||
## Настройка VS Code
|
||
|
||
Каждый проект содержит папку `.vscode/` с конфигурацией редактора. Это гарантирует, что все участники команды работают с одинаковыми настройками форматирования, линтинга и расширениями.
|
||
|
||
### Структура `.vscode/`
|
||
|
||
```text
|
||
.vscode/
|
||
├── extensions.json # Рекомендуемые расширения
|
||
└── settings.json # Настройки редактора для проекта
|
||
```
|
||
|
||
Оба файла коммитятся в репозиторий.
|
||
|
||
### Расширения
|
||
|
||
Файл `.vscode/extensions.json` определяет список расширений, которые VS Code предложит установить при открытии проекта.
|
||
|
||
```json
|
||
// .vscode/extensions.json
|
||
{
|
||
"recommendations": [
|
||
"biomejs.biome",
|
||
"MyTemplateGenerator.mytemplategenerator",
|
||
"csstools.postcss"
|
||
]
|
||
}
|
||
```
|
||
|
||
| Расширение | Назначение |
|
||
|---|---|
|
||
| [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) | Линтинг и форматирование кода. Заменяет ESLint и Prettier |
|
||
| Template File Generator \| gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) | Генерация файлов и папок из шаблонов `.templates/` через контекстное меню |
|
||
| [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss) | Подсветка синтаксиса и автодополнение для PostCSS (`@custom-media`, `@nest` и др.) |
|
||
|
||
#### Зачем это нужно
|
||
|
||
- Новый участник команды получает все нужные расширения одним кликом.
|
||
- Нет разночтений: все используют одинаковый форматтер и линтер.
|
||
- Расширения привязаны к проекту, а не к конкретному разработчику.
|
||
|
||
### Настройки редактора
|
||
|
||
Файл `.vscode/settings.json` переопределяет пользовательские настройки VS Code на уровне проекта.
|
||
|
||
```json
|
||
// .vscode/settings.json
|
||
{
|
||
"editor.defaultFormatter": "biomejs.biome",
|
||
"editor.formatOnSave": true,
|
||
"editor.codeActionsOnSave": {
|
||
"source.fixAll.biome": "explicit",
|
||
"source.organizeImports.biome": "explicit"
|
||
},
|
||
"files.associations": {
|
||
"*.css": "postcss"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Разбор настроек
|
||
|
||
| Настройка | Значение | Что делает |
|
||
|---|---|---|
|
||
| `editor.defaultFormatter` | `biomejs.biome` | Biome используется как единственный форматтер для всех файлов |
|
||
| `editor.formatOnSave` | `true` | Код автоматически форматируется при каждом сохранении |
|
||
| `codeActionsOnSave.source.fixAll.biome` | `explicit` | Biome автоматически применяет безопасные исправления при сохранении |
|
||
| `codeActionsOnSave.source.organizeImports.biome` | `explicit` | Импорты сортируются и группируются автоматически при сохранении |
|
||
| `files.associations` | `"*.css": "postcss"` | Все CSS-файлы открываются с подсветкой PostCSS вместо стандартного CSS |
|
||
|
||
#### Зачем это нужно
|
||
|
||
- **Единый стиль кода** -- форматирование происходит автоматически, невозможно закоммитить неформатированный код.
|
||
- **Автофикс при сохранении** -- распространённые ошибки линтинга исправляются без ручного вмешательства.
|
||
- **Сортировка импортов** -- импорты всегда в одном порядке, без конфликтов при мерже.
|
||
- **PostCSS-подсветка** -- кастомные at-правила (`@custom-media`, `@define-mixin`) подсвечиваются корректно, а не как ошибки.
|
||
|
||
### Что не должно быть в `.vscode/`
|
||
|
||
Не коммитятся файлы, специфичные для конкретного разработчика:
|
||
|
||
- **Не коммитить**: отладочные конфигурации с локальными путями, персональные сниппеты, настройки тем оформления.
|
||
- **Коммитить**: только `extensions.json` и `settings.json` с общими для команды настройками. |