# 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 Порядок действий при разработке — от создания проекта до реализации фич. ### Создание проекта Инициализация нового проекта из готового шаблона. 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. ## Технологии и библиотеки Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте. ### Что используем #### Стек - `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` — шаблонизатор для создания слоёв и других файлов из шаблонов. ## Именование Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту. ### Базовые правила | Что | Рекомендуется | | ---------------- | ---------------------- | | Папки | `kebab-case` | | Файлы | `kebab-case` | | Переменные | `camelCase` | | Константы | `SCREAMING_SNAKE_CASE` | | Классы | `PascalCase` | | React-компоненты | `PascalCase` | | Хуки | `useSomething` | | CSS классы | `camelCase` | | Ключи enum | `SCREAMING_SNAKE_CASE` | ### Именование файлов Суффикс обозначает роль или тип файла. Пишется в единственном числе. Формат: `name..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; const userIds = ['u1', 'u2']; const ordersMap = new Map(); const featureFlagsDict = { beta: true, legacy: false } as Record; ``` **Плохо** ```ts // Плохо: имя не отражает, что это коллекция. const user = []; // Плохо: словарь назван как массив. const usersMap = []; // Плохо: по имени непонятно, что это словарь. const users = {} as Record; ``` ## 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. - **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда. ## Слои Раздел описывает слои 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-состояния ## Модули Раздел описывает модули 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 (
{messages.map(m => )}
) } ``` ### Жизненный цикл Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности. - Нужен на одной странице → `screens/{name}/parts/` - Появился в 2+ местах → поднимается по природе: - абстрактный UI → `ui/` - блок с данными/логикой → `widgets/` - представление бизнес-домена → `business/{area}/parts/` Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность. ## Сегменты Раздел описывает сегменты 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 ``` ## Стиль кода Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость. ### Отступы - 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 ``` **Плохо** ```ts // Плохо: двойные кавычки в TS и конкатенация вместо шаблонной строки. const label = "Сохранить"; const title = 'Привет, ' + name; ``` ```tsx // Плохо: одинарные кавычки в JSX-атрибутах. ``` ### Точки с запятой и запятые - Допускаются упущения точки с запятой, если код остаётся читаемым и однозначным. - В многострочных массивах, объектах и параметрах функции запятая в конце допускается, но не обязательна. ### Импорты - В именованных импортах использовать пробелы внутри фигурных скобок. - Типы импортировать через `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 } }; ``` ## Документирование Этот раздел описывает правила документирования кода: когда и как писать комментарии к компонентам, функциям, типам и интерфейсам. ### Общие правила - Документировать публичные функции, компоненты, типы, интерфейсы и 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; } ``` ## Типизация Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с `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; ``` ## Структура проекта Раздел описывает расположение файлов и папок в проекте 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_` доступны в клиентском коде. Остальные доступны только на сервере. ## Компоненты Правила написания 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 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 (
{children}
) } ``` `container/index.ts` ```ts export { Container } from './container' ``` ## Файлы роутинга Правила для специальных файлов 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 } ``` `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 } export default ErrorPage ``` ::: 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 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 (
{children}
) } ``` ```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` | Стор | ::: ## Стили Раздел описывает правила написания 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. - Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение. ## SVG-спрайты ## Настройка 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` с общими для команды настройками.