Compare commits
39 Commits
4ae6ac893d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| bf1781f143 | |||
| e835210d6d | |||
| f4e78e3227 | |||
| c8d52e9128 | |||
| 9f1bc0cc32 | |||
| a6cd14585b | |||
| 1195c7b75d | |||
| 028a69f3ac | |||
| a9ea898220 | |||
| 7965ec2a96 | |||
| 7c224fed99 | |||
| 74cbd43a23 | |||
| ef58a02609 | |||
| e265799c26 | |||
| 781efc52f1 | |||
| ec01ae2e1c | |||
| 3d93efd90a | |||
| e5e4ace91a | |||
| 5a773a5b4f | |||
| f645b2ad40 | |||
| 90bf360c06 | |||
| 5cf0f0f8ba | |||
| 464c709859 | |||
| 64db18917b | |||
| ae103e962e | |||
| 99c0995cb6 | |||
| d621e6b57d | |||
| 787223010f | |||
| f5732904f4 | |||
| 36304c14f0 | |||
| 436c87a986 | |||
| be3d86f198 | |||
| 073fc6507f | |||
| cba311d78e | |||
| 11f9b702e0 | |||
| f7d3506a91 | |||
| be8e89fccd | |||
| fbac3e1a55 | |||
| 6ccc4a0d06 |
@@ -20,6 +20,9 @@ jobs:
|
|||||||
echo "DOCKER_REGISTRY=$DOCKER_REGISTRY" >> $GITHUB_ENV
|
echo "DOCKER_REGISTRY=$DOCKER_REGISTRY" >> $GITHUB_ENV
|
||||||
REGISTRY_IMAGE="$DOCKER_REGISTRY/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
|
REGISTRY_IMAGE="$DOCKER_REGISTRY/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
|
||||||
echo "REGISTRY_IMAGE=$REGISTRY_IMAGE" >> $GITHUB_ENV
|
echo "REGISTRY_IMAGE=$REGISTRY_IMAGE" >> $GITHUB_ENV
|
||||||
|
# Версия сборки: тег если есть, иначе короткий SHA
|
||||||
|
BUILD_VERSION=$(git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD)
|
||||||
|
echo "BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Login to Container Registry
|
- name: Login to Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -47,6 +50,8 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
BUILD_VERSION=${{ env.BUILD_VERSION }}
|
||||||
provenance: false
|
provenance: false
|
||||||
sbom: false
|
sbom: false
|
||||||
|
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -135,3 +135,7 @@ dist
|
|||||||
.vitepress/cache
|
.vitepress/cache
|
||||||
.vitepress/dist
|
.vitepress/dist
|
||||||
docs/.vitepress
|
docs/.vitepress
|
||||||
|
|
||||||
|
# Генерируется через `npm run llms`
|
||||||
|
docs/public/
|
||||||
|
generated/
|
||||||
@@ -1,112 +1,189 @@
|
|||||||
import { defineConfig } from 'vitepress';
|
import { defineConfig } from 'vitepress';
|
||||||
|
|
||||||
const ruSidebar = [
|
const sidebar = [
|
||||||
{
|
{
|
||||||
text: 'Workflow',
|
text: 'Главная',
|
||||||
link: '/workflow',
|
link: '/docs/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Подсказки',
|
||||||
|
link: '/docs/workflow',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Базовые правила',
|
text: 'Базовые правила',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Технологии и библиотеки', link: '/basics/tech-stack' },
|
{ text: 'Технологии и библиотеки', link: '/docs/basics/tech-stack' },
|
||||||
{ text: 'Именование', link: '/basics/naming' },
|
{ text: 'Именование', link: '/docs/basics/naming' },
|
||||||
{ text: 'Архитектура', link: '/basics/architecture' },
|
{
|
||||||
{ text: 'Стиль кода', link: '/basics/code-style' },
|
text: 'Архитектура',
|
||||||
{ text: 'Документирование', link: '/basics/documentation' },
|
collapsed: true,
|
||||||
{ text: 'Типизация', link: '/basics/typing' },
|
items: [
|
||||||
|
{ text: 'Обзор', link: '/docs/basics/architecture/' },
|
||||||
|
{ text: 'Слои', link: '/docs/basics/architecture/layers' },
|
||||||
|
{ text: 'Модули', link: '/docs/basics/architecture/modules' },
|
||||||
|
{ text: 'Сегменты', link: '/docs/basics/architecture/segments' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ text: 'Стиль кода', link: '/docs/basics/code-style' },
|
||||||
|
{ text: 'Документирование', link: '/docs/basics/documentation' },
|
||||||
|
{ text: 'Типизация', link: '/docs/basics/typing' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Создание проекта',
|
||||||
|
items: [
|
||||||
|
{ text: 'Из шаблона', link: '/docs/creating-project/from-template' },
|
||||||
|
{ text: 'По гайду вручную', link: '/docs/creating-project/manual' },
|
||||||
|
{ text: 'Чистый Next.js', link: '/docs/creating-project/nextjs' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Работа с данными',
|
||||||
|
// collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Введение', link: '/docs/data/' },
|
||||||
|
{
|
||||||
|
text: 'REST',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Обзор', link: '/docs/data/rest/' },
|
||||||
|
{
|
||||||
|
text: 'Создание клиента',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Обзор', link: '/docs/data/rest/clients/' },
|
||||||
|
{ text: 'Автогенерация из OpenAPI', link: '/docs/data/rest/clients/auto' },
|
||||||
|
{ text: 'Ручное создание', link: '/docs/data/rest/clients/manual' },
|
||||||
|
{ text: 'GET-хуки REST-клиента', link: '/docs/data/rest/clients/hooks' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Использование',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Стратегии получения данных', link: '/docs/data/rest/strategies/' },
|
||||||
|
{ text: 'Серверный await', link: '/docs/data/rest/strategies/server-await' },
|
||||||
|
{ text: 'Параллельные серверные запросы', link: '/docs/data/rest/strategies/parallel-server-requests' },
|
||||||
|
{ text: 'Передача промиса ниже', link: '/docs/data/rest/strategies/pass-promise-down' },
|
||||||
|
{ text: 'Начальные данные для клиентских хуков', link: '/docs/data/rest/strategies/client-hooks-initial-data' },
|
||||||
|
{ text: 'Клиентский GET-хук', link: '/docs/data/rest/strategies/client-get-hook' },
|
||||||
|
{ text: 'Business-композиция', link: '/docs/data/rest/strategies/business-composition' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ text: 'Realtime', link: '/docs/data/realtime' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Прикладные разделы',
|
text: 'Прикладные разделы',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Структура проекта', link: '/applied/project-structure' },
|
{ text: 'Структура проекта', link: '/docs/applied/project-structure' },
|
||||||
{ text: 'Компоненты', link: '/applied/components' },
|
{ text: 'Страницы', link: '/docs/applied/page-level' },
|
||||||
{ text: 'Страницы (App Router)', link: '/applied/page-level' },
|
{ text: 'Компонент', link: '/docs/applied/component' },
|
||||||
{ text: 'Шаблоны и генерация кода', link: '/applied/templates-generation' },
|
{ text: 'Модуль', link: '/docs/applied/module' },
|
||||||
{ text: 'Стили', link: '/applied/styles' },
|
{
|
||||||
{ text: 'Изображения', link: '/applied/images-sprites' },
|
text: 'Стили',
|
||||||
{ text: 'SVG-спрайты', link: '/applied/svg-sprites' },
|
collapsed: true,
|
||||||
{ text: 'Видео', link: '/applied/video' },
|
items: [
|
||||||
{ text: 'API', link: '/applied/api' },
|
{ text: 'Настройка', link: '/docs/applied/styles/styles-setup' },
|
||||||
{ text: 'Stores', link: '/applied/stores' },
|
{ text: 'Использование', link: '/docs/applied/styles/styles-usage' },
|
||||||
{ text: 'Хуки', link: '/applied/hooks' },
|
],
|
||||||
{ text: 'Шрифты', link: '/applied/fonts' },
|
},
|
||||||
{ text: 'Локализация', link: '/applied/localization' },
|
{
|
||||||
{ text: 'Настройка VS Code', link: '/applied/vscode' },
|
text: 'SVG-спрайты',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Введение', link: '/docs/applied/svg-sprites/svg-sprites-intro' },
|
||||||
|
{ text: 'Настройка', link: '/docs/applied/svg-sprites/svg-sprites-setup' },
|
||||||
|
{ text: 'Использование', link: '/docs/applied/svg-sprites/svg-sprites-usage' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ text: 'Изображения', link: '/docs/applied/images' },
|
||||||
|
{ text: 'Шрифты', link: '/docs/applied/fonts' },
|
||||||
|
{ text: 'Алиасы импортов', link: '/docs/applied/aliases' },
|
||||||
|
{
|
||||||
|
text: 'Шаблоны генерации',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Введение', link: '/docs/applied/templates/templates-intro' },
|
||||||
|
{ text: 'Настройка', link: '/docs/applied/templates/templates-setup' },
|
||||||
|
{ text: 'Создание шаблонов', link: '/docs/applied/templates/templates-create' },
|
||||||
|
{ text: 'Использование', link: '/docs/applied/templates/templates-usage' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ text: 'Biome', link: '/docs/applied/biome' },
|
||||||
|
{ text: 'PostCSS', link: '/docs/applied/postcss' },
|
||||||
|
{ text: 'VS Code', link: '/docs/applied/vscode' },
|
||||||
|
{ text: 'Локализация', link: '/docs/applied/localization' },
|
||||||
|
// Неактивные разделы: страницы существуют, но пока пустые.
|
||||||
|
// Оставляем в sidebar без `link`, чтобы видеть план, но без перехода.
|
||||||
|
{ text: 'Stores · в разработке' }
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const enSidebar = [
|
/**
|
||||||
{
|
* Vite-плагин: отдаёт `.txt` и `.md` с явной кодировкой UTF-8.
|
||||||
text: 'Processes',
|
* Без этого браузер декодирует как ISO-8859-1 и кириллица ломается.
|
||||||
items: [
|
*/
|
||||||
{ text: 'Getting Started', link: '/en/workflow/getting-started' },
|
const utf8TextPlugin = {
|
||||||
{ text: 'Creating an App', link: '/en/workflow/creating-app' },
|
name: 'utf8-text-files',
|
||||||
{ text: 'Creating Pages', link: '/en/workflow/creating-pages' },
|
configureServer(server: any) {
|
||||||
{ text: 'Creating Components', link: '/en/workflow/creating-components' },
|
server.middlewares.use((req: any, res: any, next: any) => {
|
||||||
{ text: 'Styling', link: '/en/workflow/styling' },
|
const url: string = req.url || '';
|
||||||
{ text: 'Data Fetching', link: '/en/workflow/data-fetching' },
|
if (url.endsWith('.txt') || url.endsWith('.md')) {
|
||||||
{ text: 'State Management', link: '/en/workflow/state-management' },
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||||
{ text: 'Localization', link: '/en/workflow/localization' },
|
}
|
||||||
],
|
next();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{
|
};
|
||||||
text: 'Basic Rules',
|
|
||||||
items: [
|
|
||||||
{ text: 'Tech Stack', link: '/en/basics/tech-stack' },
|
|
||||||
{ text: 'Architecture', link: '/en/basics/architecture' },
|
|
||||||
{ text: 'Code Style', link: '/en/basics/code-style' },
|
|
||||||
{ text: 'Naming', link: '/en/basics/naming' },
|
|
||||||
{ text: 'Documentation', link: '/en/basics/documentation' },
|
|
||||||
{ text: 'Typing', link: '/en/basics/typing' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Applied Sections',
|
|
||||||
items: [
|
|
||||||
{ text: 'VS Code Setup', link: '/en/applied/vscode' },
|
|
||||||
{ text: 'Project Structure', link: '/en/applied/project-structure' },
|
|
||||||
{ text: 'Components', link: '/en/applied/components' },
|
|
||||||
{ text: 'Page-level Components', link: '/en/applied/page-level' },
|
|
||||||
{ text: 'Templates & Code Generation', link: '/en/applied/templates-generation' },
|
|
||||||
{ text: 'Styles', link: '/en/applied/styles' },
|
|
||||||
{ text: 'Images', link: '/en/applied/images-sprites' },
|
|
||||||
{ text: 'SVG Sprites', link: '/en/applied/svg-sprites' },
|
|
||||||
{ text: 'Video', link: '/en/applied/video' },
|
|
||||||
{ text: 'API', link: '/en/applied/api' },
|
|
||||||
{ text: 'Stores', link: '/en/applied/stores' },
|
|
||||||
{ text: 'Hooks', link: '/en/applied/hooks' },
|
|
||||||
{ text: 'Fonts', link: '/en/applied/fonts' },
|
|
||||||
{ text: 'Localization', link: '/en/applied/localization' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
srcDir: 'docs',
|
srcDir: 'docs',
|
||||||
title: 'NextJS Style Guide',
|
// `docs/public/` содержит сгенерированные `.md`-копии и `llms.txt` для LLM
|
||||||
description: 'Правила и стандарты разработки на NextJS и TypeScript',
|
// (попадают в корень `dist/` как статика). Исключаем из сканирования
|
||||||
|
// страниц, иначе VitePress рендерит их как HTML-страницы.
|
||||||
rewrites: {
|
//
|
||||||
'ru/:rest*': ':rest*',
|
// `DEVELOP.md` и `MAP.md` — файлы архива (точка входа и карта).
|
||||||
},
|
// Содержат относительные ссылки, на сайте им делать нечего —
|
||||||
|
// эту роль выполняют сайдбар и `llms.txt`.
|
||||||
locales: {
|
srcExclude: ['public/**', '**/DEVELOP.md', '**/MAP.md'],
|
||||||
root: {
|
|
||||||
label: 'Русский',
|
|
||||||
lang: 'ru-RU',
|
lang: 'ru-RU',
|
||||||
|
title: 'NextJS Style Guide',
|
||||||
|
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
||||||
|
|
||||||
|
// Чистые URL без `.html` — канон для индексации.
|
||||||
|
// Серверная поддержка реализована в Caddyfile (try_files + редирект).
|
||||||
|
cleanUrls: true,
|
||||||
|
|
||||||
|
// Дублируем указатель на llms.txt в <head> — для агентов,
|
||||||
|
// которые читают HTML, но не парсят полный DOM/href.
|
||||||
|
head: [
|
||||||
|
['link', { rel: 'alternate', type: 'text/plain', href: '/llms.txt', title: 'llms.txt' }],
|
||||||
|
['link', { rel: 'alternate', type: 'text/plain', href: '/llms-full.txt', title: 'llms-full.txt' }],
|
||||||
|
],
|
||||||
|
|
||||||
|
vite: {
|
||||||
|
plugins: [utf8TextPlugin],
|
||||||
|
define: {
|
||||||
|
__BUILD_VERSION__: JSON.stringify(process.env.BUILD_VERSION || 'dev'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
sidebar: ruSidebar,
|
sidebar,
|
||||||
|
socialLinks: [
|
||||||
|
{ icon: 'github', link: 'https://gromlab.ru/docs/nextjs-style-guide' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
|
||||||
en: {
|
// Расширенный блок описания для llms.txt — даёт LLM полный
|
||||||
label: 'English',
|
// технический контекст: стек, методология, охват тем.
|
||||||
lang: 'en-US',
|
// Используется в generate-llms.ts.
|
||||||
link: '/en/',
|
llmsBlockquote:
|
||||||
themeConfig: {
|
'Стандарты разработки frontend-приложений на Next.js (App Router) + TypeScript + React с архитектурой SLM (Scoped Layered Module Design — модульная архитектура со слоями ответственности, где каждый модуль содержит всё необходимое: компоненты, хуки, сторы, типы, стили).',
|
||||||
sidebar: enSidebar,
|
llmsContext:
|
||||||
},
|
'Стек: React, TypeScript, Next.js App Router, Mantine UI, SWR, Zustand, i18next, PostCSS Modules, Vitest, clsx.\n\nДокументация покрывает архитектуру SLM (слои, модули, сегменты, направление зависимостей, публичный API), правила оформления кода (именование, форматирование, импорты, типизация, JSDoc), реализацию компонентов и хуков, работу с App Router, кодогенерацию из шаблонов, стилизацию (Mobile First, токены), работу с API и сокетами, управление состоянием через Zustand, локализацию, ассеты (шрифты, изображения, SVG-спрайты) и настройку VS Code.',
|
||||||
},
|
} as any);
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -15,3 +15,5 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
При работе с документацией следовать правилам из CONTRIBUTING.md.
|
При работе с документацией следовать правилам из CONTRIBUTING.md.
|
||||||
|
|
||||||
- Язык документации и коммитов — русский.
|
- Язык документации и коммитов — русский.
|
||||||
- После изменений в `.md`-файлах — запустить `npm run docs` для обновления RULES.md.
|
- После изменений в `.md`-файлах — запустить `npm run llms` для обновления `llms.txt` и README.
|
||||||
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`)
|
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`):
|
||||||
и порядок файлов (`concat-md.js`).
|
он же является источником порядка и группировки для `llms.txt`.
|
||||||
|
|||||||
295
CONTRIBUTING.md
295
CONTRIBUTING.md
@@ -7,9 +7,8 @@
|
|||||||
Документационный сайт с правилами и стандартами фронтенд-разработки на Next.js + TypeScript.
|
Документационный сайт с правилами и стандартами фронтенд-разработки на Next.js + TypeScript.
|
||||||
|
|
||||||
- Движок: VitePress
|
- Движок: VitePress
|
||||||
- Языки: русский (основной), английский
|
- Язык: русский
|
||||||
- Русская версия: `docs/ru/`
|
- Контент: `docs/docs/`
|
||||||
- Английская версия: `docs/en/`
|
|
||||||
|
|
||||||
## Команды
|
## Команды
|
||||||
|
|
||||||
@@ -17,81 +16,138 @@
|
|||||||
|---------|-----------|
|
|---------|-----------|
|
||||||
| `npm run dev` | Локальный сервер разработки |
|
| `npm run dev` | Локальный сервер разработки |
|
||||||
| `npm run build` | Сборка статического сайта |
|
| `npm run build` | Сборка статического сайта |
|
||||||
| `npm run docs` | Генерация `generated/{lang}/RULES.md` — единый файл для AI-ассистентов |
|
| `npm run llms` | Генерация `llms.txt` (карта документации для LLM) и README |
|
||||||
|
|
||||||
## Структура файлов
|
## Структура файлов
|
||||||
|
|
||||||
```
|
```
|
||||||
docs/
|
docs/
|
||||||
├── ru/ # Русская версия (основная)
|
├── index.md # Лендинг (URL `/`)
|
||||||
│ ├── index.md # Главная страница
|
└── docs/ # Контент документации (URL `/docs/...`)
|
||||||
│ ├── basics/ # Базовые правила
|
├── index.md # Главная страница
|
||||||
│ │ ├── tech-stack.md
|
├── workflow.md # Подсказки
|
||||||
│ │ ├── architecture.md
|
├── basics/ # Базовые правила: каким должен быть код
|
||||||
│ │ ├── code-style.md
|
│ ├── tech-stack.md
|
||||||
│ │ ├── naming.md
|
│ ├── architecture/
|
||||||
│ │ ├── documentation.md
|
│ ├── code-style.md
|
||||||
│ │ └── typing.md
|
│ ├── naming.md
|
||||||
│ └── applied/ # Прикладные разделы
|
│ ├── documentation.md
|
||||||
│ ├── vscode.md
|
│ └── typing.md
|
||||||
│ ├── project-structure.md
|
├── creating-project/ # Создание проекта: как поднять новый проект
|
||||||
│ ├── components.md
|
│ ├── from-template.md
|
||||||
│ ├── page-level.md
|
│ ├── manual.md
|
||||||
│ ├── templates-generation.md
|
│ └── nextjs.md
|
||||||
│ ├── styles.md
|
├── data/ # Работа с данными
|
||||||
│ ├── images-sprites.md
|
│ ├── index.md
|
||||||
│ ├── svg-sprites.md
|
│ ├── realtime.md
|
||||||
│ ├── video.md
|
│ └── rest/
|
||||||
│ ├── api.md
|
└── applied/ # Прикладные разделы: настройка и использование
|
||||||
│ ├── stores.md
|
├── project-structure.md
|
||||||
│ ├── hooks.md
|
├── page-level.md
|
||||||
│ ├── fonts.md
|
├── component.md
|
||||||
│ └── localization.md
|
├── module.md
|
||||||
├── en/ # Английская версия (зеркало ru/)
|
├── styles/ # Стили: настройка + использование
|
||||||
|
│ ├── styles-setup.md
|
||||||
|
│ └── styles-usage.md
|
||||||
|
├── svg-sprites/ # SVG-спрайты: введение + настройка + использование
|
||||||
|
│ ├── svg-sprites-intro.md
|
||||||
|
│ ├── svg-sprites-setup.md
|
||||||
|
│ └── svg-sprites-usage.md
|
||||||
|
├── images.md
|
||||||
|
├── fonts.md
|
||||||
|
├── aliases.md
|
||||||
|
├── templates/ # Шаблоны генерации: введение + настройка + использование
|
||||||
|
│ ├── templates-intro.md
|
||||||
|
│ ├── templates-setup.md
|
||||||
|
│ ├── templates-create.md
|
||||||
|
│ └── templates-usage.md
|
||||||
|
├── biome.md
|
||||||
|
├── postcss.md
|
||||||
|
├── vscode.md
|
||||||
|
├── localization.md
|
||||||
|
└── stores.md
|
||||||
.vitepress/
|
.vitepress/
|
||||||
├── config.ts # Конфигурация VitePress, сайдбары, локали
|
└── config.ts # Конфигурация VitePress, сайдбар
|
||||||
generated/
|
generate-llms.ts # Скрипт генерации llms.txt и README
|
||||||
├── ru/RULES.md # Сгенерированный единый файл (ru)
|
|
||||||
└── en/RULES.md # Сгенерированный единый файл (en)
|
|
||||||
concat-md.js # Скрипт генерации RULES.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Сгенерированные артефакты (`docs/public/`): `llms.txt`, `llms-full.txt`,
|
||||||
|
`nextjs-style-guide.zip`, `manifest.json`, копии `.md` в `docs/public/docs/`.
|
||||||
|
|
||||||
### Добавление нового раздела
|
### Добавление нового раздела
|
||||||
|
|
||||||
1. Создать `.md`-файл в нужной папке (`basics/` или `applied/`).
|
1. Создать `.md`-файл в нужной папке: `basics/`, `creating-project/`,
|
||||||
2. Добавить пункт в сайдбар — `.vitepress/config.ts` (оба языка, если есть перевод).
|
или `applied/`.
|
||||||
3. Добавить файл в массив `fileOrder` — `concat-md.js` (для генерации RULES.md).
|
2. Добавить пункт в сайдбар — `.vitepress/config.ts`.
|
||||||
|
Сайдбар — единственный источник порядка и группировки для `llms.txt`.
|
||||||
|
3. Запустить `npm run llms` для обновления `llms.txt` и README.
|
||||||
|
|
||||||
## Два типа документации
|
## Типы разделов
|
||||||
|
|
||||||
### Базовые правила
|
Документация разделена на четыре группы. Каждая отвечает на свой вопрос
|
||||||
|
и имеет свою природу — это влияет на содержимое и структуру страницы.
|
||||||
|
|
||||||
|
### Базовые правила (`basics/`)
|
||||||
|
|
||||||
**Отвечает на вопрос:** «Каким должен быть любой код?»
|
**Отвечает на вопрос:** «Каким должен быть любой код?»
|
||||||
|
|
||||||
Универсальные стандарты, **не привязанные к конкретной области**.
|
Универсальные стандарты, **не привязанные к конкретной области**.
|
||||||
Правило базовое, если оно применимо ко всему коду одинаково: именование переменных, оформление импортов, когда использовать `type` vs `interface`.
|
Правило базовое, если оно применимо ко всему коду одинаково: именование
|
||||||
|
переменных, оформление импортов, когда использовать `type` vs `interface`.
|
||||||
|
|
||||||
Примеры в базовых правилах допускаются, но служат иллюстрацией принципа, а не инструкцией по конкретной области.
|
Примеры в базовых правилах допускаются, но служат иллюстрацией принципа,
|
||||||
|
а не инструкцией по конкретной области.
|
||||||
|
|
||||||
**Граница:** если правило касается только одной области (только стили, только компоненты, только API) — оно живёт в прикладном разделе, не в базовых.
|
**Граница:** если правило касается только одной области (только стили,
|
||||||
|
только компоненты, только API) — оно живёт в прикладном разделе, не в базовых.
|
||||||
|
|
||||||
### Прикладные разделы
|
### Создание проекта (`creating-project/`)
|
||||||
|
|
||||||
**Отвечает на вопрос:** «Как работать с X?»
|
**Отвечает на вопрос:** «Как поднять новый проект?»
|
||||||
|
|
||||||
Полное описание конкретной области: структура файлов, правила, именование, типизация, примеры.
|
Сценарии запуска нового проекта целиком: из шаблона, вручную, чистая
|
||||||
|
установка фреймворка. Раздел описывает порядок шагов на уровне всего
|
||||||
|
проекта; детали отдельных инструментов лежат в `applied/`.
|
||||||
|
|
||||||
**Граница:** прикладной раздел не дублирует базовые правила.
|
**Граница:** не дублирует разделы `applied/`. Ссылается на них как на
|
||||||
Если правило уже описано в базовых — прикладной раздел ссылается на него, но не повторяет.
|
шаги в общем сценарии.
|
||||||
|
|
||||||
|
### Прикладные разделы (`applied/`)
|
||||||
|
|
||||||
|
**Отвечает на вопрос:** «Как поставить инструмент и как им пользоваться?»
|
||||||
|
|
||||||
|
Прикладные разделы объединяют настройку и использование инструментов
|
||||||
|
и подсистем. Каждый раздел — самостоятельная предметная область.
|
||||||
|
|
||||||
|
Разделы делятся на два типа:
|
||||||
|
|
||||||
|
1. **Только настройка** — разовая установка инструмента (линтер,
|
||||||
|
CSS-процессор, алиасы). Файл без суффикса: `biome.md`, `postcss.md`.
|
||||||
|
|
||||||
|
2. **Настройка + использование** — область, требующая и установки,
|
||||||
|
и повседневных правил. Два файла с суффиксами: `styles-setup.md`
|
||||||
|
(настройка) и `styles-usage.md` (использование). В сайдбаре
|
||||||
|
оборачиваются в collapsed-группу.
|
||||||
|
|
||||||
|
**Граница:** прикладной раздел не дублирует базовые правила. Если правило
|
||||||
|
уже описано в `basics/` — прикладной раздел ссылается на него, но не
|
||||||
|
повторяет.
|
||||||
|
|
||||||
## Структура прикладного раздела
|
## Структура прикладного раздела
|
||||||
|
|
||||||
Шаблон ниже описывает все допустимые секции. Раздел включает только те секции, которые для него релевантны — пустые секции не создаются.
|
Шаблон ниже относится к usage-страницам прикладных разделов (`applied/*-usage.md`).
|
||||||
|
Setup-страницы (`applied/*-setup.md`) и `creating-project/` имеют другую
|
||||||
|
структуру — ориентированную на пошаговую установку (требования → установка →
|
||||||
|
проверка).
|
||||||
|
|
||||||
|
Шаблон описывает все допустимые секции. Раздел включает только те,
|
||||||
|
которые для него релевантны — пустые секции не создаются.
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# {Название}
|
# {Название}
|
||||||
|
|
||||||
Краткое описание: о чём раздел и какие аспекты работы с областью он охватывает.
|
{Одно-два предложения, по которым читатель за секунду решает, нужен ли ему раздел.
|
||||||
|
Правила оформления — секция «Заголовок и описание» ниже.}
|
||||||
|
|
||||||
## Что нужно знать
|
## Что нужно знать
|
||||||
|
|
||||||
@@ -171,19 +227,152 @@ concat-md.js # Скрипт генерации RULES.md
|
|||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
title: Название раздела
|
title: Название раздела
|
||||||
|
description: Описание раздела одним предложением.
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
Значение `title` совпадает с текстом `h1`-заголовка в файле.
|
- `title` совпадает с текстом `h1`-заголовка в файле.
|
||||||
|
- `description` совпадает с абзацем-описанием сразу под `h1`.
|
||||||
|
|
||||||
### Заголовки
|
Подробнее о требованиях к самому заголовку и описанию — секция
|
||||||
|
«Заголовок и описание» ниже.
|
||||||
|
|
||||||
- Один `h1` на файл — совпадает с `title` из frontmatter.
|
### Заголовок и описание
|
||||||
- Сразу после `h1` — вводный абзац (одно-два предложения).
|
|
||||||
|
Каждая страница начинается с `h1`-заголовка и абзаца-описания сразу под ним.
|
||||||
|
Эта пара — **навигационный маркер**: попадает в сайдбар, `llms.txt`,
|
||||||
|
`MAP.md` архива и должна за секунду давать читателю или LLM понять,
|
||||||
|
**когда сюда нужно идти**.
|
||||||
|
|
||||||
|
#### Структура заголовков
|
||||||
|
|
||||||
|
- Один `h1` на файл, совпадает с `title` во frontmatter.
|
||||||
|
- Сразу после `h1` — описание (одно-два предложения, см. ниже).
|
||||||
- Основные секции — `h2`.
|
- Основные секции — `h2`.
|
||||||
- Подсекции внутри `h2` — `h3`.
|
- Подсекции внутри `h2` — `h3`.
|
||||||
- `h4` не используется.
|
- `h4` не используется.
|
||||||
|
|
||||||
|
#### Имя `h1` (заголовок страницы)
|
||||||
|
|
||||||
|
- Называет предметную область, а не реализацию.
|
||||||
|
- Без имён пакетов, опций, конфигов и путей.
|
||||||
|
- Самодостаточен — читается без контекста сайдбара.
|
||||||
|
- Исключение: имя инструмента допустимо, если оно — единственное
|
||||||
|
устойчивое имя самой области (`PostCSS`, `Biome`, `VS Code`).
|
||||||
|
- Если страница вложена в семантическую группу
|
||||||
|
(`Архитектура → Слои`, `Данные → REST → Серверные компоненты`)
|
||||||
|
и короткое имя теряет смысл при прямой ссылке — `h1` поднимает
|
||||||
|
имя родителя в заголовок: `Слои SLM`, `Сегменты SLM`. В сайдбаре
|
||||||
|
допустимо оставить короткий вариант (`Слои`, `Сегменты`) — там
|
||||||
|
путь группы виден через дерево.
|
||||||
|
- Подъём в заголовок применяется только когда читается грамматически
|
||||||
|
естественно (`Слои SLM`, `Автогенерация REST-клиента`). Если
|
||||||
|
получается натянутая конструкция (`REST в серверных компонентах`) —
|
||||||
|
заголовок остаётся коротким, а контекст полностью переносится
|
||||||
|
в описание. Заголовок и описание — пара: если один не несёт
|
||||||
|
контекст, его обязательно несёт второй.
|
||||||
|
|
||||||
|
**Хорошо:** «Алиасы импортов», «Структура проекта», «SVG-спрайты»,
|
||||||
|
«Слои SLM», «Автогенерация REST-клиента».
|
||||||
|
|
||||||
|
**Плохо:** «Установка и настройка» (что устанавливаем?),
|
||||||
|
«Использование» (что используем?), «Введение» (во что?),
|
||||||
|
«Сегменты» (чего сегменты?), «REST в серверных компонентах»
|
||||||
|
(грамматически натянуто — лучше короткий h1 + контекст в описании).
|
||||||
|
|
||||||
|
#### Описание
|
||||||
|
|
||||||
|
Описание — короткий ответ на вопрос «у меня задача X, мне сюда?».
|
||||||
|
Не аннотация. Не оглавление.
|
||||||
|
|
||||||
|
**Запреты:**
|
||||||
|
|
||||||
|
- Не упоминать конкретные пакеты, библиотеки, инструменты
|
||||||
|
(`@gromlab/svg-sprites`, `Mantine`, `Zustand`).
|
||||||
|
- Не упоминать конкретные файлы и пути
|
||||||
|
(`postcss.config.mjs`, `.templates/`, `biome.json`).
|
||||||
|
- Не упоминать конкретные опции, ключи API, имена функций
|
||||||
|
(`baseUrl`, `cl()`, `useStore`).
|
||||||
|
- Не начинать с «Раздел описывает», «Этот раздел»,
|
||||||
|
«В этом разделе», «Здесь рассмотрено».
|
||||||
|
- Не использовать дежурные префиксы как шаблон
|
||||||
|
(«Правила работы с...», «Правила написания...») — само то,
|
||||||
|
что раздел про правила, и так понятно из секции и заголовка.
|
||||||
|
- Не пересказывать содержимое перечислением подсекций
|
||||||
|
(«организация, реализация, делегирование, метаданные»).
|
||||||
|
- Не аргументировать пользу
|
||||||
|
(«обеспечит единообразие», «упростит поддержку»).
|
||||||
|
|
||||||
|
**Требования:**
|
||||||
|
|
||||||
|
- 1 предложение, обычно 5–12 слов.
|
||||||
|
- Звучит как ответ человека другу, а не как техспек.
|
||||||
|
- Описание читается **самостоятельно**, без контекста сайдбара.
|
||||||
|
- Если страница вложена в семантическую группу
|
||||||
|
(например, `Данные → REST → Клиенты → ...`) и её заголовок
|
||||||
|
без этой группы теряет смысл — описание явно содержит имя
|
||||||
|
родительской области, чтобы читалось без сайдбара.
|
||||||
|
|
||||||
|
**Подходящие формы:**
|
||||||
|
|
||||||
|
- «Как X.»
|
||||||
|
- «Что такое X.»
|
||||||
|
- «Из чего состоит X.»
|
||||||
|
- «Установка X.»
|
||||||
|
- «Какие X есть и как ими пользоваться.»
|
||||||
|
|
||||||
|
Перечисление аспектов через двоеточие — только если без него читатель
|
||||||
|
не сможет различить раздел от соседнего.
|
||||||
|
|
||||||
|
**Тест навигации.** Читатель видит описание — за секунду должен понять
|
||||||
|
«мне сюда» или «нет, не сюда». Если приходится перечитывать —
|
||||||
|
описание слишком длинное.
|
||||||
|
|
||||||
|
**Тест на изменение.** Если в разделе сменится пакет, переименуется
|
||||||
|
файл или добавится правило — придётся ли править описание?
|
||||||
|
Если да — оно слишком конкретное.
|
||||||
|
|
||||||
|
**Хорошо:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Какие алиасы импортов есть в проекте и как ими пользоваться.
|
||||||
|
```
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Установка и настройка линтера-форматтера в новом проекте.
|
||||||
|
```
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Из чего состоит проект и где что лежит.
|
||||||
|
```
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Получение REST-данных в серверных компонентах.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Плохо:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Раздел описывает, какие алиасы используются в проекте: их полный список,
|
||||||
|
где они объявлены и как ими пользоваться между модулями и внутри модуля.
|
||||||
|
```
|
||||||
|
|
||||||
|
_Начинается с «Раздел описывает», пересказывает содержимое._
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Установка и настройка CSS-процессора PostCSS в проекте: набор плагинов,
|
||||||
|
конфиг `postcss.config.mjs`. Выполняется один раз при заведении проекта.
|
||||||
|
```
|
||||||
|
|
||||||
|
_Упомянут конкретный файл, перечисление аспектов превратилось в оглавление._
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Правила работы с React-компонентами: файловая структура,
|
||||||
|
типизация пропсов, документирование, реализация.
|
||||||
|
```
|
||||||
|
|
||||||
|
_Дежурный префикс «Правила работы с...» плюс оглавление подсекций._
|
||||||
|
|
||||||
### Примеры кода
|
### Примеры кода
|
||||||
|
|
||||||
- Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `.
|
- Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `.
|
||||||
|
|||||||
43
Caddyfile
43
Caddyfile
@@ -1,5 +1,44 @@
|
|||||||
:8080 {
|
:8080 {
|
||||||
root * /srv
|
root * /srv
|
||||||
file_server
|
|
||||||
try_files {path} /index.html
|
# Устаревшие пути llms.txt в подпапках → корень.
|
||||||
|
# Без этого опечатка `/docs/llms.txt` уходит в SPA-фолбэк и
|
||||||
|
# отдаёт HTML под видом text/plain — агент верит, что получил llms.txt.
|
||||||
|
redir /docs/llms.txt /llms.txt 301
|
||||||
|
redir /docs/llms-full.txt /llms-full.txt 301
|
||||||
|
|
||||||
|
# Чистые URL: запросы вида `/docs/foo.html` редиректим на `/docs/foo`.
|
||||||
|
# Канон сайта — без `.html` (cleanUrls в VitePress).
|
||||||
|
# Не трогаем index.html в корне — он не имеет смысловой пары без расширения.
|
||||||
|
@legacyHtml {
|
||||||
|
path_regexp legacyHtml ^(/.+)\.html$
|
||||||
|
not path /index.html
|
||||||
|
}
|
||||||
|
redir @legacyHtml {re.legacyHtml.1} 301
|
||||||
|
|
||||||
|
# Подсказка агентам, где лежит карта документации (RFC 8288).
|
||||||
|
# Позволяет найти llms.txt без парсинга DOM — по HTTP-заголовку.
|
||||||
|
header Link "</llms.txt>; rel=\"llms\""
|
||||||
|
|
||||||
|
# Кириллица в .txt/.md ломается без явного charset.
|
||||||
|
# Применяем заголовок только к РЕАЛЬНО существующим файлам,
|
||||||
|
# иначе SPA-фолбэк (HTML) уезжает с Content-Type: text/plain.
|
||||||
|
@existingText {
|
||||||
|
path *.txt *.md
|
||||||
|
file
|
||||||
|
}
|
||||||
|
header @existingText Content-Type "text/plain; charset=utf-8"
|
||||||
|
|
||||||
|
# Несуществующие .txt/.md → 404, не HTML-фолбэк.
|
||||||
|
# Это критично для llms.txt: агент должен получить честный 404,
|
||||||
|
# а не валидный «как бы текст» с лендингом внутри.
|
||||||
|
@missingText {
|
||||||
|
path *.txt *.md
|
||||||
|
not file
|
||||||
|
}
|
||||||
|
respond @missingText 404
|
||||||
|
|
||||||
|
file_server
|
||||||
|
# cleanUrls: пробуем точное совпадение → +.html → каталог → SPA-фолбэк.
|
||||||
|
try_files {path} {path}.html {path}/ /index.html
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
FROM node:24-alpine AS build
|
FROM node:24-alpine AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
# zip нужен для упаковки nextjs-style-guide.zip
|
||||||
|
RUN apk add --no-cache zip
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
ARG BUILD_VERSION=dev
|
||||||
|
ENV BUILD_VERSION=${BUILD_VERSION}
|
||||||
|
RUN npm run llms && npm run build
|
||||||
|
|
||||||
FROM caddy:2-alpine
|
FROM caddy:2-alpine
|
||||||
COPY Caddyfile /etc/caddy/Caddyfile
|
COPY Caddyfile /etc/caddy/Caddyfile
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
# Ассистент
|
|
||||||
|
|
||||||
## Для ассистента
|
|
||||||
|
|
||||||
- Всегда используй Русский язык для общения и генерации документации/комментариев/коммитов.
|
|
||||||
- Всегда следуй этим правилам при генерации кода и ответах.
|
|
||||||
- Всегда пиши план действий перед генерацией кода.
|
|
||||||
- Всегда спрашивай разрешения у пользователя перед генерацией кода.
|
|
||||||
- Всегда проверяй, что код соответствует линтингу и форматированию.
|
|
||||||
- Всегда сверяйся с чек-листом при генерации кода.
|
|
||||||
- Не предлагай решения, которые противоречат этим правилам этого файла.
|
|
||||||
- Если не уверен — уточни у пользователя, не гадай, не придумывай.
|
|
||||||
|
|
||||||
## Обязательность чек-листов
|
|
||||||
|
|
||||||
- Все чек-листы, приведённые в правилах, обязательны к исполнению.
|
|
||||||
- Ассистент обязан сверяться с чек-листом при выполнении любой задачи, связанной с кодом.
|
|
||||||
- Нельзя сокращать, игнорировать или опускать пункты чек-листа — каждый пункт должен быть выполнен или явно отмечен как невыполнимый с объяснением причины.
|
|
||||||
- В каждом ответе, связанном с генерацией или изменением кода, ассистент обязан ссылаться на соответствующий чек-лист и подтверждать его выполнение.
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
---
|
|
||||||
title: Stores
|
|
||||||
---
|
|
||||||
|
|
||||||
# Stores
|
|
||||||
|
|
||||||
## Сторы (Stores)
|
|
||||||
|
|
||||||
> В этом разделе собраны основные правила и рекомендации по созданию и оформлению сторов. Следуйте этим принципам, чтобы обеспечить чистую архитектуру, удобство поддержки и единый стиль работы с состоянием в проекте.
|
|
||||||
> В проекте для организации состояния используется только библиотека Zustand.
|
|
||||||
|
|
||||||
### Структура
|
|
||||||
- Store размещается в файле `<store-name>.store.ts` в папке `stores/` на своём уровне абстракции согласно архитектуре проекта.
|
|
||||||
- Интерфейс состояния описывается в этом же файле с суффиксом `State` (PascalCase).
|
|
||||||
- Для каждого store создаётся отдельный хук доступа (например, `useTodoStore`).
|
|
||||||
- Для глобальных сторов используйте только `shared/store`.
|
|
||||||
|
|
||||||
### Именование
|
|
||||||
- Соблюдайте [правила именования файлов и папок](#правила-именования-файлов-и-папок):
|
|
||||||
- Файл store — `<store-name>.store.ts` (kebab-case).
|
|
||||||
- Имя интерфейса состояния — PascalCase с суффиксом `State`.
|
|
||||||
- Имя хука — camelCase с префиксом `use`.
|
|
||||||
|
|
||||||
### Требования
|
|
||||||
- В store допускается только хранение состояния и методы управления им, без бизнес-логики, асинхронных операций и side-effects (см. раздел "Правила организации и использовалья Store").
|
|
||||||
- Для методов, изменяющих состояние через set, если используется функция — тело функции в фигурных скобках, return с новой строки после стрелки.
|
|
||||||
- Не дублируйте логику между сторами.
|
|
||||||
|
|
||||||
### Типизация
|
|
||||||
- Всегда указывайте типы для всех полей состояния и методов.
|
|
||||||
- Не используйте неявное приведение типов и не полагайтесь на автоматический вывод, если это может снизить читаемость или безопасность.
|
|
||||||
|
|
||||||
### Документирование
|
|
||||||
- Документируйте только назначение store и смысл полей, строго по [правилам документирования кода](#правило-для-документирования-кода).
|
|
||||||
|
|
||||||
### Экспорт
|
|
||||||
- Экспортируйте хук доступа к store и интерфейс состояния через `index.ts` слоя/компонента.
|
|
||||||
|
|
||||||
### Примеры
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { create } from 'zustand';
|
|
||||||
import { TodoItem } from './types/todo-item.interface';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Состояние хранилища задач.
|
|
||||||
*/
|
|
||||||
export interface TodoStoreState {
|
|
||||||
/** Массив задач. */
|
|
||||||
items: TodoItem[];
|
|
||||||
/** Добавить задачу. */
|
|
||||||
addTodo: (item: TodoItem) => void;
|
|
||||||
/** Удалить задачу. */
|
|
||||||
removeTodo: (id: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Хук для доступа к хранилищу задач.
|
|
||||||
*/
|
|
||||||
export const useTodoStore = create<TodoStoreState>((set) => ({
|
|
||||||
items: [],
|
|
||||||
addTodo: (item) => set((state) => {
|
|
||||||
return {
|
|
||||||
items: [...state.items, item],
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
removeTodo: (id) => set((state) => {
|
|
||||||
return {
|
|
||||||
items: state.items.filter((t) => t.id !== id),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
### Чек-лист
|
|
||||||
|
|
||||||
- [ ] Store размещён в `stores/<store-name>.store.ts` на своём уровне абстракции согласно архитектуре проекта.
|
|
||||||
- [ ] Именование файлов и сущностей соответствует [правилам именования файлов и папок](#правила-именования-файлов-и-папок).
|
|
||||||
- [ ] Все поля и методы строго типизированы (см. [общие правила типизации](#общие-правила-типизации)).
|
|
||||||
- [ ] В store только состояние и методы управления им, без бизнес-логики и side-effects.
|
|
||||||
- [ ] Для методов, изменяющих состояние через set, используется функция с return с новой строки.
|
|
||||||
- [ ] Документировано только назначение store и смысл полей (см. [правила документирования кода](#правило-для-документирования-кода)).
|
|
||||||
- [ ] Нет неиспользуемого или невалидного кода.
|
|
||||||
- [ ] Экспорт через индексный файл.
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
---
|
|
||||||
title: CSS
|
|
||||||
---
|
|
||||||
|
|
||||||
# CSS
|
|
||||||
|
|
||||||
## Правила оформления и стилизации CSS-кода
|
|
||||||
|
|
||||||
- **Препроцессоры**
|
|
||||||
Используй PostCSS и модули для стилизации.
|
|
||||||
|
|
||||||
- **Архитектура написания стилей**
|
|
||||||
Используй подход **Mobile First**
|
|
||||||
Используй CSS переменные для стилизации.
|
|
||||||
Используй Custom Media Queries для адаптивных стилей.
|
|
||||||
Используй BEM для именования классов.
|
|
||||||
Между каждым CSS-правилом (селектором) должен быть один пустой сброс строки, пример:
|
|
||||||
```css
|
|
||||||
.todo-list {
|
|
||||||
max-width: 600px;
|
|
||||||
padding: var(--space-3);
|
|
||||||
|
|
||||||
@media (--md) {
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list__text {
|
|
||||||
font-size: 18px;
|
|
||||||
|
|
||||||
@media (--md) {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Запрещено писать правила подряд без пустой строки:
|
|
||||||
```css
|
|
||||||
/* Так делать нельзя! */
|
|
||||||
.todo-list { ... }
|
|
||||||
.todo-list__text { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Методология именования классов**
|
|
||||||
Использовать методологию **BEM** для именования классов.
|
|
||||||
- Блок: kebab-case, пример `.user-bar { }`
|
|
||||||
- Елемент: kebab-case, соеденный с блоком двойным нижним подчеркиванием, пример `.user-bar__slide { }`
|
|
||||||
- Модификатор: kebab-case, отдельный самостоятельный класс, **не соединяется** с блоком/елементом, имя модификатора всегда начинается с нижнего подчеркивания, пример: `._red { }`
|
|
||||||
|
|
||||||
- **Единицы измерения**
|
|
||||||
Используй `px` как основная единица измирения, так-же допускается использовать остальные единицы измерения если того требует реализуемый дизайн.
|
|
||||||
|
|
||||||
- **Импорт стилей**
|
|
||||||
Стили компонента должны импортироваться только внутри соответствующего компонента.
|
|
||||||
Запрещено импортировать стили одного компонента в другой.
|
|
||||||
Запрещено импортировать `css переменные` в файлы стилей, они доступны глобально.
|
|
||||||
Запрещено импортировать `custom media` в файлы стилей, они доступны глобально.
|
|
||||||
|
|
||||||
- **Переменные**
|
|
||||||
Все значения переменных нужно писать в `/shared/styles` или в Mantine ThemeProvider.
|
|
||||||
Все что не является цветами, брекпоинтами, отступами, скруглением допускаются использоваться в компонентах.
|
|
||||||
Обязательное создавай CSS перменные для:
|
|
||||||
- "Цветов", пример: `--color-danger: red;`.
|
|
||||||
- "Брекпоинты", описываем в (Сustom media) пример: `@custom-media --md (min-width: 62em);`.
|
|
||||||
- "Отспупы (--space)", , пример: `--space-1: 4px;`, `--space-2: 8px;`, `--space-3: 12px;` итд..
|
|
||||||
- "Скругление углов (--radius)", пример: `--radius-1: 4px;`,`--radius-2: 8px;`,`--radius-3: 12px;` итд..
|
|
||||||
|
|
||||||
- **Вложенность селекторов**
|
|
||||||
Запрещено использовать вложенность селекторов.
|
|
||||||
Разрешено использовать вложенность только для:
|
|
||||||
- Псевдо-классов `:hover`, `:active` итд..
|
|
||||||
- Псевдо-елементов `::before`, `::after`
|
|
||||||
- Медиа запросов `@media`
|
|
||||||
- Классы **модификаторы** по методологии BEM
|
|
||||||
Каждый вложенный селектор отделяется 1 пустой строкой.
|
|
||||||
|
|
||||||
- **Медиа запросы**
|
|
||||||
Строго запрещено использовать `@media` без вложения в селектор.
|
|
||||||
Строго запрещено использовать в теле `@media` любые селекторы.
|
|
||||||
Разрешено использовать только Custom Media Queries (например, `@media (--md) {}`).
|
|
||||||
Запрещено использовать любые произвольные значения breakpoints (например, max-width: 768px).
|
|
||||||
**Пример как правильно писать @media**
|
|
||||||
```css
|
|
||||||
.todo-list {
|
|
||||||
max-width: 600px;
|
|
||||||
padding: 24px;
|
|
||||||
|
|
||||||
@media (--md) {
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list__text {
|
|
||||||
font-size: 18px;
|
|
||||||
|
|
||||||
@media (--md) {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
**Пример как неправильно писать @media**
|
|
||||||
```css
|
|
||||||
// Медиа запрос не вложен в селектор
|
|
||||||
@media (--md) {
|
|
||||||
.todo-list {
|
|
||||||
max-width: 600px;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
.todo-list__text {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Используется стандартный `min-width: 992px` вмето Custom Media Queries
|
|
||||||
@media (min-width: 992px) {
|
|
||||||
// Внутри @media запроса используются селекторы
|
|
||||||
.todo-list {
|
|
||||||
max-width: 600px;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
.todo-list__text {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Глобальные стили и сбросы**
|
|
||||||
Все глобальные стили (например, сбросы) должны располагаться в отдельном файле, например, `src/app/styles/global.css`.
|
|
||||||
|
|
||||||
- **Использование Mantine и PostCSS**
|
|
||||||
Для стандартных визуальных компонентов (кнопки, инпуты, layout, grid, notifications и т.д.) использовать только Mantine и его ThemeProvider.
|
|
||||||
Запрещено использовать в Mantine компонентах его props/styling, вмето этого нужно добавлять кастомные стили PostCSS.
|
|
||||||
Кастомные стили допускаются только в случае, если требуемый дизайн невозможно реализовать средствами Mantine.
|
|
||||||
При написании кастомных стилей стараться использовать переменные и токены Mantine, если это возможно.
|
|
||||||
|
|
||||||
- **Порядок CSS-свойств**
|
|
||||||
В стилях рекомендуется придерживаться логического порядка свойств:
|
|
||||||
1. Позиционирование (position, top, left, z-index и т.д.)
|
|
||||||
2. Блочная модель (display, width, height, margin, padding и т.д.)
|
|
||||||
3. Оформление (background, border, box-shadow и т.д.)
|
|
||||||
4. Текст (font, color, text-align и т.д.)
|
|
||||||
5. Прочее (transition, animation и т.д.)
|
|
||||||
|
|
||||||
- **Комментарии**
|
|
||||||
В стилях запрещено использовать комментарии.
|
|
||||||
|
|
||||||
- **Дублирования**
|
|
||||||
Не дублировать стили между компонентами. Общие стили выносить в shared/styles или использовать переменные.
|
|
||||||
|
|
||||||
- **Примеры кода стилей**
|
|
||||||
Пример как хорошо:
|
|
||||||
```css
|
|
||||||
/* Блок BEM */
|
|
||||||
.user-bar {
|
|
||||||
display: none;
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
/* Медиа запрос custom media и отделяется 1 пустой строкой */
|
|
||||||
@media (--md) {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Елемент BEM отделяется 1 пустой строкой*/
|
|
||||||
.user-bar__button-next {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
|
|
||||||
/* Псевдо-класс отделяется 1 пустой строкой*/
|
|
||||||
&:hover {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Модификатор BEM отделяется 1 пустой строкой*/
|
|
||||||
&._blue {
|
|
||||||
background-color: #2b2bbe;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Модификатор BEM отделяется 1 пустой строкой*/
|
|
||||||
&._green {
|
|
||||||
background-color: #29c53d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Пример как плохо писать:
|
|
||||||
```css
|
|
||||||
.user-bar {
|
|
||||||
display: none;
|
|
||||||
color: black;
|
|
||||||
&__button {
|
|
||||||
&_next {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
&:hover {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
}
|
|
||||||
&._blue {
|
|
||||||
background-color: #2b2bbe;
|
|
||||||
}
|
|
||||||
&._green {
|
|
||||||
background-color: #29c53d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (min-width: 992px) {
|
|
||||||
.user-bar {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Чек лист для проверки стилизации.**
|
|
||||||
- [ ] Используется PostCSS и CSS-модули для стилизации.
|
|
||||||
- [ ] Применён подход Mobile First.
|
|
||||||
- [ ] Именование классов строго по BEM:
|
|
||||||
- [ ] Модификатор — отдельный класс, начинается с нижнего подчёркивания (например, `._red`, `._active`)
|
|
||||||
- [ ] Все CSS-переменные (цвета, брейкпоинты, отступы, скругления) определены только в `/shared/styles` или через Mantine ThemeProvider.
|
|
||||||
- [ ] Для медиа-запросов используются только custom media переменные из `/shared/styles/media.css`.
|
|
||||||
- [ ] Соблюдается правила вложености селекторов.
|
|
||||||
- [ ] Соблюдается правила отступов селекторов.
|
|
||||||
- [ ] Глобальные стили (reset) вынесены в отдельный файл, остальные стили — модульные.
|
|
||||||
- [ ] Для стандартных UI-элементов используются только компоненты Mantine, кастомные стили — только при необходимости.
|
|
||||||
- [ ] В Mantine-компонентах не используются props/styling для стилизации, только PostCSS.
|
|
||||||
- [ ] Кастомные стили используют переменные и токены Mantine, если это возможно.
|
|
||||||
- [ ] В стилях нет комментариев.
|
|
||||||
- [ ] Стили компонента импортируются только внутри соответствующего компонента.
|
|
||||||
- [ ] Нет импорта стилей одного компонента в другой.
|
|
||||||
- [ ] Нет импорта файлов переменных и custom media — они доступны глобально.
|
|
||||||
- [ ] Нет дублирования стилей между компонентами, общие стили вынесены в shared/styles или используются переменные.
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
---
|
|
||||||
title: Компоненты
|
|
||||||
---
|
|
||||||
|
|
||||||
# Компоненты
|
|
||||||
|
|
||||||
## Правила создания и работы с компонентами.
|
|
||||||
|
|
||||||
### 1. Структура компонента
|
|
||||||
Ассистент при создании/рефакторинге компонента должен **строго** придерживаться следующей структуры файлов и папок:
|
|
||||||
|
|
||||||
```
|
|
||||||
component-name/
|
|
||||||
index.ts
|
|
||||||
component-name.tsx
|
|
||||||
styles/
|
|
||||||
component-name.module.css
|
|
||||||
locales/
|
|
||||||
ru.json
|
|
||||||
en.json
|
|
||||||
types/
|
|
||||||
component-name.interface.ts
|
|
||||||
component-name.type.ts
|
|
||||||
component-name.enum.ts
|
|
||||||
schemas/
|
|
||||||
schema-name.schema.ts
|
|
||||||
utils/
|
|
||||||
util-name.util.ts
|
|
||||||
hooks/
|
|
||||||
use-hook-name.hook.ts
|
|
||||||
stores/
|
|
||||||
store-name.store.ts
|
|
||||||
ui/
|
|
||||||
... # вложенные компоненты для component-name
|
|
||||||
```
|
|
||||||
|
|
||||||
Пояснения к структуре компонента:
|
|
||||||
**Обязательные файлы** обязательны для всех компонентов, даже если они пустые.
|
|
||||||
- component-name/: Папка компонента корень для всего компонента.
|
|
||||||
- index.ts: экспортирует главный компонент, интерфейс и всё, что может быть переиспользовано.
|
|
||||||
- component-name.tsx: главный компонент.
|
|
||||||
- styles/component-name.module.css: стили компонента.
|
|
||||||
- locales/ru.json: локализация на русском языке.
|
|
||||||
- locales/en.json: локализация на английском языке.
|
|
||||||
- types/component-name.interface.ts: интерфейс пропсов компонента.
|
|
||||||
**Не обязательные файлы** добавляются только при необходимости
|
|
||||||
- types/component-name.type.ts: типы компонента.
|
|
||||||
- types/component-name.enum.ts: enum компонента.
|
|
||||||
- schemas/schema-name.schema.ts: схемы валидации.
|
|
||||||
- utils/util-name.util.ts: утилиты компонента.
|
|
||||||
- hooks/use-hook-name.hook.ts: хуки компонента.
|
|
||||||
- stores/store-name.store.ts: хранилища состояния компонента.
|
|
||||||
- ui/: Папка для вложенных компонентов.
|
|
||||||
|
|
||||||
### Требования к компоненту
|
|
||||||
- Использовать `memo()` для всех компонентов, которые принимают пропсы.
|
|
||||||
- Использовать `useMemo` для всех вычислений, которые передаются в пропсы других компонентов.
|
|
||||||
- Использовать `useCallback` для всех функций/методов, которые передаются в пропсы других компонентов.
|
|
||||||
|
|
||||||
### Требования к вложенным компонентам
|
|
||||||
- Вложенный компонент — это полноценный компонент, который обязан полностью соблюдать все правила, описанные для компонентов (структура, именование, документация, типизация, стилизация и т.д.).
|
|
||||||
- Все вложенные компоненты размещаются только в папке ui/ основного компонента.
|
|
||||||
|
|
||||||
**Пояснение**
|
|
||||||
Нет необходимости повторять структуру и требования — вложенный компонент подчиняется тем же правилам, что и любой другой компонент, только располагается в папке ui/ родительского компонента.
|
|
||||||
|
|
||||||
### Требования к локализации
|
|
||||||
- Все добавленные локализации обязательно подключать в экземпляр `app/i18n` (чтобы новые namespace были доступны для i18next).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Чек-лист для создания нового компонента
|
|
||||||
- [ ] Главный компонент размещён в корне и назван по правилу PascalCase.
|
|
||||||
- [ ] Создан файл стилей в папке `styles/`, имя в kebab-case, используется BEM.
|
|
||||||
- [ ] Все классы применяются через `className={styles['component-name']}`.
|
|
||||||
- [ ] Создана папка `locales/` с файлами `ru.json` и `en.json`.
|
|
||||||
- [ ] Создан файл интерфейса пропсов в папке `types/`, даже если интерфейс пустой.
|
|
||||||
- [ ] Создан файл `index.ts` с экспортом главного компонента и интерфейса.
|
|
||||||
- [ ] Внутренние компоненты (если есть) размещены в папке `ui/`.
|
|
||||||
- [ ] Все важные части кода документированы по TSDoc (см. раздел 16).
|
|
||||||
- [ ] Остальные файлы (schemas, дополнительные типы, enum) добавлены только при необходимости.
|
|
||||||
- [ ] Именование файлов и папок соответствует правилам (см. выше).
|
|
||||||
- [ ] Нет неиспользуемого или невалидного кода.
|
|
||||||
- [ ] Для компонентов с пропсами используется `React.memo`.
|
|
||||||
- [ ] Для вычислений, передаваемых в пропсы, используется `useMemo`.
|
|
||||||
- [ ] Для функций, передаваемых в пропсы, используется `useCallback`.
|
|
||||||
- [ ] Все тексты вынесены в локализационные файлы и используются через i18n.
|
|
||||||
- [ ] Новые namespace подключены в экземпляр i18n.
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Хуки (React Hooks)
|
|
||||||
|
|
||||||
> В проекте для создания пользовательских хуков используется только React (функциональные компоненты и хуки).
|
|
||||||
> В этом разделе собраны основные правила и рекомендации по созданию и оформлению хуков. Следуйте этим принципам, чтобы обеспечить чистую архитектуру, переиспользуемость и единый стиль работы с хуками в проекте.
|
|
||||||
|
|
||||||
## Рекомендации по использованию сторонних хуков
|
|
||||||
- Если есть возможность, используйте хуки Mantine в компонентах и кастомных хуках для работы с состоянием, темизацией, медиа-запросами и другими возможностями библиотеки.
|
|
||||||
- Не дублируйте функциональность, уже реализованную в Mantine.
|
|
||||||
|
|
||||||
## Структура
|
|
||||||
- Каждый хук размещается в отдельном файле с именем `use-<hook-name>.hook.ts` в папке `hooks/` на своём уровне абстракции согласно архитектуре проекта.
|
|
||||||
- Имя хука — в стиле camelCase с префиксом `use` (например, `useTodoFilter`).
|
|
||||||
- Для сложных возвращаемых структур использовать отдельные типы или интерфейсы, размещая их в папке `types/` на своём уровне абстракции.
|
|
||||||
|
|
||||||
## Именование
|
|
||||||
- Соблюдайте [правила именования файлов и папок](#правила-именования-файлов-и-папок):
|
|
||||||
- Файл хука — `use-<hook-name>.hook.ts` (kebab-case).
|
|
||||||
- Имя хука — camelCase с префиксом `use`.
|
|
||||||
|
|
||||||
## Требования
|
|
||||||
- Хук должен быть строго типизирован: все параметры, возвращаемые значения и внутренние переменные должны иметь явные типы.
|
|
||||||
- Не хранить бизнес-логику, связанную с несколькими слоями — хук должен быть изолирован в рамках своего слоя/feature.
|
|
||||||
- Не дублировать логику между хуками — общие части выносить в shared.
|
|
||||||
- Не использовать side-effects вне useEffect/useLayoutEffect.
|
|
||||||
- Для мемоизации возвращаемых значений и функций использовать useMemo и useCallback.
|
|
||||||
- Не использовать устаревшие или неразрешённые паттерны React.
|
|
||||||
|
|
||||||
## Типизация
|
|
||||||
- Всегда явно указывать типы для всех параметров, возвращаемых значений и состояния внутри хука.
|
|
||||||
- Не используйте неявное приведение типов и не полагайтесь на автоматический вывод, если это может снизить читаемость или безопасность.
|
|
||||||
|
|
||||||
## Документирование
|
|
||||||
- Документируйте только назначение хука (описание), строго по [правилам документирования кода](#правило-для-документирования-кода).
|
|
||||||
|
|
||||||
## Экспорт
|
|
||||||
- Экспортируйте хук только именованным экспортом через `index.ts` слоя/компонента.
|
|
||||||
|
|
||||||
## Примеры
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { TodoItem } from '../types/todo-item.interface';
|
|
||||||
import { TodoStatus } from '../types/todo-status.enum';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Хук фильтрации задач по статусу.
|
|
||||||
*/
|
|
||||||
export const useTodoFilter = (items: TodoItem[], filter: TodoStatus): TodoItem[] => {
|
|
||||||
return useMemo(() => {
|
|
||||||
if (filter === TodoStatus.ALL) return items;
|
|
||||||
if (filter === TodoStatus.ACTIVE) return items.filter((t) => !t.completed);
|
|
||||||
return items.filter((t) => t.completed);
|
|
||||||
}, [items, filter]);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Чек-лист
|
|
||||||
|
|
||||||
- [ ] Хук размещён в файле `use-<hook-name>.hook.ts` в папке `hooks/` на своём уровне абстракции согласно архитектуре проекта.
|
|
||||||
- [ ] Именование файлов и сущностей соответствует [правилам именования файлов и папок](#правила-именования-файлов-и-папок).
|
|
||||||
- [ ] Все параметры, возвращаемые значения и внутренние переменные строго типизированы.
|
|
||||||
- [ ] Вся бизнес-логика изолирована в рамках слоя/feature.
|
|
||||||
- [ ] Нет дублирования логики между хуками.
|
|
||||||
- [ ] Для мемоизации используется useMemo/useCallback.
|
|
||||||
- [ ] Не используются side-effects вне useEffect/useLayoutEffect.
|
|
||||||
- [ ] Документировано только назначение хука (см. [правила документирования кода](#правило-для-документирования-кода)).
|
|
||||||
- [ ] Нет неиспользуемого или невалидного кода.
|
|
||||||
- [ ] Экспорт только именованный через индексный файл.
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
# Хуки API (React Hooks)
|
|
||||||
|
|
||||||
> В проекте для работы с API-хуками используется только React и библиотека SWR для получения данных (GET-запросы).
|
|
||||||
> В этом разделе собраны основные правила и рекомендации по созданию и оформлению хуков для работы с API. Следуйте этим принципам, чтобы обеспечить чистую архитектуру, переиспользуемость и единый стиль работы с API-хуками в проекте.
|
|
||||||
|
|
||||||
## Описание и назначение API-хуков
|
|
||||||
|
|
||||||
API-хуки предназначены для получения данных с сервера (GET-запросы) и используются в компонентах или других хуках.
|
|
||||||
В проекте для этого применяется библиотека SWR, которая обеспечивает кэширование, автоматическое обновление и удобную работу с асинхронными запросами.
|
|
||||||
|
|
||||||
**Fetcher** — это функция, которую использует SWR для выполнения запроса к API. В проекте fetcher обычно экспортируется из файла клиента (например, `backendFetcher` из `shared/api/backend/client.ts`) и инкапсулирует логику обращения к конкретному API-клиенту.
|
|
||||||
|
|
||||||
**API-клиент** — это отдельный модуль (папка), отвечающий за взаимодействие с конкретным внешним или внутренним API.
|
|
||||||
API-клиент включает:
|
|
||||||
- инициализацию экземпляра HTTP-клиента (например, Axios),
|
|
||||||
- настройку базового URL, интерцепторов и общих обработчиков ошибок,
|
|
||||||
- организацию всех сущностей и методов для работы с этим API (например, users, auth, orders и т.д.),
|
|
||||||
- экспорт всех функций, типов и fetcher через индексные файлы.
|
|
||||||
|
|
||||||
Каждый API-клиент размещается в папке `src/shared/api/<client-name>/` и имеет собственную структуру согласно архитектуре проекта.
|
|
||||||
|
|
||||||
## Структура
|
|
||||||
- Каждый API-хук размещается в отдельном файле с именем `use-<method-name>.hook-api.ts` в папке `hooks/api/<client-name>/` на своём уровне абстракции согласно архитектуре проекта.
|
|
||||||
- Имя хука — в стиле camelCase с префиксом `use` (например, `useGetUser`).
|
|
||||||
- Для сложных возвращаемых структур используйте отдельные типы или интерфейсы, размещая их в папке `types/` на своём уровне абстракции.
|
|
||||||
|
|
||||||
## Именование
|
|
||||||
- Соблюдайте [правила именования файлов и папок](#правила-именования-файлов-и-папок):
|
|
||||||
- Файл хука — `use-<method-name>.hook-api.ts` (kebab-case).
|
|
||||||
- Имя хука — camelCase с префиксом `use`.
|
|
||||||
|
|
||||||
## Требования
|
|
||||||
- Хук должен быть строго типизирован: все параметры, возвращаемые значения и внутренние переменные должны иметь явные типы.
|
|
||||||
- Для получения данных используйте только SWR.
|
|
||||||
- Не дублируйте логику между хуками — общие части выносите в shared.
|
|
||||||
- Не используйте side-effects вне useEffect/useLayoutEffect.
|
|
||||||
|
|
||||||
## Типизация
|
|
||||||
- Всегда явно указывайте типы для всех параметров, возвращаемых значений и состояния внутри хука.
|
|
||||||
- Придерживайтесь [общих правил типизации проекта](#общие-правила-типизации).
|
|
||||||
- Не используйте неявное приведение типов и не полагайтесь на автоматический вывод, если это может снизить читаемость или безопасность.
|
|
||||||
|
|
||||||
## Документирование
|
|
||||||
- Документируйте только назначение хука (описание), строго по [правилам документирования кода](#правило-для-документирования-кода).
|
|
||||||
|
|
||||||
## Экспорт
|
|
||||||
- Экспортируйте хук только именованным экспортом через `index.ts` слоя/компонента.
|
|
||||||
|
|
||||||
## Пример API хука
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// use-get-me.hook-api.ts
|
|
||||||
import useSWR from 'swr';
|
|
||||||
import { backendFetcher } from 'shared/api/backend/client';
|
|
||||||
import { UserDto } from 'shared/api/backend/entities/users/get-me.api';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Хук для получения информации о текущем пользователе.
|
|
||||||
*/
|
|
||||||
export const useGetMe = () => {
|
|
||||||
const { data, error, isLoading } = useSWR<UserDto>('/users/me', backendFetcher);
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
error,
|
|
||||||
isLoading,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Пример использования хука в компоненте
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import React from 'react';
|
|
||||||
import { useGetMe } from 'shared/hooks/api/backend/use-get-me.hook-api';
|
|
||||||
|
|
||||||
export const UserInfo: React.FC = () => {
|
|
||||||
const { data, error, isLoading } = useGetMe();
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <div>Загрузка...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <div>Ошибка загрузки данных</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return <div>Нет данных о пользователе</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>Имя: {data.name}</div>
|
|
||||||
<div>Email: {data.email}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Чек-лист для создания API-хука
|
|
||||||
|
|
||||||
- [ ] Для каждого GET-запроса создан отдельный хук.
|
|
||||||
- [ ] Хук размещён в `hooks/api/<client-name>/use-<method-name>.hook-api.ts` на своём уровне абстракции согласно архитектуре проекта.
|
|
||||||
- [ ] Именование файлов и сущностей соответствует [правилам именования файлов и папок](#правила-именования-файлов-и-папок).
|
|
||||||
- [ ] Используется SWR для получения данных.
|
|
||||||
- [ ] Все параметры, возвращаемые значения и внутренние переменные строго типизированы.
|
|
||||||
- [ ] Нет дублирования логики между хуками.
|
|
||||||
- [ ] Не используются side-effects вне useEffect/useLayoutEffect.
|
|
||||||
- [ ] Документировано только назначение хука (см. [правила документирования кода](#правило-для-документирования-кода)).
|
|
||||||
- [ ] Нет неиспользуемого или невалидного кода.
|
|
||||||
- [ ] Экспорт только именованный через индексный файл.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Чек-лист для использования API-хука
|
|
||||||
|
|
||||||
- [ ] Импортируется только нужный хук через публичные экспорты (`index.ts`).
|
|
||||||
- [ ] Использование хука строго по назначению (только для получения данных).
|
|
||||||
- [ ] Если требуется получить данные через GET-запрос в компоненте — обязательно используется соответствующий API-хук.
|
|
||||||
**Запрещено вызывать GET-методы API напрямую в компонентах, только через хуки.**
|
|
||||||
- [ ] Обработка состояний загрузки, ошибки и данных реализована корректно.
|
|
||||||
- [ ] Не происходит дублирования логики, связанной с получением данных.
|
|
||||||
- [ ] Нет неиспользуемого или невалидного кода.
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
# API
|
|
||||||
|
|
||||||
> В этом разделе собраны основные правила и рекомендации по созданию, оформлению и использованию API-клиентов и функций для работы с сервером. Следуйте этим принципам, чтобы обеспечить единый стиль, безопасность и удобство поддержки API-слоя в проекте.
|
|
||||||
|
|
||||||
## Описание и назначение API-клиента
|
|
||||||
|
|
||||||
API-клиент — это модуль (папка), отвечающий за взаимодействие с конкретным внешним или внутренним API.
|
|
||||||
В проекте для HTTP-запросов используется только Axios.
|
|
||||||
API-клиент инкапсулирует:
|
|
||||||
- инициализацию экземпляра Axios,
|
|
||||||
- настройку базового URL, интерцепторов, обработчиков ошибок,
|
|
||||||
- организацию всех сущностей и методов для работы с этим API (например, users, auth, orders и т.д.),
|
|
||||||
- экспорт всех функций, типов и fetcher через индексные файлы.
|
|
||||||
|
|
||||||
Каждый API-клиент размещается в папке `src/shared/api/<client-name>/` и имеет собственную структуру согласно архитектуре проекта.
|
|
||||||
|
|
||||||
|
|
||||||
## Использование методов API
|
|
||||||
|
|
||||||
- Все методы API должны использоваться строго внутри блока `try...catch`.
|
|
||||||
- При вызове методов API всегда используйте полный путь, например:
|
|
||||||
`await api.backend.createUser({ email, password });`
|
|
||||||
- Запрещено вызывать методы API вне блока `try...catch` даже в тестах, утилитах и других вспомогательных функциях.
|
|
||||||
|
|
||||||
|
|
||||||
## Структура клиента
|
|
||||||
```text
|
|
||||||
src/shared/api/backend/
|
|
||||||
│
|
|
||||||
├── client.ts
|
|
||||||
├── index.ts
|
|
||||||
└── entities/
|
|
||||||
├── users/
|
|
||||||
│ ├── get-me.api.ts
|
|
||||||
│ ├── create-user.api.ts
|
|
||||||
│ ├── update-user.api.ts
|
|
||||||
│ └── index.ts
|
|
||||||
├── auth/
|
|
||||||
│ ├── login.api.ts
|
|
||||||
│ ├── register.api.ts
|
|
||||||
│ └── index.ts
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
## Описание ключевых элементов
|
|
||||||
|
|
||||||
- **client.ts**
|
|
||||||
Экземпляр Axios с настройками, интерцепторами, экспортом fetcher для SWR.
|
|
||||||
|
|
||||||
- **index.ts**
|
|
||||||
Главная точка экспорта: экспортирует client, fetcher, все сущности и их методы.
|
|
||||||
|
|
||||||
- **entities/**
|
|
||||||
Папка для бизнес-сущностей (например, users, auth, orders и т.д.).
|
|
||||||
|
|
||||||
- **`<entity>/`**
|
|
||||||
Папка для отдельной сущности. Имя — в kebab-case, отражает бизнес-область (например, users, auth).
|
|
||||||
|
|
||||||
- **`<operation>.api.ts`**
|
|
||||||
Файл для каждой операции (CRUD, спец. действия).
|
|
||||||
Внутри:
|
|
||||||
- DTO (интерфейсы запроса/ответа)
|
|
||||||
- Функция, реализующая запрос через client
|
|
||||||
|
|
||||||
- **index.ts (внутри `<entity>`/)**
|
|
||||||
Экспортирует все методы и типы этой сущности.
|
|
||||||
|
|
||||||
- **index.ts (внутри entities/)**
|
|
||||||
Экспортирует все сущности (users, auth и т.д.).
|
|
||||||
|
|
||||||
|
|
||||||
## Именование
|
|
||||||
|
|
||||||
- Соблюдайте [правила именования файлов и папок](#правила-именования-файлов-и-папок):
|
|
||||||
- Файл клиента — `client.ts`.
|
|
||||||
- Файл функции — `<operation>-<entity>.api.ts` (например, `create-user.api.ts`).
|
|
||||||
- DTO — в папке `dto/` (например, `create-user.dto.ts`).
|
|
||||||
- Все функции и типы экспортируются через индексные файлы на каждом уровне (сущность, entities, клиент).
|
|
||||||
|
|
||||||
|
|
||||||
## Требования
|
|
||||||
|
|
||||||
- Для каждого действия (CRUD, спец. действия) — отдельная функция и файл.
|
|
||||||
- Все функции используют общий экземпляр Axios из `client.ts`.
|
|
||||||
- Все функции строго типизированы (используются DTO).
|
|
||||||
- DTO объявляется в отдельном файле в папке `dto/` перед функцией, которая его использует.
|
|
||||||
- Для каждого GET метода обязательно должен быть создан API-хук.
|
|
||||||
- Все API-хуки должны создаваться строго по [документации раздела "Хуки для API"](#хуки-для-api-api-hooks).
|
|
||||||
|
|
||||||
|
|
||||||
## Типизация
|
|
||||||
|
|
||||||
- Все функции и DTO строго типизированы.
|
|
||||||
- Все интерфейсы, типы и enum размещены в папке `types/` на своём уровне абстракции.
|
|
||||||
- Все DTO размещены в папке `dto/` на своём уровне абстракции.
|
|
||||||
- Придерживайтесь [общих правил типизации проекта](#общие-правила-типизации).
|
|
||||||
|
|
||||||
|
|
||||||
## Документирование
|
|
||||||
|
|
||||||
- Документируйте только назначение функций и DTO.
|
|
||||||
- В описании указывается только смысл функции/типа.
|
|
||||||
|
|
||||||
|
|
||||||
## Экспорт
|
|
||||||
|
|
||||||
- Все функции и типы экспортируются через индексные файлы на каждом уровне (сущность, entities, клиент).
|
|
||||||
|
|
||||||
|
|
||||||
## Примеры
|
|
||||||
|
|
||||||
### Пример клиента API
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// client.ts
|
|
||||||
import axios, { AxiosInstance } from "axios";
|
|
||||||
export { AxiosError, isAxiosError } from 'axios';
|
|
||||||
export type { AxiosResponse } from 'axios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Экземпляр HTTP-клиента для работы с backend API.
|
|
||||||
*/
|
|
||||||
export const backendHttpClient: AxiosInstance = axios.create({
|
|
||||||
baseURL: '/api',
|
|
||||||
timeout: 10000,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Интерцептор запроса
|
|
||||||
backendHttpClient.interceptors.request.use(
|
|
||||||
(config) => {
|
|
||||||
// Здесь можно добавить авторизационные заголовки или другую логику
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
(error) => Promise.reject(error)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Интерцептор ответа
|
|
||||||
backendHttpClient.interceptors.response.use(
|
|
||||||
(response) => response,
|
|
||||||
(error) => {
|
|
||||||
// Здесь можно обработать ошибки (например, показать уведомление)
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Пример DTO
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// dto/create-user.dto.ts
|
|
||||||
/**
|
|
||||||
* DTO для создания пользователя.
|
|
||||||
*/
|
|
||||||
export interface CreateUserDto {
|
|
||||||
/** Email пользователя. */
|
|
||||||
email: string;
|
|
||||||
/** Пароль пользователя. */
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Пример API-функции
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// create-user.api.ts
|
|
||||||
import { backendHttpClient } from '../client';
|
|
||||||
import { CreateUserDto } from './dto/create-user.dto';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Создать пользователя.
|
|
||||||
*/
|
|
||||||
export const createUser = (data: CreateUserDto) => backendHttpClient.post('/users', data);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Пример index.ts (в папке сущности)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export * from './create-user.api';
|
|
||||||
export * from './get-user.api';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Пример использования API-функции в компоненте
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { api } from 'shared/api';
|
|
||||||
|
|
||||||
export const CreateUserForm: React.FC = () => {
|
|
||||||
const [email, setEmail] = useState('');
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
await api.backend.createUser({ email, password });
|
|
||||||
console.log('Пользователь создан!');
|
|
||||||
} catch {
|
|
||||||
console.log('Ошибка создания пользователя');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
value={email}
|
|
||||||
onChange={e => setEmail(e.target.value)}
|
|
||||||
placeholder="Email"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={e => setPassword(e.target.value)}
|
|
||||||
placeholder="Пароль"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<button type="submit">Создать пользователя</button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Чек-лист для создания клиента
|
|
||||||
- [ ] Новый клиент размещён в `src/shared/api/<client-name>/`.
|
|
||||||
- [ ] В корне клиента есть client.ts (экземпляр Axios) и index.ts (главный экспорт).
|
|
||||||
- [ ] Все бизнес-сущности размещены в entities/, каждая — в отдельной папке.
|
|
||||||
- [ ] Для каждой операции создан отдельный файл `<operation>`.api.ts с DTO и функцией.
|
|
||||||
- [ ] DTO объявлен непосредственно перед функцией.
|
|
||||||
- [ ] В каждой папке сущности есть свой index.ts для экспорта методов и типов.
|
|
||||||
- [ ] В папке entities/ есть общий index.ts для экспорта всех сущностей.
|
|
||||||
- [ ] Все экспорты организованы через индексные файлы.
|
|
||||||
- [ ] Для каждого GET-метода создан отдельный SWR-хук (см. правила API-хуков).
|
|
||||||
- [ ] Нет дублирования кода и неиспользуемых файлов.
|
|
||||||
|
|
||||||
## Чек-лист для использования API
|
|
||||||
- [ ] Импортируется только нужный метод через публичные экспорты (index.ts).
|
|
||||||
- [ ] Все вызовы API обёрнуты в try...catch.
|
|
||||||
- [ ] Используются только строго типизированные методы.
|
|
||||||
- [ ] Не происходит обращения к Axios напрямую — только через client.
|
|
||||||
- [ ] Нет дублирования логики и неиспользуемого кода.
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
title: Общие принципы
|
|
||||||
---
|
|
||||||
|
|
||||||
# Общие принципы
|
|
||||||
|
|
||||||
## Стек технологий и библиотеки
|
|
||||||
- Использовать **TypeScript** для всех файлов логики и компонентов.
|
|
||||||
- Использовать **FSD (Feature-Sliced Design)**: разделять код на features, entities, processes, widgets, shared.
|
|
||||||
- Использовать **React** (функциональные компоненты, хуки).
|
|
||||||
- Использовать **Mantine UI** для UI-компонентов.
|
|
||||||
- Использовать **Axios** в качестве клиента для работы с API.
|
|
||||||
- Использовать **SWR** для data fetching (GET-запросы).
|
|
||||||
- Использовать **Zustand** для глобального состояния.
|
|
||||||
- Использовать **i18n** для локализации.
|
|
||||||
- Использовать **Vitest** для тестирования.
|
|
||||||
- Использовать **PostCSS модули** для стилизации.
|
|
||||||
- Использовать **BEM** для именований классов в стилях
|
|
||||||
- Использовать **Mobile First** подход для написания стилей.
|
|
||||||
- Использовать **Context7** примеров использования библиотек.
|
|
||||||
- Использовать **i18n** (i18next) для локализации всех пользовательских текстов.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
title: Архитектура
|
|
||||||
---
|
|
||||||
|
|
||||||
# Архитектура
|
|
||||||
|
|
||||||
## Архитектура проекта
|
|
||||||
В проекте используется FSD (Feature-Sliced Design) архитектура.
|
|
||||||
|
|
||||||
- **FSD-границы**
|
|
||||||
- Не нарушать границы слоёв (например, feature не может импортировать из widgets).
|
|
||||||
- Бизнес-логика должна быть вынесена в хуки или сервисы.
|
|
||||||
- **Импорты**
|
|
||||||
- Внутри слоя — относительные импорты.
|
|
||||||
- Между слоями — абсолютные импорты.
|
|
||||||
- **Требования**
|
|
||||||
- Не смешивать логику разных слоёв.
|
|
||||||
- Не хранить бизнес-логику в UI-компонентах.
|
|
||||||
- **Именование**
|
|
||||||
- Файлы и папки kebab-case.
|
|
||||||
|
|
||||||
---
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
---
|
|
||||||
title: Стиль кода
|
|
||||||
---
|
|
||||||
|
|
||||||
# Стиль кода
|
|
||||||
|
|
||||||
## Отступы
|
|
||||||
|
|
||||||
Используем 2 пробела для отступов во всём проекте. Не используем табы.
|
|
||||||
|
|
||||||
|
|
||||||
## Кавычки
|
|
||||||
|
|
||||||
Используем **одинарные кавычки** для строк в JavaScript/TypeScript, и **двойные кавычки** для атрибутов в JSX/TSX.
|
|
||||||
|
|
||||||
**Пример:**
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// JavaScript/TypeScript
|
|
||||||
const message = 'Привет, мир!';
|
|
||||||
const name = 'ProjectName';
|
|
||||||
```
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// JSX/TSX
|
|
||||||
<input type="text" placeholder="Введите имя" />
|
|
||||||
<button title="Сохранить">Сохранить</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Строгая типизация
|
|
||||||
|
|
||||||
всегда указывать типы для пропсов, возвращаемых значений, параметров функций.
|
|
||||||
|
|
||||||
## Ранние возвраты
|
|
||||||
|
|
||||||
(early return) для повышения читаемости.
|
|
||||||
|
|
||||||
## Мемоизация
|
|
||||||
|
|
||||||
Старайся оптимизировать код если это возможно.
|
|
||||||
|
|
||||||
## Документирование
|
|
||||||
|
|
||||||
Документируем ТОЛЬКО ОПИСАНИЕ (функций, компонентов, типов и их полей).
|
|
||||||
|
|
||||||
## any, unknown
|
|
||||||
|
|
||||||
запрещено использовать без крайней необходимости.
|
|
||||||
|
|
||||||
## Классы в TSX
|
|
||||||
|
|
||||||
Для стилизации компонентов используем CSS-модули и методологию BEM. Классы подключаются через объект стилей, импортированный из соответствующего `.module.css` файла.
|
|
||||||
|
|
||||||
> Объект стилей всегда импортируется с именем `s` (сокращённо от style), а не `styles`.
|
|
||||||
|
|
||||||
**Пример:**
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import s from './my-component.module.css';
|
|
||||||
|
|
||||||
export const MyComponent = () => (
|
|
||||||
<div className={s['my-component']}>
|
|
||||||
<button className={s['my-component__button']}>Кнопка</button>
|
|
||||||
<span className={s['my-component__text']}>Текст</span>
|
|
||||||
<button className={s['my-component__button'] + ' ' + s._active}>
|
|
||||||
Активная кнопка
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
- Имя класса всегда берётся из объекта `s`.
|
|
||||||
- Для модификаторов используется отдельный класс с нижним подчёркиванием (например, `s._active`).
|
|
||||||
- Не используйте строковые литералы с классами напрямую — только через объект `s`.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
title: Именование
|
|
||||||
---
|
|
||||||
|
|
||||||
# Именование
|
|
||||||
|
|
||||||
## Именование файлов и папок
|
|
||||||
- Папка компонента: kebab-case, совпадает с названием компонента, пример: `component-name`.
|
|
||||||
- React-компонент: kebab-case, совпадает с названием компонента, пример: `component-name.tsx`.
|
|
||||||
- Стили: kebab-case, шаблон: `<style-name>.module.css`, пример: `style-name.module.css`.
|
|
||||||
- Интерфейсы: kebab-case, шаблон: `<interface-name>.interface.ts`, пример: `interface-name.interface.ts`.
|
|
||||||
- Типы: kebab-case, шаблон: `<type-name>.type.ts`, пример: `type-name.type.ts`.
|
|
||||||
- Enum: kebab-case, шаблон: `<enum-name>.enum.ts`, пример: `enum-name.enum.ts`.
|
|
||||||
- Схемы: kebab-case, шаблон: `<schema-name>.schema.ts`, пример: `schema-name.schema.ts`.
|
|
||||||
- Локализация: kebab-case, пример: `ru.json`, `en.json`.
|
|
||||||
- Утилиты: kebab-case, шаблон: `<util-name>.util.ts`, пример: `util-name.util.ts`
|
|
||||||
- React Hooks: kebab-case, шаблон: `use-<hook-name>.hook.ts`, пример: `use-hook-name.hook.ts`
|
|
||||||
- Хранилища состояния компонента: kebab-case, шаблон: `<store-name>.store.ts`, пример: `store-name.store.ts`
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
---
|
|
||||||
title: Документирование
|
|
||||||
---
|
|
||||||
|
|
||||||
# Документирование
|
|
||||||
|
|
||||||
## Правило для документирования кода
|
|
||||||
|
|
||||||
- Документировать разрешено только описание (назначение) функций, компонентов, типов, интерфейсов, enum и их полей.
|
|
||||||
- Строго запрещено документировать параметры, возвращаемые значения, типы пропсов, аргументы, возвращаемые значения функций, компоненты, хуки и т.д.
|
|
||||||
- В интерфейсах, типах и enum разрешено документировать только смысл (описание) каждого поля или значения.
|
|
||||||
- В React-компонентах, функциях, хранилищах, схемах, утилитах разрешено документировать только назначение (описание), без детализации параметров и возвращаемых значений.
|
|
||||||
- Описание должно быть кратким, информативным и реально помогать понять структуру и бизнес-логику.
|
|
||||||
- Не допускается избыточная или дублирующая очевидное документация.
|
|
||||||
- В конце описания всегда ставить точку.
|
|
||||||
|
|
||||||
**Примеры правильного документирования**
|
|
||||||
```tsx
|
|
||||||
/**
|
|
||||||
* Список задач пользователя.
|
|
||||||
*/
|
|
||||||
export const TodoList = memo(() => { ... });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Интерфейс задачи.
|
|
||||||
*/
|
|
||||||
export interface TodoItem {
|
|
||||||
/** Уникальный идентификатор задачи. */
|
|
||||||
id: string;
|
|
||||||
/** Текст задачи. */
|
|
||||||
text: string;
|
|
||||||
/** Статус выполнения задачи. */
|
|
||||||
completed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Перечисление фильтров задач.
|
|
||||||
*/
|
|
||||||
export enum TodoFilter {
|
|
||||||
/** Все задачи. */
|
|
||||||
All = 'all',
|
|
||||||
/** Только активные задачи. */
|
|
||||||
Active = 'active',
|
|
||||||
/** Только выполненные задачи. */
|
|
||||||
Completed = 'completed',
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Примеры неправильного документирования**
|
|
||||||
```ts
|
|
||||||
// ❌ Не нужно:/
|
|
||||||
/**
|
|
||||||
* @param id - идентификатор задачи
|
|
||||||
* @returns объект задачи
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ❌ Не нужно:/
|
|
||||||
/**
|
|
||||||
* @param props - пропсы компонента
|
|
||||||
* @returns JSX.Element
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ❌ Не нужно:/
|
|
||||||
/**
|
|
||||||
* id — идентификатор задачи
|
|
||||||
* text — текст задачи
|
|
||||||
* completed — статус выполнения
|
|
||||||
*/
|
|
||||||
```
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
---
|
|
||||||
title: Типизация
|
|
||||||
---
|
|
||||||
|
|
||||||
# Типизация
|
|
||||||
|
|
||||||
## Общие правила типизации
|
|
||||||
|
|
||||||
> Данный раздел определяет единые требования к типизации для всего проекта. Соблюдение этих правил обеспечивает читаемость, предсказуемость и безопасность кода.
|
|
||||||
|
|
||||||
- Использовать только строгую типизацию TypeScript для всех файлов логики, компонентов, хуков, API, сторов и утилит.
|
|
||||||
- Всегда явно указывать типы для:
|
|
||||||
- Пропсов компонентов
|
|
||||||
- Параметров функций и методов
|
|
||||||
- Возвращаемых значений функций и методов
|
|
||||||
- Всех переменных состояния (в том числе в store)
|
|
||||||
- Всех значимых переменных и констант, если их тип не очевиден из присваивания
|
|
||||||
- Не использовать `any` и `unknown` без крайней необходимости. Если использование неизбежно — обязательно добавить комментарий с обоснованием.
|
|
||||||
- Все интерфейсы, типы и enum всегда размещать в папке `types/` на своём уровне абстракции (например, `features/todo/types/`).
|
|
||||||
- Для DTO всегда использовать отдельную папку `dto/` на уровне сущности или слоя.
|
|
||||||
- Для сложных структур использовать отдельные интерфейсы или типы, размещая их в соответствующих файлах в папке `types/`.
|
|
||||||
- Для DTO, enum, схем и других сущностей — всегда создавать отдельные типы/интерфейсы с осмысленными именами.
|
|
||||||
- Ключи enum всегда писать ЗАГЛАВНЫМИ_БУКВАМИ (SCREAMING_SNAKE_CASE).
|
|
||||||
- Не использовать неявное приведение типов и не полагаться на автоматический вывод, если это может снизить читаемость или безопасность.
|
|
||||||
- Для массивов и объектов всегда указывать тип элементов/ключей.
|
|
||||||
- Для возвращаемых значений асинхронных функций всегда указывать тип Promise.
|
|
||||||
- Типизацию коллбеков и функций, передаваемых в пропсы, указывать инлайн, не выносить в отдельные типы.
|
|
||||||
- Для типизации внешних библиотек использовать официальные типы или создавать собственные декларации при необходимости.
|
|
||||||
- Не использовать устаревшие или не рекомендуемые паттерны типизации (например, `Function`, `Object`, `{}`).
|
|
||||||
---
|
|
||||||
|
|
||||||
### Примеры
|
|
||||||
|
|
||||||
#### Интерфейс и типы для сущностей (всегда в папке types/)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// features/todo/types/todo-item.interface.ts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Интерфейс задачи.
|
|
||||||
*/
|
|
||||||
export interface TodoItem {
|
|
||||||
/** Уникальный идентификатор задачи. */
|
|
||||||
id: string;
|
|
||||||
/** Текст задачи. */
|
|
||||||
text: string;
|
|
||||||
/** Статус выполнения задачи. */
|
|
||||||
completed: boolean;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Типизация enum (всегда в папке types/)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// features/todo/types/todo-status.enum.ts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Перечисление статусов задачи.
|
|
||||||
*/
|
|
||||||
export enum TodoStatus {
|
|
||||||
/** Активная задача. */
|
|
||||||
ACTIVE = 'active',
|
|
||||||
/** Выполненная задача. */
|
|
||||||
COMPLETED = 'completed',
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Типизация пропсов компонента
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { FC, memo } from 'react';
|
|
||||||
import { TodoItem } from './types/todo-item.interface';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Список задач.
|
|
||||||
*/
|
|
||||||
export interface TodoListProps {
|
|
||||||
/** Массив задач. */
|
|
||||||
items: TodoItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TodoList: FC<TodoListProps> = memo(({ items }) => (
|
|
||||||
<ul>
|
|
||||||
{items.map((item) => (
|
|
||||||
<li key={item.id}>{item.text}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
));
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Типизация функций и коллбеков (инлайн)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
/**
|
|
||||||
* Функция фильтрации задач.
|
|
||||||
*/
|
|
||||||
export const getCompletedTodos = (items: TodoItem[]): TodoItem[] => {
|
|
||||||
return items.filter((t) => t.completed);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Колбэк для обработки клика (инлайн).
|
|
||||||
*/
|
|
||||||
const handleClick = (id: string): void => {
|
|
||||||
console.log('Clicked:', id);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Типизация асинхронных функций
|
|
||||||
|
|
||||||
```ts
|
|
||||||
/**
|
|
||||||
* Получить задачи с сервера.
|
|
||||||
*/
|
|
||||||
export const fetchTodos = async (): Promise<TodoItem[]> => {
|
|
||||||
const response = await fetch('/api/todos');
|
|
||||||
return response.json();
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Типизация состояния в store (интерфейс в types/)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// features/todo/types/todo-store.interface.ts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Состояние хранилища задач.
|
|
||||||
*/
|
|
||||||
export interface TodoStoreState {
|
|
||||||
/** Массив задач. */
|
|
||||||
items: TodoItem[];
|
|
||||||
/** Добавить задачу. */
|
|
||||||
addTodo: (item: TodoItem) => void;
|
|
||||||
/** Удалить задачу. */
|
|
||||||
removeTodo: (id: string) => void;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Типизация DTO (всегда в папке dto/)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// features/todo/dto/create-todo.dto.ts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DTO для создания задачи.
|
|
||||||
*/
|
|
||||||
export interface CreateTodoDto {
|
|
||||||
/** Текст задачи. */
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// features/todo/dto/todo-response.dto.ts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DTO ответа сервера.
|
|
||||||
*/
|
|
||||||
export interface TodoResponseDto {
|
|
||||||
/** Созданная задача. */
|
|
||||||
todo: TodoItem;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Типизация внешних библиотек
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { AxiosResponse } from 'axios';
|
|
||||||
|
|
||||||
export const getData = async (): Promise<AxiosResponse<TodoItem[]>> => {
|
|
||||||
// ...
|
|
||||||
};
|
|
||||||
```
|
|
||||||
### Чек-лист проверки типизации
|
|
||||||
|
|
||||||
- [ ] Все пропсы компонентов явно типизированы через интерфейс или тип в папке `types/`.
|
|
||||||
- [ ] Все параметры и возвращаемые значения функций и методов явно типизированы.
|
|
||||||
- [ ] Все переменные состояния (в том числе в store) имеют явные типы.
|
|
||||||
- [ ] Все интерфейсы, типы и enum размещены в папке `types/` на своём уровне абстракции.
|
|
||||||
- [ ] Ключи всех enum написаны ЗАГЛАВНЫМИ_БУКВАМИ (SCREAMING_SNAKE_CASE).
|
|
||||||
- [ ] Все DTO размещены в папке `dto/` на своём уровне абстракции.
|
|
||||||
- [ ] Не используется `any` и `unknown` без крайней необходимости и поясняющего комментария.
|
|
||||||
- [ ] Для сложных структур используются отдельные интерфейсы или типы.
|
|
||||||
- [ ] Для массивов и объектов указан тип элементов/ключей.
|
|
||||||
- [ ] Для асинхронных функций указан тип Promise с конкретным типом результата.
|
|
||||||
- [ ] Типы коллбеков и функций, передаваемых в пропсы, указаны инлайн.
|
|
||||||
- [ ] Не используются устаревшие типы (`Function`, `Object`, `{}`).
|
|
||||||
- [ ] Для внешних библиотек используются официальные типы или собственные декларации.
|
|
||||||
- [ ] Нет неявного приведения типов, все типы читаемы и прозрачны.
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
title: Локализация
|
|
||||||
---
|
|
||||||
|
|
||||||
# Локализация
|
|
||||||
|
|
||||||
## Правила использования локализации
|
|
||||||
|
|
||||||
- Все пользовательские тексты должны быть вынесены в локализационные файлы.
|
|
||||||
- Для каждого компонента создавать папку `locales/` с файлами `ru.json`, `en.json` и т.д.
|
|
||||||
- Новые namespace обязательно регистрировать в экземпляре i18n (см. `app/i18n.ts`).
|
|
||||||
- В коде использовать только функцию перевода из i18n, не использовать "жёстко" прописанные строки.
|
|
||||||
123
README.md
123
README.md
@@ -1,57 +1,94 @@
|
|||||||
# NextJS Style Guide
|
# NextJS Style Guide
|
||||||
|
|
||||||
Rules and standards for NextJS and TypeScript development: architecture, typing, styles, components, API, and infrastructure.
|
Стандарты разработки фронтенд-приложений на Next.js и TypeScript.
|
||||||
|
|
||||||
## Documentation Structure
|
Сайт: https://nextjs-style-guide.gromlab.ru
|
||||||
|
|
||||||
### Processes
|
## Использование
|
||||||
|
|
||||||
**What to do** in a specific situation — step-by-step instructions.
|
**Для AI-агентов:**
|
||||||
|
|
||||||
| Section | Answers the question |
|
- [llms.txt](https://nextjs-style-guide.gromlab.ru/llms.txt) — Карта разделов, оглавление со ссылками на разделы.
|
||||||
|---------|---------------------|
|
- [llms-full.txt](https://nextjs-style-guide.gromlab.ru/llms-full.txt) — Вся документация одним файлом.
|
||||||
| Getting Started | What tools to install before starting development? |
|
|
||||||
| Creating an App | How to create a new project, where to get a template? |
|
|
||||||
| Creating Pages | How to add a page: routing and screen? |
|
|
||||||
| Creating Components | How to generate components using templates? |
|
|
||||||
| Styling | What to use: Mantine, tokens, or PostCSS? |
|
|
||||||
| Data Fetching | How to fetch data: SWR, codegen, sockets? |
|
|
||||||
| State Management | When and how to create a store (Zustand)? |
|
|
||||||
| Localization | How to add translations and work with i18next? |
|
|
||||||
|
|
||||||
### Basic Rules
|
**Для проекта:**
|
||||||
|
|
||||||
**What the code should look like** — standards not tied to a specific technology.
|
- [nextjs-style-guide.zip](https://nextjs-style-guide.gromlab.ru/nextjs-style-guide.zip) — Набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
|
||||||
|
|
||||||
| Section | Answers the question |
|
## Структура документации
|
||||||
|---------|---------------------|
|
|
||||||
| Tech Stack | What stack do we use? |
|
|
||||||
| Architecture | How are FSD layers, dependencies, and public API structured? |
|
|
||||||
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
|
||||||
| Naming | How to name files, variables, components, hooks? |
|
|
||||||
| Documentation | How to write JSDoc: what to document and what not? |
|
|
||||||
| Typing | How to type: type vs interface, any/unknown, FC? |
|
|
||||||
|
|
||||||
### Applied Sections
|
### Подсказки
|
||||||
|
|
||||||
**How a specific area works** — rules, structure, and code examples for specific technologies and tools.
|
[Подсказки](docs/docs/workflow.md) — короткие ответы на типовые вопросы и решения для спорных ситуаций.
|
||||||
|
|
||||||
| Section | Answers the question |
|
### Базовые правила
|
||||||
|---------|---------------------|
|
|
||||||
| Project Structure | How are folders and files organized by FSD? |
|
|
||||||
| Components | How is a component structured: files, props, clsx, FC? |
|
|
||||||
| Page-level Components | How to define layout, page, loading, error, not-found? |
|
|
||||||
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
|
|
||||||
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
|
|
||||||
| Images | _(not filled)_ |
|
|
||||||
| SVG Sprites | _(not filled)_ |
|
|
||||||
| Video | _(not filled)_ |
|
|
||||||
| API | _(not filled)_ |
|
|
||||||
| Stores | _(not filled)_ |
|
|
||||||
| Hooks | _(not filled)_ |
|
|
||||||
| Fonts | _(not filled)_ |
|
|
||||||
| Localization | _(not filled)_ |
|
|
||||||
|
|
||||||
## For Assistants
|
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
|
||||||
|
|
||||||
Full documentation in a single MD file: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/en/RULES.md
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Технологии и библиотеки | Какой стек используем? |
|
||||||
|
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
||||||
|
| SLM Design | Что такое SLM и зачем она нужна? |
|
||||||
|
| Архитектура: Слои | Какие слои есть и как между ними устроены зависимости? |
|
||||||
|
| Архитектура: Модули | Что такое модуль и как он устроен? |
|
||||||
|
| Архитектура: Сегменты | Какие сегменты есть внутри модуля? |
|
||||||
|
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
||||||
|
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||||
|
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||||
|
|
||||||
|
### Создание проекта
|
||||||
|
|
||||||
|
**Как начать новый проект** — варианты установки и эталонный набор инструментов.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Создание проекта из шаблона | Как начать проект из готового шаблона? |
|
||||||
|
| Создание проекта вручную | Как поднять проект с нуля без шаблона? |
|
||||||
|
| Чистая установка Next.js | Как поставить голый Next.js под дальнейшую сборку? |
|
||||||
|
|
||||||
|
### Настройка
|
||||||
|
|
||||||
|
**Как сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Алиасы импортов | Как настроить алиасы импортов? |
|
||||||
|
| Biome | Как настроить линтер и форматтер? |
|
||||||
|
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
|
||||||
|
| Стили | Как подключить базовые стили и токены? |
|
||||||
|
| SVG-спрайты | Как подключить генерацию SVG-спрайтов? |
|
||||||
|
| Шаблоны генерации | Как подключить шаблоны для кодогенерации? |
|
||||||
|
| VS Code | Как настроить редактор для проекта? |
|
||||||
|
|
||||||
|
### Использование
|
||||||
|
|
||||||
|
**Как это устроено и как этим пользоваться** — структура, примеры и правила для конкретных областей.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||||
|
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||||
|
| Файлы роутинга | Как описывать layout, page, loading, error, not-found? |
|
||||||
|
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||||
|
| Стили | Как писать CSS: вложенность, медиа, токены? |
|
||||||
|
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
|
||||||
|
| Изображения | _(не заполнен)_ |
|
||||||
|
| Видео | _(не заполнен)_ |
|
||||||
|
| Stores | _(не заполнен)_ |
|
||||||
|
| Хуки | _(не заполнен)_ |
|
||||||
|
| Шрифты | _(не заполнен)_ |
|
||||||
|
| Локализация | _(не заполнен)_ |
|
||||||
|
|
||||||
|
### Данные
|
||||||
|
|
||||||
|
**Как работать с источниками данных** — REST, realtime и потребление в компонентах.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Источники данных | Как устроена работа с данными в проекте? |
|
||||||
|
| REST: Автоматическая генерация | Как сгенерировать REST-клиент автоматически из OpenAPI? |
|
||||||
|
| REST: Ручное создание | Как написать REST-клиент вручную? |
|
||||||
|
| REST: Серверные компоненты | Как получать данные в серверных компонентах? |
|
||||||
|
| REST: Клиентские компоненты | Как получать данные в клиентских компонентах? |
|
||||||
|
| Realtime | Как работать с realtime-каналами и сокетами? |
|
||||||
|
|||||||
60
README_RU.md
60
README_RU.md
@@ -1,60 +0,0 @@
|
|||||||
# NextJS Style Guide
|
|
||||||
|
|
||||||
Правила и стандарты разработки на NextJS и TypeScript: архитектура, типизация, стили, компоненты, API и инфраструктурные разделы.
|
|
||||||
|
|
||||||
## Для ассистентов
|
|
||||||
|
|
||||||
Полная документация в одном MD файле: https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/RULES.md
|
|
||||||
|
|
||||||
## Структура документации
|
|
||||||
|
|
||||||
### Workflow
|
|
||||||
|
|
||||||
**Что делать и в каком порядке** — пошаговые инструкции.
|
|
||||||
|
|
||||||
| Раздел | Отвечает на вопрос |
|
|
||||||
|--------|-------------------|
|
|
||||||
| Начало работы | Что нужно знать перед началом разработки? |
|
|
||||||
| Создание проекта | Как начать новый проект? |
|
|
||||||
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
|
|
||||||
| Добавление страницы | Как добавить новую страницу в проект? |
|
|
||||||
| Добавление UI-модуля | Как создать компонент, фичу, виджет, сущность или layout? |
|
|
||||||
| Стилизация | Как стилизовать компоненты в проекте? |
|
|
||||||
| Получение данных | Как получать данные с сервера? |
|
|
||||||
| Управление состоянием | Как работать с состоянием? |
|
|
||||||
| Локализация | Как добавлять переводы и подключать локализацию? |
|
|
||||||
|
|
||||||
### Базовые правила
|
|
||||||
|
|
||||||
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
|
|
||||||
|
|
||||||
| Раздел | Отвечает на вопрос |
|
|
||||||
|--------|-------------------|
|
|
||||||
| Технологии и библиотеки | Какой стек используем? |
|
|
||||||
| Архитектура | Как устроены слои FSD, зависимости, публичный API? |
|
|
||||||
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
|
||||||
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
|
||||||
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
|
||||||
| Типизация | Как типизировать: type vs interface, any/unknown, FC? |
|
|
||||||
|
|
||||||
### Прикладные разделы
|
|
||||||
|
|
||||||
**Как это настроить и использовать** — конфигурация, структура и примеры кода для конкретных областей.
|
|
||||||
|
|
||||||
| Раздел | Отвечает на вопрос |
|
|
||||||
|--------|-------------------|
|
|
||||||
| Настройка VS Code | Как настроить редактор для проекта? |
|
|
||||||
| Структура проекта | Как организованы папки и файлы по FSD? |
|
|
||||||
| Компоненты | Как устроен компонент: файлы, пропсы, clsx, FC? |
|
|
||||||
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
|
|
||||||
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
|
||||||
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
|
||||||
| Изображения | _(не заполнен)_ |
|
|
||||||
| SVG-спрайты | _(не заполнен)_ |
|
|
||||||
| Видео | _(не заполнен)_ |
|
|
||||||
| API | _(не заполнен)_ |
|
|
||||||
| Stores | _(не заполнен)_ |
|
|
||||||
| Хуки | _(не заполнен)_ |
|
|
||||||
| Шрифты | _(не заполнен)_ |
|
|
||||||
| Локализация | _(не заполнен)_ |
|
|
||||||
|
|
||||||
107
concat-md.js
107
concat-md.js
@@ -1,107 +0,0 @@
|
|||||||
import path from "path";
|
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
// Явный порядок файлов внутри каждого языка
|
|
||||||
const fileOrder = [
|
|
||||||
// index
|
|
||||||
"index.md",
|
|
||||||
// workflow
|
|
||||||
"workflow.md",
|
|
||||||
// basics
|
|
||||||
"basics/tech-stack.md",
|
|
||||||
"basics/naming.md",
|
|
||||||
"basics/architecture.md",
|
|
||||||
"basics/code-style.md",
|
|
||||||
"basics/documentation.md",
|
|
||||||
"basics/typing.md",
|
|
||||||
// applied
|
|
||||||
"applied/project-structure.md",
|
|
||||||
"applied/components.md",
|
|
||||||
"applied/page-level.md",
|
|
||||||
"applied/templates-generation.md",
|
|
||||||
"applied/styles.md",
|
|
||||||
"applied/images-sprites.md",
|
|
||||||
"applied/svg-sprites.md",
|
|
||||||
"applied/video.md",
|
|
||||||
"applied/api.md",
|
|
||||||
"applied/stores.md",
|
|
||||||
"applied/hooks.md",
|
|
||||||
"applied/fonts.md",
|
|
||||||
"applied/localization.md",
|
|
||||||
"applied/vscode.md",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Удалить frontmatter из содержимого md-файла
|
|
||||||
const stripFrontmatter = (content) =>
|
|
||||||
content.replace(/^---[\s\S]*?---\n*/m, "");
|
|
||||||
|
|
||||||
// Сдвинуть уровень заголовков на 1 вниз (h1→h2, h2→h3, ...)
|
|
||||||
// Не трогает заголовки внутри блоков кода
|
|
||||||
const shiftHeadings = (content) => {
|
|
||||||
const lines = content.split("\n");
|
|
||||||
let inCodeBlock = false;
|
|
||||||
|
|
||||||
return lines
|
|
||||||
.map((line) => {
|
|
||||||
if (line.startsWith("```")) inCodeBlock = !inCodeBlock;
|
|
||||||
if (inCodeBlock) return line;
|
|
||||||
if (/^#{1,5}\s/.test(line)) return "#" + line;
|
|
||||||
return line;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Собрать RULES.md с мета-якорями для каждого файла
|
|
||||||
const buildRules = (lang) => {
|
|
||||||
const srcDir = `./docs/${lang}`;
|
|
||||||
const outDir = `./generated/${lang}`;
|
|
||||||
const outFile = path.join(outDir, "RULES.md");
|
|
||||||
|
|
||||||
if (!fs.existsSync(srcDir)) {
|
|
||||||
console.log(`Пропуск ${lang}: папка ${srcDir} не найдена`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mkdirSync(outDir, { recursive: true });
|
|
||||||
|
|
||||||
const parts = [];
|
|
||||||
|
|
||||||
for (const file of fileOrder) {
|
|
||||||
const filePath = path.join(srcDir, file);
|
|
||||||
if (!fs.existsSync(filePath)) continue;
|
|
||||||
|
|
||||||
const raw = fs.readFileSync(filePath, "utf8");
|
|
||||||
const content = stripFrontmatter(raw).trim();
|
|
||||||
if (!content) continue;
|
|
||||||
|
|
||||||
// Мета-якорь: путь VitePress без расширения
|
|
||||||
const route = "/" + file.replace(/\.md$/, "");
|
|
||||||
// index.md остаётся без сдвига (его h1 — главный заголовок документа)
|
|
||||||
const processed = file === "index.md" ? content : shiftHeadings(content);
|
|
||||||
parts.push(`<!-- ${route} -->\n${processed}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(outFile, parts.join("\n\n"), "utf8");
|
|
||||||
console.log(`RULES.md (${lang}) создан: ${outFile}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Собираем RULES.md для обоих языков
|
|
||||||
buildRules("ru");
|
|
||||||
buildRules("en");
|
|
||||||
|
|
||||||
// Генерируем README из index.md
|
|
||||||
const buildReadme = (lang, outFile) => {
|
|
||||||
const indexPath = `./docs/${lang}/index.md`;
|
|
||||||
|
|
||||||
if (!fs.existsSync(indexPath)) {
|
|
||||||
console.log(`Пропуск README (${lang}): ${indexPath} не найден`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = stripFrontmatter(fs.readFileSync(indexPath, "utf8"));
|
|
||||||
fs.writeFileSync(outFile, content, "utf8");
|
|
||||||
console.log(`${outFile} создан из ${indexPath}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
buildReadme("en", "./README.md");
|
|
||||||
buildReadme("ru", "./README_RU.md");
|
|
||||||
103
docs/docs/DEVELOP.md
Normal file
103
docs/docs/DEVELOP.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
---
|
||||||
|
title: Гид для агента
|
||||||
|
description: Что AI-агент обязан прочитать перед началом работы, а что — по задаче.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Обязательное чтение перед началом работы
|
||||||
|
|
||||||
|
Этот документ определяет **строгий порядок действий агента перед выполнением любых задач**.
|
||||||
|
|
||||||
|
## Общее правило
|
||||||
|
|
||||||
|
Перед началом работы над **любой задачей** агент **обязан ознакомиться с базовой документацией проекта**.
|
||||||
|
|
||||||
|
Нарушение этого порядка считается ошибкой.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Порядок обязательного чтения
|
||||||
|
|
||||||
|
Агент должен читать документацию **строго в следующем порядке**:
|
||||||
|
|
||||||
|
### 1. Архитектура (КРИТИЧЕСКИ ВАЖНО)
|
||||||
|
|
||||||
|
* [Архитектура: Обзор](./basics/architecture/index.md)
|
||||||
|
* [Архитектура: Слои](./basics/architecture/layers.md)
|
||||||
|
* [Архитектура: Модули](./basics/architecture/modules.md)
|
||||||
|
* [Архитектура: Сегменты](./basics/architecture/segments.md)
|
||||||
|
|
||||||
|
**Архитектура — это самое важное в проекте.**
|
||||||
|
|
||||||
|
Агент обязан:
|
||||||
|
|
||||||
|
* строго понимать архитектурный подход (SLM)
|
||||||
|
* соблюдать архитектуру **на 100% без отклонений**
|
||||||
|
* не предлагать решений, нарушающих архитектурные принципы
|
||||||
|
* не упрощать архитектуру даже ради скорости выполнения задачи
|
||||||
|
|
||||||
|
Любое нарушение архитектуры недопустимо.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Базовые правила
|
||||||
|
|
||||||
|
После архитектуры необходимо изучить:
|
||||||
|
|
||||||
|
* [Технологии и библиотеки](./basics/tech-stack.md)
|
||||||
|
* [Именование](./basics/naming.md)
|
||||||
|
* [Стиль кода](./basics/code-style.md)
|
||||||
|
* [Документирование](./basics/documentation.md)
|
||||||
|
* [Типизация](./basics/typing.md)
|
||||||
|
|
||||||
|
Агент обязан применять эти правила во всех решениях.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Использование карты документации
|
||||||
|
|
||||||
|
Для поиска дополнительных сведений агент должен использовать:
|
||||||
|
|
||||||
|
* [MAP.md](./MAP.md)
|
||||||
|
|
||||||
|
MAP.md содержит ссылки на все прикладные и вспомогательные разделы.
|
||||||
|
|
||||||
|
Агент может:
|
||||||
|
|
||||||
|
* переходить к нужным разделам через MAP.md
|
||||||
|
* уточнять детали реализации
|
||||||
|
* искать примеры и частные случаи
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Запрещено
|
||||||
|
|
||||||
|
Агенту запрещено:
|
||||||
|
|
||||||
|
* начинать выполнение задачи без изучения архитектуры
|
||||||
|
* игнорировать базовые правила
|
||||||
|
* принимать решения, противоречащие архитектуре
|
||||||
|
* придумывать собственные подходы, если они не описаны в документации
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ожидаемое поведение агента
|
||||||
|
|
||||||
|
Перед выполнением задачи агент должен:
|
||||||
|
|
||||||
|
1. Изучить архитектуру
|
||||||
|
2. Изучить базовые правила
|
||||||
|
3. При необходимости открыть MAP.md и найти релевантные разделы
|
||||||
|
4. Только после этого приступать к решению задачи
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Приоритеты
|
||||||
|
|
||||||
|
При принятии решений агент должен руководствоваться следующим приоритетом:
|
||||||
|
|
||||||
|
1. **Архитектура**
|
||||||
|
2. Базовые правила
|
||||||
|
3. Документация из MAP.md
|
||||||
|
4. Задача пользователя
|
||||||
|
|
||||||
|
Если задача противоречит архитектуре — задача должна быть переосмыслена, а не выполнена напрямую.
|
||||||
66
docs/docs/MAP.md
Normal file
66
docs/docs/MAP.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Карта документации
|
||||||
|
|
||||||
|
Список всех разделов архива с относительными ссылками. Точка входа
|
||||||
|
— `DEVELOP.md` рядом с этим файлом.
|
||||||
|
|
||||||
|
## Подсказки
|
||||||
|
|
||||||
|
- [Подсказки](./workflow.md) — Короткие ответы на типовые вопросы и решения для спорных ситуаций.
|
||||||
|
|
||||||
|
## Базовые правила
|
||||||
|
|
||||||
|
- [Технологии и библиотеки](./basics/tech-stack.md) — Какие библиотеки и инструменты используются в проекте.
|
||||||
|
- [Именование](./basics/naming.md) — Как называть переменные, файлы и прочие сущности в коде.
|
||||||
|
- [Архитектура: Обзор](./basics/architecture/index.md) — Архитектурный подход проекта: что такое SLM и как он устроен.
|
||||||
|
- [Архитектура: Слои](./basics/architecture/layers.md) — Из каких слоёв состоит SLM-архитектура и как они связаны.
|
||||||
|
- [Архитектура: Модули](./basics/architecture/modules.md) — Что такое модуль в SLM-архитектуре и как он устроен.
|
||||||
|
- [Архитектура: Сегменты](./basics/architecture/segments.md) — Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
|
||||||
|
- [Стиль кода](./basics/code-style.md) — Как оформляется код в проекте.
|
||||||
|
- [Документирование](./basics/documentation.md) — Что и как документировать в коде.
|
||||||
|
- [Типизация](./basics/typing.md) — Как типизируется код в проекте.
|
||||||
|
|
||||||
|
## Создание проекта
|
||||||
|
|
||||||
|
- [Из шаблона](./creating-project/from-template.md) — Создание нового проекта на основе готового шаблона.
|
||||||
|
- [По гайду вручную](./creating-project/manual.md) — Поэтапное создание нового проекта без использования шаблона.
|
||||||
|
- [Чистый Next.js](./creating-project/nextjs.md) — Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
|
||||||
|
|
||||||
|
## Работа с данными
|
||||||
|
|
||||||
|
- [Введение](./data/index.md) — Какие источники данных используются в проекте и как с ними работать.
|
||||||
|
- [REST](./data/rest/index.md) — Как правильно работать с REST API в проекте.
|
||||||
|
- [REST: Создание клиента](./data/rest/clients/index.md) — Как выбрать способ создания REST-клиента и где размещать его части.
|
||||||
|
- [REST: Создание клиента: Автогенерация из OpenAPI](./data/rest/clients/auto.md) — Генерация REST-клиента из OpenAPI-спецификации через @gromlab/api-codegen.
|
||||||
|
- [REST: Создание клиента: Ручное создание](./data/rest/clients/manual.md) — Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
||||||
|
- [REST: Создание клиента: GET-хуки REST-клиента](./data/rest/clients/hooks.md) — Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
||||||
|
- [REST: Использование: Стратегии получения данных](./data/rest/strategies/index.md) — Как выбрать способ получения REST-данных в зависимости от места и сценария.
|
||||||
|
- [REST: Использование: Серверный await](./data/rest/strategies/server-await.md) — Получение REST-данных на сервере прямым await метода клиента.
|
||||||
|
- [REST: Использование: Параллельные серверные запросы](./data/rest/strategies/parallel-server-requests.md) — Как запускать независимые REST-запросы на сервере без waterfall.
|
||||||
|
- [REST: Использование: Передача промиса ниже](./data/rest/strategies/pass-promise-down.md) — Как запускать серверный REST-запрос выше и ожидать его во вложенном server-компоненте.
|
||||||
|
- [REST: Использование: Начальные данные для клиентских хуков](./data/rest/strategies/client-hooks-initial-data.md) — Как передать серверный промис в SWR fallback, чтобы клиентские GET-хуки получили начальные данные.
|
||||||
|
- [REST: Использование: Клиентский GET-хук](./data/rest/strategies/client-get-hook.md) — Получение REST-данных в Client Components через готовые GET-хуки REST-клиента.
|
||||||
|
- [REST: Использование: Business-композиция](./data/rest/strategies/business-composition.md) — Когда REST-данные нужно объединить или интерпретировать в бизнес-модуле.
|
||||||
|
- [Realtime](./data/realtime.md) — Работа с push-данными от сервера: подписки и события.
|
||||||
|
|
||||||
|
## Прикладные разделы
|
||||||
|
|
||||||
|
- [Структура проекта](./applied/project-structure.md) — Из чего состоит проект и где что лежит.
|
||||||
|
- [Страницы](./applied/page-level.md) — Как работать со страницами и другими файлами роутинга Next.js App Router.
|
||||||
|
- [Компонент](./applied/component.md) — Как создавать React-компоненты внутри SLM-модулей.
|
||||||
|
- [Модуль](./applied/module.md) — Как создавать и организовывать SLM-модули в проекте.
|
||||||
|
- [Стили: Настройка](./applied/styles/styles-setup.md) — Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
|
||||||
|
- [Стили: Использование](./applied/styles/styles-usage.md) — Как пишутся стили в проекте.
|
||||||
|
- [SVG-спрайты](./applied/svg-sprites/svg-sprites-intro.md) — Что такое SVG-спрайты и какие проблемы они решают.
|
||||||
|
- [SVG-спрайты: Настройка](./applied/svg-sprites/svg-sprites-setup.md) — Подключение SVG-спрайтов в новом проекте.
|
||||||
|
- [SVG-спрайты: Использование](./applied/svg-sprites/svg-sprites-usage.md) — Как добавлять и использовать SVG-иконки в коде.
|
||||||
|
- [Изображения](./applied/images.md) — Как подключать изображения через Next.js Image в проекте.
|
||||||
|
- [Шрифты](./applied/fonts.md) — Как подключать шрифты через Next.js Font в проекте.
|
||||||
|
- [Алиасы импортов](./applied/aliases.md) — Какие алиасы импортов есть в проекте и как ими пользоваться.
|
||||||
|
- [Шаблоны генерации](./applied/templates/templates-intro.md) — Что такое шаблоны кодогенерации и какие проблемы они решают.
|
||||||
|
- [Шаблоны генерации: Настройка](./applied/templates/templates-setup.md) — Первичная установка шаблонов кодогенерации в проект.
|
||||||
|
- [Шаблоны генерации: Создание шаблонов](./applied/templates/templates-create.md) — Структура шаблонов, синтаксис переменных и примеры.
|
||||||
|
- [Шаблоны генерации: Использование](./applied/templates/templates-usage.md) — Генерация файлов из шаблонов через VS Code плагин и CLI.
|
||||||
|
- [Biome](./applied/biome.md) — Установка и настройка линтера-форматтера в новом проекте.
|
||||||
|
- [PostCSS](./applied/postcss.md) — Установка и настройка CSS-процессора в новом проекте.
|
||||||
|
- [VS Code](./applied/vscode.md) — Единые настройки редактора и расширений для команды.
|
||||||
|
- [Локализация](./applied/localization.md) — Как организовать локализацию как infrastructure-модуль.
|
||||||
77
docs/docs/applied/aliases.md
Normal file
77
docs/docs/applied/aliases.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
title: Алиасы импортов
|
||||||
|
description: Какие алиасы импортов есть в проекте и как ими пользоваться.
|
||||||
|
keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, layouts, screens, widgets, business, infrastructure, ui, shared]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Алиасы импортов
|
||||||
|
|
||||||
|
Какие алиасы импортов есть в проекте и как ими пользоваться.
|
||||||
|
|
||||||
|
## Конфиг
|
||||||
|
|
||||||
|
`tsconfig.json` в корне проекта:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"app/*": ["./src/app/*"],
|
||||||
|
"layouts/*": ["./src/layouts/*"],
|
||||||
|
"screens/*": ["./src/screens/*"],
|
||||||
|
"widgets/*": ["./src/widgets/*"],
|
||||||
|
"business/*": ["./src/business/*"],
|
||||||
|
"infrastructure/*": ["./src/infrastructure/*"],
|
||||||
|
"ui/*": ["./src/ui/*"],
|
||||||
|
"shared/*": ["./src/shared/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Восемь алиасов — ровно по числу слоёв. Других алиасов в проекте нет.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- **Каждый импорт между модулями — через алиас слоя.** Относительные пути (`../../`) запрещены за пределами своего модуля.
|
||||||
|
- **Внутри одного модуля** допустимы относительные импорты (`./model`, `./ui/button`) — это часть инкапсуляции модуля.
|
||||||
|
- **Префикс `@/` не используется.** Имя слоя — само по себе адрес.
|
||||||
|
- **Направление импортов** определяется архитектурой, не алиасами. Алиас разрешает импорт технически, но не отменяет правила слоёв (→ [Слои](/docs/basics/architecture/layers)).
|
||||||
|
|
||||||
|
**Хорошо**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Button } from 'ui/button'
|
||||||
|
import { useUser } from 'business/user'
|
||||||
|
import { formatDate } from 'shared/utils/date'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Плохо**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Относительный путь между модулями
|
||||||
|
import { Button } from '../../../ui/button'
|
||||||
|
|
||||||
|
// Префикс @/, которого нет в paths
|
||||||
|
import { Button } from '@/ui/button'
|
||||||
|
|
||||||
|
// Алиас на src — не предусмотрен
|
||||||
|
import { Button } from 'src/ui/button'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Внутри модуля
|
||||||
|
|
||||||
|
Внутри своего модуля — относительные пути:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/ui/button/button.tsx
|
||||||
|
import styles from './button.module.css'
|
||||||
|
import { Icon } from './icon'
|
||||||
|
```
|
||||||
|
|
||||||
|
Не использовать алиас на самого себя:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Плохо — алиас вместо относительного пути внутри модуля
|
||||||
|
import { Icon } from 'ui/button/icon'
|
||||||
|
```
|
||||||
81
docs/docs/applied/biome.md
Normal file
81
docs/docs/applied/biome.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
title: Biome
|
||||||
|
description: Установка и настройка линтера-форматтера в новом проекте.
|
||||||
|
keywords: [biome, линтер, форматтер, lint, format, biome.json, "@biomejs/biome", замена eslint, замена prettier]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Biome
|
||||||
|
|
||||||
|
Установка и настройка линтера-форматтера в новом проекте.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Node.js 18+.
|
||||||
|
- Проект без установленного ESLint и Prettier (они конфликтуют с Biome).
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
1. Установить пакет:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save-dev --save-exact @biomejs/biome
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Инициализировать конфиг:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @biomejs/biome init
|
||||||
|
```
|
||||||
|
|
||||||
|
В корне появится `biome.json` с дефолтными настройками.
|
||||||
|
|
||||||
|
3. Привести `biome.json` к стандартному виду — добавить override для `*.css` (см. «Стандартный `biome.json`»). Делается сразу после `init`, до первого запуска `lint`/`check`.
|
||||||
|
|
||||||
|
4. Добавить скрипты в `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"lint": "biome lint .",
|
||||||
|
"format": "biome format --write .",
|
||||||
|
"check": "biome check --write ."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Скрипт | Что делает |
|
||||||
|
|--------|-----------|
|
||||||
|
| `lint` | Проверка правил без правок |
|
||||||
|
| `format` | Автоформатирование всех файлов |
|
||||||
|
| `check` | Lint + format + organize imports в один проход (основная команда) |
|
||||||
|
|
||||||
|
## Стандартный `biome.json`
|
||||||
|
|
||||||
|
Дефолтный `biome.json`, созданный `biome init`, кастомизируется ровно одним блоком — `overrides` для `*.css` с отключённым правилом `suspicious/noUnknownAtRules`. Этот override **обязателен по умолчанию во всех проектах**, независимо от того, подключены ли уже стили: проектный CSS-стек использует `@custom-media` и другие нестандартные at-правила, которые Biome не распознаёт; без override `npm run lint` падает.
|
||||||
|
|
||||||
|
Фрагмент, который добавляется в `biome.json`:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"includes": ["**/*.css"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"suspicious": {
|
||||||
|
"noUnknownAtRules": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Если в `biome.json` уже есть массив `overrides` — добавить элемент в него; не дублировать массив.
|
||||||
|
|
||||||
|
Прочая настройка правил Biome — отдельная задача, не входит в стандартный канон.
|
||||||
|
|
||||||
|
## Интеграция с VS Code
|
||||||
|
|
||||||
|
Расширение `biomejs.biome` и автоформатирование при сохранении настраиваются в [VS Code](/docs/applied/vscode).
|
||||||
165
docs/docs/applied/component.md
Normal file
165
docs/docs/applied/component.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
---
|
||||||
|
title: Компонент
|
||||||
|
description: Как должен выглядеть сгенерированный React-компонент внутри SLM-модуля.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Компонент
|
||||||
|
|
||||||
|
Как должен выглядеть сгенерированный React-компонент внутри SLM-модуля.
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Архитектурное определение компонента описано в разделе [Модули → Компонент](/docs/basics/architecture/modules#компонент), а структура сегмента `ui/` — в разделе [Сегменты → ui/](/docs/basics/architecture/segments#сегмент-ui).
|
||||||
|
|
||||||
|
Эта страница не повторяет архитектурные ограничения. Она показывает, каким должен быть результат генерации компонента: структура папки, `.tsx`, типы, стили и локальный экспорт.
|
||||||
|
|
||||||
|
::: danger Компоненты не создаются вручную
|
||||||
|
Компоненты в проекте создаются только через кодогенератор: через [VS Code](/docs/applied/templates/templates-usage#через-vs-code) или [CLI](/docs/applied/templates/templates-usage#через-cli).
|
||||||
|
|
||||||
|
Ручное создание компонента запрещено. Это грубое нарушение правил работы в проекте для разработчика и AI-ассистента.
|
||||||
|
|
||||||
|
Если в проекте нет шаблона `.templates/component`, сначала создайте шаблон по разделу [Создание шаблонов](/docs/applied/templates/templates-create), и только потом генерируйте компонент на его основе.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Создание
|
||||||
|
|
||||||
|
1. Проверьте, что в проекте есть шаблон `.templates/component`.
|
||||||
|
2. Если шаблона нет — создайте его по разделу [Создание шаблонов](/docs/applied/templates/templates-create).
|
||||||
|
3. Сгенерируйте компонент через [VS Code или CLI](/docs/applied/templates/templates-usage).
|
||||||
|
|
||||||
|
Структура и код ниже показывают ожидаемый результат генерации. Их нельзя использовать как инструкцию для ручного создания файлов.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
Компонент размещается в `ui/{component-name}/` родительского модуля.
|
||||||
|
|
||||||
|
Для каждого компонента обязательны `.tsx`, типы, стили и локальный `index.ts`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
user-card/
|
||||||
|
└── ui/
|
||||||
|
└── user-status/
|
||||||
|
├── styles/
|
||||||
|
│ └── user-status.module.css
|
||||||
|
├── types/
|
||||||
|
│ └── user-status-props.type.ts
|
||||||
|
├── user-status.tsx
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Реализация
|
||||||
|
|
||||||
|
Пример ниже показывает файлы базового компонента.
|
||||||
|
|
||||||
|
### Типы
|
||||||
|
|
||||||
|
Файл типов делится на три части:
|
||||||
|
|
||||||
|
- `UserStatusParams` — собственные параметры компонента. Здесь лежат только данные, которые нужны именно этому компоненту.
|
||||||
|
- `RootAttrs` — параметры корневой обёртки: `div`, `span`, `a`, `button` или другого HTML-элемента. Если компонент сам управляет `children`, они исключаются через `Omit`.
|
||||||
|
- `UserStatusProps` — итоговые пропсы компонента. Тип объединяет собственные параметры и параметры корневой обёртки.
|
||||||
|
|
||||||
|
Собственные параметры и их поля документируются по правилам раздела [Документирование → Типы, интерфейсы, enum](/docs/basics/documentation#типы-интерфейсы-enum).
|
||||||
|
|
||||||
|
`user-card/ui/user-status/types/user-status-props.type.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { ComponentPropsWithoutRef } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Параметры UserStatus.
|
||||||
|
*/
|
||||||
|
export type UserStatusParams = {
|
||||||
|
/** Текст статуса пользователя. */
|
||||||
|
label: string
|
||||||
|
/** Доступен ли пользователь сейчас. */
|
||||||
|
isOnline: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Атрибуты корневого элемента без children. */
|
||||||
|
type RootAttrs = Omit<ComponentPropsWithoutRef<'span'>, 'children'>
|
||||||
|
|
||||||
|
export type UserStatusProps = RootAttrs & UserStatusParams
|
||||||
|
```
|
||||||
|
|
||||||
|
### TSX
|
||||||
|
|
||||||
|
В `.tsx` лежит только сам компонент:
|
||||||
|
|
||||||
|
- Компонент объявляется через `const` и именованный экспорт.
|
||||||
|
- `React.FC` не используется.
|
||||||
|
- Параметры компонента типизируются через `Props`.
|
||||||
|
- Возвращаемый тип не указывается: TypeScript корректно выводит JSX-результат, а явный `ReactElement` сужает допустимые варианты возврата.
|
||||||
|
- JSDoc-комментарий обязателен и пишется по правилам раздела [Документирование → Компоненты](/docs/basics/documentation#компоненты).
|
||||||
|
- Пропсы деструктурируются в теле компонента, а не в сигнатуре.
|
||||||
|
- Из пропсов обязательно выделяются `className` и `...rootAttrs`.
|
||||||
|
- Функция конкатенации CSS-классов импортируется и именуется `cl`.
|
||||||
|
- Корневой CSS-класс всегда называется `.root`.
|
||||||
|
|
||||||
|
Комментарий описывает назначение и сценарии применения компонента, а не DOM-разметку или внутреннюю реализацию.
|
||||||
|
|
||||||
|
`className` — внешний CSS-класс, который родитель может передать компоненту. `rootAttrs` — остальные атрибуты корневой обёртки: `id`, `aria-*`, `data-*`, обработчики событий и другие HTML-атрибуты. Они прокидываются на корневой DOM-элемент компонента.
|
||||||
|
|
||||||
|
`.root` нужен, чтобы в DevTools быстро находить корневой DOM-узел компонента и одинаково подключать внешний `className` к реальному корню.
|
||||||
|
|
||||||
|
`user-card/ui/user-status/user-status.tsx`
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import cl from 'clsx'
|
||||||
|
import type { UserStatusProps } from './types/user-status-props.type'
|
||||||
|
import styles from './styles/user-status.module.css'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Статус пользователя в карточке профиля.
|
||||||
|
*
|
||||||
|
* Используется для:
|
||||||
|
* - отображения текущей доступности пользователя
|
||||||
|
* - визуального выделения онлайн- и офлайн-состояний
|
||||||
|
*/
|
||||||
|
export const UserStatus = (props: UserStatusProps) => {
|
||||||
|
const { label, isOnline, className, ...rootAttrs } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
{...rootAttrs}
|
||||||
|
className={cl(styles.root, isOnline && styles.online, className)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Стили
|
||||||
|
|
||||||
|
`user-card/ui/user-status/styles/user-status.module.css`
|
||||||
|
|
||||||
|
```css
|
||||||
|
.root {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.root::before {
|
||||||
|
content: '';
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.online {
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Локальный экспорт
|
||||||
|
|
||||||
|
`user-card/ui/user-status/index.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { UserStatus } from './user-status'
|
||||||
|
export type { UserStatusProps } from './types/user-status-props.type'
|
||||||
|
```
|
||||||
128
docs/docs/applied/fonts.md
Normal file
128
docs/docs/applied/fonts.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
---
|
||||||
|
title: Шрифты
|
||||||
|
description: Как подключать шрифты через Next.js Font в проекте.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Шрифты
|
||||||
|
|
||||||
|
Как подключать шрифты через Next.js Font в проекте.
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Шрифты подключаются через `next/font`. Это стандартный способ Next.js: шрифты загружаются без ручных `<link>`, `@font-face` и настройки preconnect.
|
||||||
|
|
||||||
|
Шрифт подключается в точке инициализации приложения, а в CSS используется через переменную.
|
||||||
|
|
||||||
|
## Google Fonts
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/layout.tsx
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { Inter } from 'next/font/google'
|
||||||
|
import 'shared/styles/global.css'
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ['latin', 'cyrillic'],
|
||||||
|
variable: '--font-main',
|
||||||
|
display: 'swap',
|
||||||
|
})
|
||||||
|
|
||||||
|
type RootLayoutProps = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: RootLayoutProps) {
|
||||||
|
return (
|
||||||
|
<html lang="ru" className={inter.variable}>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* src/shared/styles/global.css */
|
||||||
|
body {
|
||||||
|
font-family: var(--font-main), system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Локальные шрифты
|
||||||
|
|
||||||
|
Каждый локальный шрифт размещается в отдельной папке внутри `src/shared/fonts/`. В этой же папке лежит `.font.ts`, где объявляется `localFont`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/shared/fonts/
|
||||||
|
└── roboto/
|
||||||
|
├── roboto.font.ts
|
||||||
|
├── Roboto-Regular.woff2
|
||||||
|
├── Roboto-Italic.woff2
|
||||||
|
├── Roboto-Bold.woff2
|
||||||
|
└── Roboto-BoldItalic.woff2
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/shared/fonts/roboto/roboto.font.ts
|
||||||
|
import localFont from 'next/font/local'
|
||||||
|
|
||||||
|
export const roboto = localFont({
|
||||||
|
src: [
|
||||||
|
{
|
||||||
|
path: './Roboto-Regular.woff2',
|
||||||
|
weight: '400',
|
||||||
|
style: 'normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: './Roboto-Italic.woff2',
|
||||||
|
weight: '400',
|
||||||
|
style: 'italic',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: './Roboto-Bold.woff2',
|
||||||
|
weight: '700',
|
||||||
|
style: 'normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: './Roboto-BoldItalic.woff2',
|
||||||
|
weight: '700',
|
||||||
|
style: 'italic',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variable: '--font-main',
|
||||||
|
display: 'swap',
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`app/` импортирует готовый объект шрифта и только подключает его к документу:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/layout.tsx
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { roboto } from 'shared/fonts/roboto/roboto.font'
|
||||||
|
import 'shared/styles/global.css'
|
||||||
|
|
||||||
|
type RootLayoutProps = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: RootLayoutProps) {
|
||||||
|
return (
|
||||||
|
<html lang="ru" className={roboto.variable}>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Путь в `localFont` указывается относительно `.font.ts`, поэтому файлы шрифта импортируются коротко: `./Roboto-Regular.woff2`.
|
||||||
|
|
||||||
|
Если шрифтов несколько, у каждого своя папка и свой `.font.ts`.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- Использовать `next/font/google` или `next/font/local`.
|
||||||
|
- Не подключать шрифты через ручные `<link>` и `@font-face` без необходимости.
|
||||||
|
- Подключать шрифты один раз — в корневом layout через готовый объект шрифта.
|
||||||
|
- Использовать CSS-переменные `variable`, а не жёстко прописывать семейство в каждом компоненте.
|
||||||
|
- Локальные файлы шрифтов хранить в `src/shared/fonts/{font-name}/` рядом с `{font-name}.font.ts`.
|
||||||
|
- Не объявлять `localFont` внутри `src/app/layout.tsx`; layout только импортирует готовый шрифт.
|
||||||
95
docs/docs/applied/images.md
Normal file
95
docs/docs/applied/images.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
---
|
||||||
|
title: Изображения
|
||||||
|
description: Как подключать изображения через Next.js Image в проекте.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Изображения
|
||||||
|
|
||||||
|
Как подключать изображения через Next.js Image в проекте.
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Изображения рендерятся через компонент `Image` из `next/image`. Это сохраняет единый API для размеров, `alt`, lazy-loading и `priority`, даже если оптимизация изображений отключена.
|
||||||
|
|
||||||
|
В проекте оптимизация Next.js Image отключается через `unoptimized`, чтобы сборка и рантайм не зависели от встроенного image optimizer.
|
||||||
|
|
||||||
|
## Настройка
|
||||||
|
|
||||||
|
Отключение оптимизации задаётся глобально в `next.config.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { NextConfig } from 'next'
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
images: {
|
||||||
|
unoptimized: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default nextConfig
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого `unoptimized` не нужно повторять на каждом `Image`.
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
Статические изображения, доступные по URL, размещаются в `public/`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
public/
|
||||||
|
└── images/
|
||||||
|
└── user-avatar.png
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
export const UserAvatar = () => {
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
src="/images/user-avatar.png"
|
||||||
|
alt="Аватар пользователя"
|
||||||
|
width={96}
|
||||||
|
height={96}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- Использовать `Image` из `next/image`, не обычный `<img>`.
|
||||||
|
- Для контентных изображений всегда писать осмысленный `alt`.
|
||||||
|
- Для декоративных изображений использовать `alt=""`.
|
||||||
|
- Указывать `width` и `height`, если изображение не использует `fill`.
|
||||||
|
- При `fill` задавать `sizes` и контролировать размеры родителя стилями.
|
||||||
|
- `priority` ставить только для изображений первого экрана.
|
||||||
|
- SVG-иконки не оформлять как изображения — для них используется раздел [SVG-спрайты](/docs/applied/svg-sprites/svg-sprites-intro).
|
||||||
|
|
||||||
|
## Пример с `fill`
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import Image from 'next/image'
|
||||||
|
import styles from '../styles/article-card-cover.module.css'
|
||||||
|
|
||||||
|
export const ArticleCardCover = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<Image
|
||||||
|
src="/images/article-cover.jpg"
|
||||||
|
alt="Обложка статьи"
|
||||||
|
fill
|
||||||
|
sizes="(min-width: 768px) 33vw, 100vw"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
```
|
||||||
81
docs/docs/applied/localization.md
Normal file
81
docs/docs/applied/localization.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
title: Локализация
|
||||||
|
description: Как организовать локализацию как infrastructure-модуль.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Локализация
|
||||||
|
|
||||||
|
Как организовать локализацию как infrastructure-модуль.
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Локализация — инфраструктурная подсистема приложения. Она отвечает за текущую локаль, словари, форматирование переводов и API для компонентов.
|
||||||
|
|
||||||
|
Код локализации живёт в `src/infrastructure/i18n/`. Компоненты и модули не читают словари напрямую — они используют публичный API infrastructure-модуля.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/i18n/
|
||||||
|
├── config/
|
||||||
|
│ └── i18n.config.ts
|
||||||
|
├── dictionaries/
|
||||||
|
│ ├── ru.ts
|
||||||
|
│ └── en.ts
|
||||||
|
├── hooks/
|
||||||
|
│ └── use-translation.hook.ts
|
||||||
|
├── providers/
|
||||||
|
│ └── i18n-provider.tsx
|
||||||
|
├── types/
|
||||||
|
│ └── i18n.type.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Набор сегментов может отличаться, но публичная точка входа остаётся одна — `infrastructure/i18n`.
|
||||||
|
|
||||||
|
## Подключение
|
||||||
|
|
||||||
|
`app/` только подключает готовый провайдер локализации. Реализация провайдера, словари и конфиг остаются в `infrastructure/i18n/`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/layout.tsx
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { I18nProvider } from 'infrastructure/i18n'
|
||||||
|
|
||||||
|
type RootLayoutProps = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: RootLayoutProps) {
|
||||||
|
return (
|
||||||
|
<html lang="ru">
|
||||||
|
<body>
|
||||||
|
<I18nProvider locale="ru">{children}</I18nProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
Компоненты получают переводы через готовый API модуля локализации:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useTranslation } from 'infrastructure/i18n'
|
||||||
|
|
||||||
|
export const ProfileTitle = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return <h1>{t('profile.title')}</h1>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- Локализация живёт в `infrastructure/i18n/`.
|
||||||
|
- `app/` только подключает готовый provider и передаёт locale.
|
||||||
|
- Словари не импортируются напрямую в компоненты, screens или business-модули.
|
||||||
|
- Ключи переводов не собираются динамически из строк, если это ломает типизацию и поиск.
|
||||||
|
- Тексты интерфейса не хардкодятся в переиспользуемых компонентах, если они должны переводиться.
|
||||||
|
- Форматирование дат, чисел и валют должно проходить через API локализации или отдельные утилиты infrastructure-модуля.
|
||||||
156
docs/docs/applied/module.md
Normal file
156
docs/docs/applied/module.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
title: Модуль
|
||||||
|
description: Как должен выглядеть сгенерированный SLM-модуль в проекте.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Модуль
|
||||||
|
|
||||||
|
Как должен выглядеть сгенерированный SLM-модуль в проекте.
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Архитектурное определение модуля описано в разделе [Архитектура → Модули](/docs/basics/architecture/modules). Список сегментов описан в разделе [Архитектура → Сегменты](/docs/basics/architecture/segments).
|
||||||
|
|
||||||
|
Эта страница показывает прикладное оформление трёх типов модулей: UI, бизнес и инфраструктурный.
|
||||||
|
|
||||||
|
## Создание
|
||||||
|
|
||||||
|
1. Проверьте, что в проекте есть нужный шаблон в `.templates/`.
|
||||||
|
2. Если шаблона нет — создайте его по разделу [Создание шаблонов](/docs/applied/templates/templates-create).
|
||||||
|
3. Сгенерируйте модуль через [VS Code или CLI](/docs/applied/templates/templates-usage).
|
||||||
|
|
||||||
|
## Типы модулей
|
||||||
|
|
||||||
|
Архитектура определяет три типа модулей ([Типы модулей](/docs/basics/architecture/modules#типы-модулей)):
|
||||||
|
|
||||||
|
| Тип | Обязательный файл | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| UI-модуль | `{name}.tsx` | Модуль, выросший из компонента |
|
||||||
|
| Бизнес-модуль | `{name}.factory.ts` | Модуль вокруг публичного runtime API |
|
||||||
|
| Инфраструктурный модуль | нет | Модуль вокруг технического сервиса |
|
||||||
|
|
||||||
|
## UI-модуль
|
||||||
|
|
||||||
|
UI-модуль — это компонент, который перерос ограничения компонента: получил собственные хуки, вложенные модули в `parts/`, сценарную логику или публичный API. Внутренняя структура та же, что у компонента: корневой `.tsx`, типы, стили, `ui/`. Но без ограничений компонента.
|
||||||
|
|
||||||
|
Подробное оформление компонентов внутри `ui/` описано в разделе [Компонент](/docs/applied/component).
|
||||||
|
|
||||||
|
## Бизнес-модуль
|
||||||
|
|
||||||
|
Бизнес-модуль строится вокруг публичного runtime API. Ключевой файл — фабрика (`{name}.factory.ts`), которая возвращает всё, что нужно внешнему коду в runtime.
|
||||||
|
|
||||||
|
Архитектурное описание фабрики: [Архитектура → Фабрика](/docs/basics/architecture/modules#фабрика).
|
||||||
|
|
||||||
|
### Структура
|
||||||
|
|
||||||
|
```text
|
||||||
|
business/customer/
|
||||||
|
├── customer.factory.ts
|
||||||
|
├── index.ts
|
||||||
|
└── types/
|
||||||
|
├── customer.type.ts
|
||||||
|
├── customer-api.type.ts
|
||||||
|
├── customer-deps.type.ts
|
||||||
|
└── customer-factory.type.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Типы
|
||||||
|
|
||||||
|
`business/customer/types/customer-api.type.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type CustomerApi = {
|
||||||
|
useCustomer: () => Customer
|
||||||
|
CustomerCard: (props: CustomerCardProps) => ReactNode
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`business/order/types/order-deps.type.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type OrderDeps = {
|
||||||
|
customer: Pick<CustomerApi, 'useCustomer'>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`business/order/types/order-factory.type.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type OrderFactory = (deps: OrderDeps) => OrderApi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Фабрика без зависимостей
|
||||||
|
|
||||||
|
`business/customer/customer.factory.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { CustomerFactory } from './types/customer-factory.type'
|
||||||
|
|
||||||
|
export const customerFactory: CustomerFactory = () => {
|
||||||
|
return {
|
||||||
|
useCustomer,
|
||||||
|
CustomerCard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Фабрика с зависимостями
|
||||||
|
|
||||||
|
`business/order/order.factory.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { OrderFactory } from './types/order-factory.type'
|
||||||
|
|
||||||
|
export const orderFactory: OrderFactory = (deps) => {
|
||||||
|
return {
|
||||||
|
useOrder,
|
||||||
|
OrderCard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Композиция на уровне screen
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// screens/home/home.screen.tsx
|
||||||
|
import { customerFactory } from '@/business/customer'
|
||||||
|
import { orderFactory } from '@/business/order'
|
||||||
|
|
||||||
|
const customer = customerFactory()
|
||||||
|
const order = orderFactory({ customer })
|
||||||
|
|
||||||
|
const { useOrder, OrderCard } = order
|
||||||
|
|
||||||
|
export const HomeScreen = () => {
|
||||||
|
const currentOrder = useOrder()
|
||||||
|
|
||||||
|
return <OrderCard order={currentOrder} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Инфраструктурный модуль
|
||||||
|
|
||||||
|
Инфраструктурный модуль строится вокруг технического сервиса или интеграции. Его структура определяется природой сервиса — фиксированного корневого файла нет.
|
||||||
|
|
||||||
|
Архитектурное описание: [Архитектура → Типы модулей → Инфраструктурный модуль](/docs/basics/architecture/modules#инфраструктурный-модуль).
|
||||||
|
|
||||||
|
Пример модуля темы:
|
||||||
|
|
||||||
|
```text
|
||||||
|
theme/
|
||||||
|
├── index.ts
|
||||||
|
├── config/
|
||||||
|
├── hooks/
|
||||||
|
├── styles/
|
||||||
|
└── ui/
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример модуля API-клиента:
|
||||||
|
|
||||||
|
```text
|
||||||
|
backend-api/
|
||||||
|
├── backend-api.client.ts
|
||||||
|
├── config/
|
||||||
|
├── types/
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
186
docs/docs/applied/page-level.md
Normal file
186
docs/docs/applied/page-level.md
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
---
|
||||||
|
title: Файлы роутинга
|
||||||
|
description: Как работать со страницами и другими файлами роутинга Next.js App Router.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Файлы роутинга
|
||||||
|
|
||||||
|
Как работать со страницами и другими файлами роутинга Next.js App Router.
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
`src/app/**` — точка входа приложения и слой файлового роутинга Next.js.
|
||||||
|
|
||||||
|
Файлы роутинга не реализуют интерфейс. Они описывают маршрут: читают параметры, получают данные первого рендера, подготавливают кеш или состояние и передают результат в screen.
|
||||||
|
|
||||||
|
Границы слоя описаны в [Архитектура → Слои → App](/docs/basics/architecture/layers#слой-app).
|
||||||
|
|
||||||
|
## Граница ответственности
|
||||||
|
|
||||||
|
| Область | Где живёт |
|
||||||
|
|---|---|
|
||||||
|
| Файлы маршрутов (`page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`) | `src/app/**` |
|
||||||
|
| Параметры маршрута, `metadata`, `redirect()`, `notFound()` | `src/app/**` |
|
||||||
|
| Серверные запросы для первого рендера | `src/app/**`, через готовые клиенты и сервисы нижних слоёв |
|
||||||
|
| Прогрев SWR-кеша, начальное состояние, подключение провайдеров | `src/app/**`, только через готовые обёртки из нижних слоёв |
|
||||||
|
| UI страницы | `screens/` |
|
||||||
|
| Каркас страницы: header, footer, sidebar | `layouts/` |
|
||||||
|
| Провайдеры, сторы, хуки, API-клиенты, сервисы | нижние слои (`screens/`, `business/`, `infrastructure/`, `shared/`) |
|
||||||
|
| CSS Modules и стили компонентов | рядом с компонентами, не в `src/app/**` |
|
||||||
|
|
||||||
|
## Что можно делать в `page.tsx`
|
||||||
|
|
||||||
|
- Экспортировать `metadata` или `generateMetadata`.
|
||||||
|
- Читать `params` и `searchParams`.
|
||||||
|
- Нормализовать и валидировать параметры маршрута.
|
||||||
|
- Делать серверные запросы для первого рендера через готовые клиенты или сервисы.
|
||||||
|
- Вызывать `redirect()` и `notFound()`.
|
||||||
|
- Готовить начальные данные для screen.
|
||||||
|
- Готовить SWR `fallback` и передавать его в готовый провайдер.
|
||||||
|
- Подключать готовый провайдер стора страницы и передавать начальное состояние.
|
||||||
|
- Рендерить screen или композицию из готовых обёрток и screen.
|
||||||
|
|
||||||
|
## Что запрещено
|
||||||
|
|
||||||
|
- Писать UI-разметку страницы прямо в файле роутинга.
|
||||||
|
- Создавать локальные компоненты внутри `src/app/**`.
|
||||||
|
- Добавлять CSS Modules, стили компонентов, `components/`, `styles/`, `hooks/`, `stores/`, `services/` внутри `src/app/**`.
|
||||||
|
- Реализовывать провайдеры, сторы, хуки, API-клиенты или сервисы в файлах роутинга.
|
||||||
|
- Размещать бизнес-логику, мапперы и правила предметной области в файлах роутинга.
|
||||||
|
- Вызывать `useSWR` и доменные клиентские хуки в файлах роутинга.
|
||||||
|
|
||||||
|
## Страницы
|
||||||
|
|
||||||
|
Страница объявляется через `export default function`. Для серверных запросов используется `async function`.
|
||||||
|
|
||||||
|
```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} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Данные первого рендера
|
||||||
|
|
||||||
|
Если данные нужны до первого рендера, `page.tsx` получает их на сервере и передаёт в screen. Сам запрос выполняется через готовый клиент или сервис нижнего слоя.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { notFound } from 'next/navigation'
|
||||||
|
import { userApi } from 'infrastructure/backend-api'
|
||||||
|
import { UserScreen } from 'screens/user'
|
||||||
|
|
||||||
|
type UserPageProps = {
|
||||||
|
params: Promise<{ id: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function UserPage({ params }: UserPageProps) {
|
||||||
|
const { id } = await params
|
||||||
|
const user = await userApi.users.get(id)
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <UserScreen user={user} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Если данные нужны нескольким клиентским SWR-хукам, файл роутинга может обернуть дерево в `SWRConfig` и передать `fallback`. Запросы стартуют на сервере, а клиентские хуки получают данные из кеша.
|
||||||
|
|
||||||
|
Ключи `fallback` должны совпадать с ключами внутри GET-хуков REST-клиента. Для array-key используется `unstable_serialize`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { SWRConfig, unstable_serialize } from 'swr'
|
||||||
|
import {
|
||||||
|
backendApi,
|
||||||
|
getCurrentUserKey,
|
||||||
|
getPostListKey,
|
||||||
|
} from 'infrastructure/backend-api'
|
||||||
|
|
||||||
|
type FeedLayoutProps = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function FeedLayout({ children }: FeedLayoutProps) {
|
||||||
|
const userPromise = backendApi.user.getCurrent()
|
||||||
|
const postsPromise = backendApi.posts.list()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SWRConfig
|
||||||
|
value={{
|
||||||
|
fallback: {
|
||||||
|
[unstable_serialize(getCurrentUserKey())]: userPromise,
|
||||||
|
[unstable_serialize(getPostListKey())]: postsPromise,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SWRConfig>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробнее о стратегиях запросов и начальных данных для клиентских хуков: [REST → Стратегии получения данных](/docs/data/rest/strategies/), [REST → Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
|
||||||
|
|
||||||
|
## Инициализация состояния
|
||||||
|
|
||||||
|
Файл роутинга может подключить готовый провайдер стора страницы, если состояние зависит от маршрута или данных первого рендера. Реализация стора и провайдера не размещается в `src/app/**`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { ProfileScreen, ProfileStoreProvider } from 'screens/profile'
|
||||||
|
|
||||||
|
type ProfilePageProps = {
|
||||||
|
params: Promise<{ id: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ProfilePage({ params }: ProfilePageProps) {
|
||||||
|
const { id } = await params
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProfileStoreProvider initialState={{ userId: id }}>
|
||||||
|
<ProfileScreen />
|
||||||
|
</ProfileStoreProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
`layout.tsx` подключает готовую инициализацию приложения: глобальные стили, провайдеры и верхнеуровневые обёртки из нижних слоёв.
|
||||||
|
|
||||||
|
Вёрстка layout-каркаса выносится в слой `layouts/`. Реализация провайдеров, стилей и UI не размещается в `app/`.
|
||||||
|
|
||||||
|
## Error и Not Found
|
||||||
|
|
||||||
|
`error.tsx` и `not-found.tsx` делегируют разметку готовым screen или widget. В файле роутинга остаётся только адаптация API Next.js к пропсам нижнего слоя.
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
70
docs/docs/applied/postcss.md
Normal file
70
docs/docs/applied/postcss.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: PostCSS
|
||||||
|
description: Установка и настройка CSS-процессора в новом проекте.
|
||||||
|
keywords: [postcss, postcss.config.mjs, postcss-custom-media, postcss-nesting, autoprefixer, postcss-global-data, csstools, "@custom-media", "@nest", css-процессор]
|
||||||
|
---
|
||||||
|
|
||||||
|
# PostCSS
|
||||||
|
|
||||||
|
Установка и настройка CSS-процессора в новом проекте.
|
||||||
|
|
||||||
|
## Зачем PostCSS
|
||||||
|
|
||||||
|
Подключаем ради двух вещей:
|
||||||
|
|
||||||
|
- **Вложенность** — `&:hover`, `&::before`, `&._active` и `@media` внутри селектора. Без процессора нативный CSS не покрывает всех нужных кейсов вложенности.
|
||||||
|
- **`@custom-media`** — единые breakpoints проекта (`@media (--md)`) вместо магических `min-width`. Определяются в одном месте, переиспользуются везде.
|
||||||
|
|
||||||
|
Autoprefixer и `@csstools/postcss-global-data` идут довеском под эти две задачи.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Next.js 14+ (App Router).
|
||||||
|
- Node.js 18+.
|
||||||
|
|
||||||
|
CSS Modules поддерживаются Next.js из коробки — отдельной установки не требуют.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
1. Установить PostCSS-плагины как devDependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D postcss-custom-media postcss-nesting autoprefixer @csstools/postcss-global-data
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Создать `postcss.config.mjs` в корне проекта (см. «Конфиг»).
|
||||||
|
|
||||||
|
## Конфиг
|
||||||
|
|
||||||
|
Файл `postcss.config.mjs` в корне проекта.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// postcss.config.mjs
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
'@csstools/postcss-global-data': {
|
||||||
|
files: ['src/shared/styles/media.css'],
|
||||||
|
},
|
||||||
|
'postcss-custom-media': {},
|
||||||
|
'postcss-nesting': {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Разбор плагинов
|
||||||
|
|
||||||
|
| Плагин | Назначение |
|
||||||
|
|--------|------------|
|
||||||
|
| `@csstools/postcss-global-data` | Подгружает определения `@custom-media` из `src/shared/styles/media.css` перед обработкой каждого CSS-модуля. Семантика — «глобальный файл определений, который не импортируется в исходники» |
|
||||||
|
| `postcss-custom-media` | Поддержка `@custom-media --md (...)` и использования `@media (--md) {}`. Определения берутся из файла, который подгрузил `postcss-global-data` |
|
||||||
|
| `postcss-nesting` | Нативная CSS-вложенность: `&:hover`, `&::before`, `&._active` |
|
||||||
|
| `autoprefixer` | Добавление вендорных префиксов по browserslist |
|
||||||
|
|
||||||
|
### Почему внешний файл с `@custom-media`, а не `@import`
|
||||||
|
|
||||||
|
`@custom-media` — глобальные определения, одинаковые для всего проекта. Держим их в `src/shared/styles/media.css`. `@csstools/postcss-global-data` подгружает этот файл перед каждым модулем, а `postcss-custom-media` заменяет `@media (--md)` на конкретные `@media (min-width: ...)` на этапе сборки. Сами определения в бандл не попадают.
|
||||||
|
|
||||||
|
Опция `importFrom` у `postcss-custom-media` удалена в v10+; её роль теперь выполняет `@csstools/postcss-global-data`.
|
||||||
|
|
||||||
|
Импортировать `media.css` в файлы компонентов **не нужно** и запрещено правилами (см. [Использование стилей](/docs/applied/styles/styles-usage), раздел «Импорт стилей»).
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Структура проекта
|
title: Структура проекта
|
||||||
|
description: Из чего состоит проект и где что лежит.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Структура проекта
|
# Структура проекта
|
||||||
|
|
||||||
Раздел описывает расположение файлов и папок в проекте Next.js (App Router).
|
Из чего состоит проект и где что лежит.
|
||||||
|
|
||||||
## Корень репозитория
|
## Корень репозитория
|
||||||
|
|
||||||
@@ -40,25 +41,27 @@ public/
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
src/
|
src/
|
||||||
├── app/ # Роутинг Next.js, провайдеры, глобальные стили
|
├── app/ # Роутинг Next.js и точка входа приложения
|
||||||
├── screens/ # Собраные страницы (UI)
|
├── layouts/ # Каркасы страниц (header, footer, sidebar)
|
||||||
├── layouts/ # Шаблоны
|
├── screens/ # Контент конкретной страницы
|
||||||
├── widgets/ # Крупные самостоятельные блоки интерфейса
|
├── widgets/ # Составные блоки интерфейса, не привязанные к домену
|
||||||
├── features/ # Пользовательские сценарии
|
├── business/ # Бизнес-домены (auth, catalog, orders)
|
||||||
├── entities/ # Бизнес-сущности
|
├── infrastructure/ # Техсервисы (theme, i18n, API-адаптеры)
|
||||||
└── shared/ # Переиспользуемый код (UI, утилиты, типы и др.)
|
├── ui/ # UI-кит без бизнес-логики
|
||||||
|
└── shared/ # Общие ресурсы (утилиты, типы, стили)
|
||||||
```
|
```
|
||||||
|
|
||||||
Принципы организации слоёв описаны в разделе [Архитектура](../basics/architecture).
|
Принципы организации слоёв описаны в разделе [Архитектура](../basics/architecture/).
|
||||||
|
|
||||||
### Папка `app/`
|
### Папка `app/`
|
||||||
|
|
||||||
Совмещает два слоя: инициализацию приложения по FSD (провайдеры, глобальные стили) и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты).
|
Точка входа приложения и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты).
|
||||||
|
`app/` подключает готовую инициализацию из нижних слоёв, но не реализует провайдеры, стили, UI-компоненты, хуки, сторы или сервисы.
|
||||||
|
|
||||||
|
Подробнее о границах слоя: [Архитектура → Слои → App](/docs/basics/architecture/layers#слой-app).
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/app/
|
src/app/
|
||||||
├── providers/ # Провайдеры приложения
|
|
||||||
├── styles/ # Глобальные стили
|
|
||||||
├── layout.tsx # Корневой layout
|
├── layout.tsx # Корневой layout
|
||||||
└── page.tsx # Главная страница
|
└── page.tsx # Главная страница
|
||||||
```
|
```
|
||||||
@@ -73,12 +76,11 @@ src/app/
|
|||||||
├── screen/ # Шаблон экрана
|
├── screen/ # Шаблон экрана
|
||||||
├── layout/ # Шаблон layout
|
├── layout/ # Шаблон layout
|
||||||
├── widget/ # Шаблон виджета
|
├── widget/ # Шаблон виджета
|
||||||
├── feature/ # Шаблон фичи
|
├── module/ # Шаблон бизнес-модуля
|
||||||
├── entity/ # Шаблон сущности
|
|
||||||
└── store/ # Шаблон стора
|
└── store/ # Шаблон стора
|
||||||
```
|
```
|
||||||
|
|
||||||
Подробнее о генерации описано в разделе [Шаблоны и генерация кода](./templates-generation).
|
Подробнее о генерации описано в разделе [Шаблоны генерации](/docs/applied/templates/templates-intro).
|
||||||
|
|
||||||
## Конфигурационные файлы
|
## Конфигурационные файлы
|
||||||
|
|
||||||
176
docs/docs/applied/styles/styles-setup.md
Normal file
176
docs/docs/applied/styles/styles-setup.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
---
|
||||||
|
title: Настройка стилей
|
||||||
|
description: "Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили."
|
||||||
|
keywords: [variables.css, media.css, global.css, shared/styles, токены, переменные, custom-media, breakpoints, подключение стилей, базовые стили, инициализация]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Настройка стилей
|
||||||
|
|
||||||
|
Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Установлен PostCSS или любой другой pre/post-процессор с поддержкой `@custom-media`.
|
||||||
|
|
||||||
|
## Файлы
|
||||||
|
|
||||||
|
Состав глобальных стилей — три файла:
|
||||||
|
|
||||||
|
| Файл | Роль |
|
||||||
|
|------|------|
|
||||||
|
| `variables.css` | Токены проекта (цвета, отступы, радиусы) |
|
||||||
|
| `media.css` | Custom media queries (брейкпоинты по ширине и высоте) |
|
||||||
|
| `global.css` | Точка сборки глобальных стилей: через `@import` тянет все остальные глобалы, импортируется в приложение один раз |
|
||||||
|
|
||||||
|
Правила подключения:
|
||||||
|
|
||||||
|
- В приложение импортируется **только** `global.css`.
|
||||||
|
- `variables.css` и будущие глобальные файлы (резеты, темы, типографика) подключаются в `global.css` через `@import`.
|
||||||
|
- `media.css` **не импортируется** — ни в `global.css`, ни в компоненты, ни в точку инициализации. Его читает CSS-процессор на этапе сборки (см. [PostCSS](/docs/applied/postcss)).
|
||||||
|
|
||||||
|
## Корневой `font-size`
|
||||||
|
|
||||||
|
Базовая единица `rem` в проекте привязана к **16px**: корневой `font-size` не переопределяется. `html { font-size: ... }` писать запрещено — пользовательская настройка размера шрифта в браузере должна работать (a11y). Все `rem`-значения в `media.css` и других стилях трактуются как `1rem = 16px по умолчанию`.
|
||||||
|
|
||||||
|
Reset браузерных дефолтов (`box-sizing`, сброс `margin`, типографика) каноном не задаётся — каждый проект решает сам. Если заводится — подключается через `global.css`.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
### 1. Создать файлы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p src/shared/styles
|
||||||
|
touch src/shared/styles/variables.css src/shared/styles/media.css src/shared/styles/global.css
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Заполнить `media.css`
|
||||||
|
|
||||||
|
Файл `src/shared/styles/media.css`. Стандартный набор брейкпоинтов проекта; редактировать только при согласованном изменении шкалы.
|
||||||
|
|
||||||
|
Единица — `rem` (реагирует на корневой `font-size`). Перевод исходит из дефолтного `html { font-size: 16px }`, т.е. `1rem = 16px`.
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* src/shared/styles/media.css */
|
||||||
|
|
||||||
|
/* Ширина — Mobile First (min-width), кроме --xs (max-width) */
|
||||||
|
@custom-media --xs (max-width: 35.9375rem); /* 575px — до sm */
|
||||||
|
@custom-media --sm (min-width: 36rem); /* 576px — телефон альбом / малый планшет */
|
||||||
|
@custom-media --md (min-width: 48rem); /* 768px — планшет */
|
||||||
|
@custom-media --lg (min-width: 62rem); /* 992px — малый десктоп */
|
||||||
|
@custom-media --xl (min-width: 75rem); /* 1200px — десктоп */
|
||||||
|
@custom-media --2xl (min-width: 88rem); /* 1408px — широкий десктоп */
|
||||||
|
@custom-media --3xl (min-width: 120rem); /* 1920px — full HD+ */
|
||||||
|
|
||||||
|
/* Высота — min-height */
|
||||||
|
@custom-media --h-xs (min-height: 41.6875rem); /* 667px — iPhone SE портрет */
|
||||||
|
@custom-media --h-sm (min-height: 43.875rem); /* 702px */
|
||||||
|
@custom-media --h-md (min-height: 50.625rem); /* 810px — iPad портрет */
|
||||||
|
@custom-media --h-lg (min-height: 56.25rem); /* 900px */
|
||||||
|
@custom-media --h-xl (min-height: 62.5rem); /* 1000px */
|
||||||
|
@custom-media --h-2xl (min-height: 68.75rem); /* 1100px */
|
||||||
|
@custom-media --h-3xl (min-height: 75rem); /* 1200px */
|
||||||
|
```
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
|
||||||
|
- только `@custom-media` на верхнем уровне;
|
||||||
|
- имена короткие, по шкале (`--xs` … `--3xl`); высотные — с префиксом `--h-`;
|
||||||
|
- единица — `rem`, не `em`/`px`; пиксельное значение указывается комментарием;
|
||||||
|
- значения ширины — `min-width` (Mobile First), исключение `--xs` — `max-width` (блок «строго меньше `--sm`»);
|
||||||
|
- значения высоты — `min-height`.
|
||||||
|
|
||||||
|
### 3. Заполнить `variables.css`
|
||||||
|
|
||||||
|
Файл `src/shared/styles/variables.css`. Набор токенов под проект расширяется по мере роста дизайн-системы.
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* src/shared/styles/variables.css */
|
||||||
|
:root {
|
||||||
|
/* Цвета */
|
||||||
|
--color-primary: #3b82f6;
|
||||||
|
--color-bg: #ffffff;
|
||||||
|
--color-bg-hover: #f5f5f5;
|
||||||
|
--color-text: #1a1a1a;
|
||||||
|
|
||||||
|
/* Отступы */
|
||||||
|
--space-1: 4px;
|
||||||
|
--space-2: 8px;
|
||||||
|
--space-3: 12px;
|
||||||
|
--space-4: 16px;
|
||||||
|
|
||||||
|
/* Скругления */
|
||||||
|
--radius-1: 4px;
|
||||||
|
--radius-2: 8px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
|
||||||
|
- все токены определяются в `:root` — без вложенных селекторов;
|
||||||
|
- именование — `kebab-case` по ролям: `--color-*`, `--space-*`, `--radius-*`;
|
||||||
|
- `px` — основная единица для пространственных токенов;
|
||||||
|
- темы накладываются поверх через `[data-theme="..."] { ... }` — в отдельном файле темы или здесь же.
|
||||||
|
|
||||||
|
`variables.css` напрямую в приложение не импортируется — только через `global.css`.
|
||||||
|
|
||||||
|
### 4. Заполнить `global.css`
|
||||||
|
|
||||||
|
Файл `src/shared/styles/global.css`. Единственный глобальный файл, импортируемый в точку инициализации приложения. Внутри — `@import` остальных глобалов относительным путём.
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* src/shared/styles/global.css */
|
||||||
|
@import './variables.css';
|
||||||
|
|
||||||
|
/* Сюда же подключаются будущие глобалы через @import:
|
||||||
|
* @import './reset.css';
|
||||||
|
* @import './typography.css';
|
||||||
|
* @import './themes.css';
|
||||||
|
* media.css НЕ импортируется — он работает через PostCSS.
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
|
||||||
|
- пути в `@import` — относительные (`./variables.css`), не через алиасы; нативный CSS `@import` не понимает tsconfig-paths;
|
||||||
|
- `media.css` в `global.css` **не импортируется**;
|
||||||
|
- собственные глобальные правила (`html { ... }`, `body { ... }`) писать **не здесь**, а в отдельных файлах рядом (`reset.css`, `typography.css`) и подключать через `@import`. `global.css` — только точка сборки;
|
||||||
|
- порядок `@import` определяет порядок каскада: токены первыми, дальше резеты / темы / типографика.
|
||||||
|
|
||||||
|
### 5. Подключить `global.css` в layout
|
||||||
|
|
||||||
|
Импорт делается **один раз** — в корневом layout приложения:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/layout.tsx
|
||||||
|
import 'shared/styles/global.css'
|
||||||
|
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'App',
|
||||||
|
description: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="ru">
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`variables.css` и `media.css` в layout **не импортируются напрямую** — только через `global.css` (variables) или через PostCSS на сборке (media).
|
||||||
|
|
||||||
|
## Проверка установки
|
||||||
|
|
||||||
|
- В `src/shared/styles/` присутствуют три файла: `variables.css`, `media.css`, `global.css`. В `src/app/` папки `styles/` нет.
|
||||||
|
- В `src/app/layout.tsx` есть `import 'shared/styles/global.css'`. Импортов `variables.css` и `media.css` там нет.
|
||||||
|
- В проекте **не появились** PostCSS-пакеты и `postcss.config.*` — этот раздел их не ставит.
|
||||||
|
- `npm run build` завершается успешно.
|
||||||
|
|
||||||
|
## Дальше
|
||||||
|
|
||||||
|
- [PostCSS](/docs/applied/postcss) — подключить процессор, чтобы заработали `@media (--md)` и вложенность.
|
||||||
|
- [Использование стилей](/docs/applied/styles/styles-usage) — правила написания CSS в компонентах.
|
||||||
|
- [SVG-спрайты](/docs/applied/svg-sprites/svg-sprites-setup) — стили иконок отдельно от глобальных.
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
---
|
---
|
||||||
title: Стили
|
title: Использование стилей
|
||||||
|
description: Как пишутся стили в проекте.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Стили
|
# Использование стилей
|
||||||
|
|
||||||
Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование.
|
Как пишутся стили в проекте.
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
- Только **PostCSS** и **CSS Modules** для кастомной стилизации.
|
- Только **PostCSS** и **CSS Modules** для кастомной стилизации.
|
||||||
- Подход **Mobile First** — стили пишутся от мобильных к десктопу.
|
- Подход **Mobile First** — стили пишутся от мобильных к десктопу.
|
||||||
- Именование классов — `camelCase` (`.root`, `.buttonNext`, `.itemTitle`).
|
- Именование классов — `camelCase` (`.root`, `.buttonNext`, `.itemTitle`).
|
||||||
|
- Корневой класс каждого CSS Module компонента всегда называется `.root` — это упрощает ориентацию в DevTools и отладку DOM.
|
||||||
- Модификаторы — отдельный класс с `_`, применяется через `&._modifier`.
|
- Модификаторы — отдельный класс с `_`, применяется через `&._modifier`.
|
||||||
|
|
||||||
**Хорошо**
|
**Хорошо**
|
||||||
@@ -142,13 +144,13 @@ title: Стили
|
|||||||
|
|
||||||
## CSS-переменные
|
## CSS-переменные
|
||||||
|
|
||||||
- Цвета (`--color-*`), отступы (`--space-*`), скругления (`--radius-*`) определяются в `app/styles/variables.css` через `:root`.
|
- Цвета (`--color-*`), отступы (`--space-*`), скругления (`--radius-*`) определяются в `src/shared/styles/variables.css` через `:root`.
|
||||||
- Файл переменных подключается один раз в корневом layout/entry point — после этого переменные доступны глобально через каскад.
|
- Файл переменных подключается через `src/shared/styles/global.css`, который импортируется один раз в `src/app/layout.tsx`.
|
||||||
- Не дублировать магические значения в компонентах.
|
- Не дублировать магические значения в компонентах.
|
||||||
|
|
||||||
**Хорошо**
|
**Хорошо**
|
||||||
```css
|
```css
|
||||||
/* app/styles/variables.css */
|
/* src/shared/styles/variables.css */
|
||||||
:root {
|
:root {
|
||||||
--color-primary: #3b82f6;
|
--color-primary: #3b82f6;
|
||||||
--color-bg: #ffffff;
|
--color-bg: #ffffff;
|
||||||
@@ -182,11 +184,11 @@ title: Стили
|
|||||||
|
|
||||||
## Custom Media
|
## Custom Media
|
||||||
|
|
||||||
- Breakpoints определяются через Custom Media Queries в `app/styles/media.css`.
|
- Breakpoints определяются через Custom Media Queries в `src/shared/styles/media.css`.
|
||||||
- Custom media подключаются глобально через конфиг PostCSS (плагин `postcss-custom-media`) — не импортировать в файлы стилей.
|
- Custom media подключаются глобально через конфиг PostCSS (плагин `postcss-custom-media`) — не импортировать в файлы стилей.
|
||||||
|
|
||||||
```css
|
```css
|
||||||
/* app/styles/media.css */
|
/* src/shared/styles/media.css */
|
||||||
@custom-media --sm (min-width: 36em);
|
@custom-media --sm (min-width: 36em);
|
||||||
@custom-media --md (min-width: 62em);
|
@custom-media --md (min-width: 62em);
|
||||||
@custom-media --lg (min-width: 82em);
|
@custom-media --lg (min-width: 82em);
|
||||||
31
docs/docs/applied/svg-sprites/svg-sprites-intro.md
Normal file
31
docs/docs/applied/svg-sprites/svg-sprites-intro.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: SVG-спрайты
|
||||||
|
description: "Что такое SVG-спрайты и какие проблемы они решают."
|
||||||
|
---
|
||||||
|
|
||||||
|
# SVG-спрайты
|
||||||
|
|
||||||
|
Что такое SVG-спрайты и какие проблемы они решают.
|
||||||
|
|
||||||
|
## Проблема
|
||||||
|
|
||||||
|
Иконки в проекте — это десятки и сотни SVG-файлов, которые нужно как-то доставлять в интерфейс. Подход «один `<img>` на иконку» или инлайн SVG в каждом компоненте приводят к трём проблемам:
|
||||||
|
|
||||||
|
- **Дублирование.** Инлайн SVG в нескольких компонентах — один и тот же код размазан по проекту. Изменение иконки требует правок в десяти местах.
|
||||||
|
- **Размер бандла.** Каждый инлайн SVG — полный XML-код, который попадает в JS-бандл. Сотня иконок × средний размер SVG = сотни килобайт, которые браузер парсит как JavaScript, а не как статику.
|
||||||
|
- **Нет управления цветом.** Инлайн SVG жёстко закрашивает иконку. Сменить цвет по состоянию (`:hover`, `._disabled`) — значит дублировать SVG или городить `currentColor`-хаки в каждом компоненте.
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
SVG-спрайты — это единый файл-контейнер, в который собираются все иконки проекта. В коде используется один React-компонент `<SvgSprite icon="name"/>`, а браузер загружает спрайт как статику — один раз, с кешированием.
|
||||||
|
|
||||||
|
Что дают SVG-спрайты:
|
||||||
|
|
||||||
|
- **Один источник.** Каждая иконка — один SVG-файл в `src/shared/sprites/`. Обновил файл — иконка обновилась везде.
|
||||||
|
- **Лёгкий бандл.** Спрайт отдаётся как статический файл из `public/`, не попадает в JavaScript. Типы имён иконок генерируются автоматически — автодополнение работает без ручных описаний.
|
||||||
|
- **Цвет через CSS.** При сборке цвета в SVG заменяются на CSS-переменные. Цвет иконки меняется через `color` родителя или через переменные `--icon-color-N` — как любой другой стиль.
|
||||||
|
|
||||||
|
## Состав раздела
|
||||||
|
|
||||||
|
- [Настройка](/docs/applied/svg-sprites/svg-sprites-setup) — подключение пакета, конфигурация, первая генерация.
|
||||||
|
- [Использование](/docs/applied/svg-sprites/svg-sprites-usage) — добавление иконок, компонент `<SvgSprite/>`, управление цветом.
|
||||||
132
docs/docs/applied/svg-sprites/svg-sprites-setup.md
Normal file
132
docs/docs/applied/svg-sprites/svg-sprites-setup.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
title: Настройка SVG-спрайтов
|
||||||
|
description: Подключение SVG-спрайтов в новом проекте.
|
||||||
|
keywords: [svg-sprites, установка, настройка, config, пакет, "@gromlab/svg-sprites", svg-sprites.config.ts]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Настройка SVG-спрайтов
|
||||||
|
Подключение SVG-спрайтов в новом проекте.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
1. Установить пакет:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @gromlab/svg-sprites
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Создать `svg-sprites.config.ts` в корне проекта (см. [Стандартный конфиг](#стандартныи-конфиг)).
|
||||||
|
|
||||||
|
3. Создать папку входа для SVG-файлов в слое `shared`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p src/shared/sprites/icons
|
||||||
|
```
|
||||||
|
|
||||||
|
Источники спрайтов живут в `src/shared/sprites/<group>/` — это слой `shared` SLM-архитектуры (см. [Структура проекта](/docs/applied/project-structure), [Архитектура](/docs/basics/architecture/)). В `src/` посторонних каталогов вне слоёв не заводим.
|
||||||
|
|
||||||
|
4. Добавить скрипты в `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"sprite": "svg-sprites",
|
||||||
|
"predev": "svg-sprites",
|
||||||
|
"prebuild": "svg-sprites"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Хуки `predev` и `prebuild` гарантируют, что спрайты и типы всегда актуальны перед запуском и сборкой.
|
||||||
|
|
||||||
|
5. Добавить сгенерированные артефакты в `.gitignore`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# Сгенерированные спрайты и React-компонент
|
||||||
|
/public/sprites/
|
||||||
|
/src/ui/svg-sprite/
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Выполнить первую генерацию:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run sprite
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Подключить спрайт в layout. Глобальный спрайт (иконки) подключается через `<link rel="preload">` в корневом layout — браузер загрузит файл заранее и закеширует:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/layout.tsx
|
||||||
|
import 'shared/styles/global.css'
|
||||||
|
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'App',
|
||||||
|
description: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<link rel="preload" href="/sprites/icons.sprite.stack.svg" as="image" />
|
||||||
|
</head>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Локальные спрайты (если есть) подключаются аналогично в layout конкретной страницы или маршрута.
|
||||||
|
|
||||||
|
## Стандартный конфиг
|
||||||
|
|
||||||
|
Файл `svg-sprites.config.ts` в корне проекта. Это канон — отклонения только по явной причине.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// svg-sprites.config.ts
|
||||||
|
import { defineConfig } from '@gromlab/svg-sprites'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
output: 'public/sprites',
|
||||||
|
publicPath: '/sprites',
|
||||||
|
react: 'src/ui/svg-sprite',
|
||||||
|
sprites: [
|
||||||
|
{ name: 'icons', input: 'src/shared/sprites/icons' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Фиксированные значения
|
||||||
|
|
||||||
|
| Опция | Значение | Почему так |
|
||||||
|
|-------|----------|------------|
|
||||||
|
| `output` | `public/sprites` | Единая папка статики Next.js |
|
||||||
|
| `publicPath` | `/sprites` | URL-путь без `public/` (Next.js раздаёт `public/` как `/`) |
|
||||||
|
| `react` | `src/ui/svg-sprite` | Слой `ui/` из архитектуры проекта (→ [Архитектура](/docs/basics/architecture/)) |
|
||||||
|
| `sprites[0].name` | `icons` | Основной спрайт всегда называется `icons` |
|
||||||
|
|
||||||
|
### Трансформации
|
||||||
|
|
||||||
|
Все значения по умолчанию оставлять включёнными:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
transform: {
|
||||||
|
removeSize: true,
|
||||||
|
replaceColors: true,
|
||||||
|
addTransition: true,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Явно прописывать блок `transform` не нужно — пакет применяет эти значения по умолчанию.
|
||||||
|
|
||||||
|
Отключать `replaceColors` — только для отдельного спрайта с фиксированной палитрой (например, брендовые логотипы). Делать это на уровне спрайта, не глобально.
|
||||||
|
|
||||||
|
### Режим
|
||||||
|
|
||||||
|
По умолчанию `mode: 'stack'` — не указывать явно. Переход на `symbol` требует обоснования: превью и примеры в пакете оптимизированы под `stack`.
|
||||||
|
|
||||||
|
## Дальше
|
||||||
|
|
||||||
|
- [Использование](/docs/applied/svg-sprites/svg-sprites-usage) — добавление иконок, компонент `<SvgSprite/>`, управление цветом.
|
||||||
56
docs/docs/applied/svg-sprites/svg-sprites-usage.md
Normal file
56
docs/docs/applied/svg-sprites/svg-sprites-usage.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: Использование SVG-спрайтов
|
||||||
|
description: Как добавлять и использовать SVG-иконки в коде.
|
||||||
|
keywords: [svg, спрайт, sprite, иконка, icon, SvgSprite, превью, preview, цвет, color]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Использование SVG-спрайтов
|
||||||
|
|
||||||
|
Как добавлять и использовать SVG-иконки в коде.
|
||||||
|
|
||||||
|
## Шаги
|
||||||
|
|
||||||
|
1. **Положить SVG в папку спрайта:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/shared/sprites/icons/new-icon.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Импортировать компонент.** Компонент `<SvgSprite/>` генерируется пакетом вместе с типами имён иконок — автодополнение работает без ручных описаний:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { SvgSprite } from 'ui/svg-sprite'
|
||||||
|
|
||||||
|
<SvgSprite icon="new-icon" />
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Посмотреть и пощупать иконку — в превью.** Пакет генерирует HTML-превью рядом со спрайтом (`public/sprites/icons.preview.html`). Там виден набор иконок, имена и поведение цвета.
|
||||||
|
|
||||||
|
## Управление цветом
|
||||||
|
|
||||||
|
При сборке цвета в SVG заменяются на CSS-переменные `--icon-color-N`. Управление — через обычный CSS родителя.
|
||||||
|
|
||||||
|
**Моно-иконка** наследует `color` родителя (`--icon-color-1` по умолчанию `currentColor`):
|
||||||
|
|
||||||
|
```css
|
||||||
|
.button {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Точечное переопределение** — через переменную:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.icon-danger {
|
||||||
|
--icon-color-1: var(--color-danger);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Мульти-иконка** — переменные задаются явно, порядок виден в превью:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.folder {
|
||||||
|
--icon-color-1: var(--color-folder-bg);
|
||||||
|
--icon-color-2: var(--color-folder-accent);
|
||||||
|
}
|
||||||
|
```
|
||||||
97
docs/docs/applied/templates/templates-create.md
Normal file
97
docs/docs/applied/templates/templates-create.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
title: Создание шаблонов генерации
|
||||||
|
description: "Структура шаблонов, синтаксис переменных и примеры."
|
||||||
|
keywords: [шаблоны, templates, .templates, syntax, переменные, kebabCase, pascalCase, scaffold]
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- @formatter:off -->
|
||||||
|
::: v-pre
|
||||||
|
|
||||||
|
# Создание шаблонов генерации
|
||||||
|
|
||||||
|
Структура шаблонов, синтаксис переменных и примеры.
|
||||||
|
|
||||||
|
## Структура шаблонов
|
||||||
|
|
||||||
|
Все шаблоны лежат в `.templates/` в корне проекта. Каждая папка — отдельный шаблон.
|
||||||
|
|
||||||
|
```text
|
||||||
|
.templates/
|
||||||
|
├── component/ # шаблон компонента
|
||||||
|
│ └── {{name.kebabCase}}/
|
||||||
|
│ ├── styles/
|
||||||
|
│ │ └── {{name.kebabCase}}.module.css
|
||||||
|
│ ├── types/
|
||||||
|
│ │ └── {{name.kebabCase}}-props.type.ts
|
||||||
|
│ ├── {{name.kebabCase}}.tsx
|
||||||
|
│ └── index.ts
|
||||||
|
└── store/ # шаблон Zustand стора
|
||||||
|
└── {{name.kebabCase}}/
|
||||||
|
├── {{name.kebabCase}}.store.ts
|
||||||
|
├── {{name.kebabCase}}.type.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обязательный шаблон компонента
|
||||||
|
|
||||||
|
Перед созданием компонентов в проекте должен существовать шаблон `.templates/component`.
|
||||||
|
|
||||||
|
Если шаблона нет, компонент не создаётся вручную. Сначала создаётся шаблон компонента, затем компонент генерируется через [VS Code или CLI](/docs/applied/templates/templates-usage).
|
||||||
|
|
||||||
|
## Синтаксис шаблонов
|
||||||
|
|
||||||
|
### Переменные
|
||||||
|
|
||||||
|
Переменные работают в именах файлов/папок и внутри файлов:
|
||||||
|
|
||||||
|
```text
|
||||||
|
{{variable}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Переменные могут быть любыми. `name` — дефолтная, подставляется генератором автоматически. Если реализация требует дополнительных параметров — можно использовать произвольные наборы переменных.
|
||||||
|
|
||||||
|
### Модификаторы
|
||||||
|
|
||||||
|
Модификаторы меняют регистр и формат записи переменной:
|
||||||
|
|
||||||
|
```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'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Дальше
|
||||||
|
|
||||||
|
- [Использование](/docs/applied/templates/templates-usage) — генерация через VS Code плагин и CLI.
|
||||||
|
|
||||||
|
:::
|
||||||
32
docs/docs/applied/templates/templates-intro.md
Normal file
32
docs/docs/applied/templates/templates-intro.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title: Шаблоны генерации
|
||||||
|
description: "Что такое шаблоны кодогенерации и какие проблемы они решают."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Шаблоны генерации
|
||||||
|
|
||||||
|
Что такое шаблоны кодогенерации и какие проблемы они решают.
|
||||||
|
|
||||||
|
## Проблема
|
||||||
|
|
||||||
|
Каждый новый модуль в проекте — компонент, стор, бизнес-модуль — требует однотипной структуры файлов и boilerplate-кода. Ручное создание приводит к трём проблемам:
|
||||||
|
|
||||||
|
- **Расхождения.** Разные разработчики создают модули по-разному: забывают `index.ts`, называют типы не по канону, пропускают стили.
|
||||||
|
- **Время.** Создание одного компонента с типами, стилями и экспортом — 5–10 минут рутины. За спринт набегают часы.
|
||||||
|
- **Ошибки копипасты.** Копирование существующего модуля и переименование — источник опечаток и забытых ссылок.
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Шаблоны кодогенерации — это папки с файлами-заготовками в `.templates/`. Вместо ручного создания файлов разработчик вызывает генератор, указывает имя — и получает готовый модуль со всей структурой, именами и boilerplate, подставленными автоматически.
|
||||||
|
|
||||||
|
Что дают шаблоны:
|
||||||
|
|
||||||
|
- **Единообразие.** Все модули одного типа идентичны по структуре. Канон живёт в шаблоне, а не в памяти разработчика.
|
||||||
|
- **Скорость.** Генерация модуля — одна команда. Остальное время — на бизнес-логику.
|
||||||
|
- **Согласованность с архитектурой.** Шаблоны учитывают SLM: правильные слои, сегменты, экспорты. Отклонение от стайлгайда требует осознанного усилия, а не случайного упущения.
|
||||||
|
|
||||||
|
## Состав раздела
|
||||||
|
|
||||||
|
- [Настройка](/docs/applied/templates/templates-setup) — первичная установка: скачивание стандартного набора шаблонов в проект.
|
||||||
|
- [Создание шаблонов](/docs/applied/templates/templates-create) — структура файлов, синтаксис переменных, примеры.
|
||||||
|
- [Использование](/docs/applied/templates/templates-usage) — генерация через VS Code плагин и CLI.
|
||||||
44
docs/docs/applied/templates/templates-setup.md
Normal file
44
docs/docs/applied/templates/templates-setup.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
title: Настройка шаблонов генерации
|
||||||
|
description: Первичная установка шаблонов кодогенерации в проект.
|
||||||
|
keywords: [шаблоны, templates, .templates, tiged, generator, генератор шаблонов, скачать шаблоны, scaffold]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Настройка шаблонов генерации
|
||||||
|
|
||||||
|
Первичная установка шаблонов кодогенерации в проект.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
1. Проверить, что `.templates/` отсутствует (или согласовать перезапись, если папка уже есть).
|
||||||
|
|
||||||
|
2. Скачать папку из эталонного репозитория:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx tiged git@gromlab.ru:templates/nextjs-template.git/.templates .templates
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Если `tiged` падает в default-режиме (HTTP-tarball у Gitea) — повторить с явным git-режимом:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx tiged --mode=git git@gromlab.ru:templates/nextjs-template.git/.templates .templates
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Проверить генерацию:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @gromlab/create component test src/ui
|
||||||
|
```
|
||||||
|
|
||||||
|
После проверки — удалить тестовый модуль.
|
||||||
|
|
||||||
|
## Проверка установки
|
||||||
|
|
||||||
|
- В корне проекта есть папка `.templates/`.
|
||||||
|
- Внутри `.templates/` присутствуют стандартные шаблоны (или согласованный кастомный набор).
|
||||||
|
- Пробная генерация через `npx @gromlab/create ...` отрабатывает без ошибок.
|
||||||
|
|
||||||
|
## Дальше
|
||||||
|
|
||||||
|
- [Создание шаблонов](/docs/applied/templates/templates-create) — структура файлов, синтаксис переменных, примеры.
|
||||||
|
- [Использование](/docs/applied/templates/templates-usage) — генерация через VS Code плагин и CLI.
|
||||||
45
docs/docs/applied/templates/templates-usage.md
Normal file
45
docs/docs/applied/templates/templates-usage.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
title: Использование шаблонов генерации
|
||||||
|
description: Генерация файлов из шаблонов через VS Code плагин и CLI.
|
||||||
|
keywords: [шаблоны, templates, generate, VS Code, CLI, gromlab/create, npx, scaffold]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Использование шаблонов генерации
|
||||||
|
|
||||||
|
Генерация файлов из шаблонов через VS Code плагин и CLI.
|
||||||
|
|
||||||
|
::: danger Ручное создание запрещено
|
||||||
|
Файлы, для которых есть шаблоны в `.templates/`, создаются только генератором. Ручное создание компонента, модуля, стора или другого шаблонного блока запрещено.
|
||||||
|
|
||||||
|
Если нужного шаблона нет, сначала создайте шаблон в `.templates/`, затем сгенерируйте код на его основе.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Через 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` | Компонент в текущей папке |
|
||||||
|
| `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` | Стор |
|
||||||
|
|
||||||
|
CLI вызывается через `npx`, в `package.json` отдельно не добавляется.
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Настройка VS Code
|
title: VS Code
|
||||||
|
description: Единые настройки редактора и расширений для команды.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Настройка VS Code
|
# VS Code
|
||||||
|
|
||||||
Каждый проект содержит папку `.vscode/` с конфигурацией редактора. Это гарантирует, что все участники команды работают с одинаковыми настройками форматирования, линтинга и расширениями.
|
Единые настройки редактора и расширений для команды.
|
||||||
|
|
||||||
## Структура `.vscode/`
|
## Структура `.vscode/`
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ title: Настройка VS Code
|
|||||||
| Расширение | Назначение |
|
| Расширение | Назначение |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) | Линтинг и форматирование кода. Заменяет ESLint и Prettier |
|
| [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) | Линтинг и форматирование кода. Заменяет ESLint и Prettier |
|
||||||
| [MyTemplateGenerator](https://open-vsx.org/extension/MyTemplateGenerator/mytemplategenerator) | Генерация файлов и папок из шаблонов `.templates/` через контекстное меню |
|
| 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` и др.) |
|
| [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss) | Подсветка синтаксиса и автодополнение для PostCSS (`@custom-media`, `@nest` и др.) |
|
||||||
|
|
||||||
### Зачем это нужно
|
### Зачем это нужно
|
||||||
108
docs/docs/basics/architecture/index.md
Normal file
108
docs/docs/basics/architecture/index.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# SLM Design
|
||||||
|
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
||||||
|
|
||||||
|
::: warning Локальная копия
|
||||||
|
Документация по архитектуре — локальная копия. Оригинал находится на сайте [slm-design.gromlab.ru](https://slm-design.gromlab.ru/).
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Разделы спецификации
|
||||||
|
|
||||||
|
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
|
||||||
|
|
||||||
|
- [Слои](./layers.md) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
|
||||||
|
- [Модули](./modules.md) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
|
||||||
|
- [Сегменты](./segments.md) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
|
||||||
|
|
||||||
|
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты.
|
||||||
|
|
||||||
|
## Преимущества
|
||||||
|
|
||||||
|
### Вертикальная организация домена
|
||||||
|
|
||||||
|
Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы.
|
||||||
|
|
||||||
|
### Dependency Injection без фреймворков
|
||||||
|
|
||||||
|
Cross-domain зависимости в бизнес-слое реализуются через фабрики — модуль декларирует что ему нужно, а точка использования предоставляет зависимости. Домены изолированы без DI-контейнеров, провайдеров и шин событий.
|
||||||
|
|
||||||
|
### Разделение ответственности без перегрузки слоёв
|
||||||
|
|
||||||
|
Сервисы приложения (`infra/`), 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/
|
||||||
|
│
|
||||||
|
├── infra/
|
||||||
|
│ ├── theme/
|
||||||
|
│ ├── i18n/
|
||||||
|
│ ├── backend-api/
|
||||||
|
│ └── logger/
|
||||||
|
│
|
||||||
|
├── ui/
|
||||||
|
│ ├── button/
|
||||||
|
│ ├── input/
|
||||||
|
│ ├── modal/
|
||||||
|
│ ├── toast/
|
||||||
|
│ └── dropdown/
|
||||||
|
│
|
||||||
|
└── shared/
|
||||||
|
├── lib/
|
||||||
|
├── types/
|
||||||
|
└── styles/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Принципы
|
||||||
|
|
||||||
|
- **Домен — единое целое.** Всё, что относится к домену, живёт в одном модуле.
|
||||||
|
- **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости.
|
||||||
|
- **Зависимости однонаправлены.** Импорты только сверху вниз, только через публичный API.
|
||||||
|
- **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.
|
||||||
249
docs/docs/basics/architecture/layers.md
Normal file
249
docs/docs/basics/architecture/layers.md
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
# Слои
|
||||||
|
|
||||||
|
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
|
||||||
|
|
||||||
|
## Определение
|
||||||
|
|
||||||
|
**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.**
|
||||||
|
|
||||||
|
## Группы слоёв
|
||||||
|
|
||||||
|
Слои делятся на три группы:
|
||||||
|
|
||||||
|
| Группа | Слои | Описание |
|
||||||
|
|--------|------|----------|
|
||||||
|
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
|
||||||
|
| Ядро | `business`, `infra`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
|
||||||
|
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
|
||||||
|
|
||||||
|
## Направление зависимостей
|
||||||
|
|
||||||
|
Любой импорт между модулями — только через публичный API.
|
||||||
|
|
||||||
|
```
|
||||||
|
app → [ layouts | screens ] → widgets → business → infra → ui → shared
|
||||||
|
```
|
||||||
|
|
||||||
|
- `layouts` и `screens` — параллельные слои, не импортируют друг друга
|
||||||
|
- Модули одного слоя в группе «Композиция» изолированы друг от друга
|
||||||
|
- Модули одного слоя `infra` и `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 и сервисами.
|
||||||
|
|
||||||
|
Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`. Каждый бизнес-модуль создаёт публичный runtime API через фабрику в корне. Cross-domain зависимости: runtime — через аргументы фабрики, типы — напрямую через `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/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Один модуль = один бизнес-домен
|
||||||
|
- Циклические зависимости между доменами запрещены
|
||||||
|
- Публичный runtime API — через фабрику в корне модуля (`{name}.factory.ts`). `index.ts` экспортирует только фабрику и type-only экспорты
|
||||||
|
- Импорт runtime-кода между доменами — через фабрику. `import type` — напрямую
|
||||||
|
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
|
||||||
|
|
||||||
|
## Слой infra
|
||||||
|
|
||||||
|
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
|
||||||
|
|
||||||
|
Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`.
|
||||||
|
|
||||||
|
Отличие от `shared/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infra/
|
||||||
|
├── theme/
|
||||||
|
├── i18n/
|
||||||
|
├── backend-api/
|
||||||
|
├── maps-api/
|
||||||
|
├── logger/
|
||||||
|
├── feature-flags/
|
||||||
|
└── realtime/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Один модуль = один техсервис
|
||||||
|
- Импортирует `infra/`, `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
|
||||||
|
|
||||||
|
Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене.
|
||||||
|
|
||||||
|
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
|
||||||
|
|
||||||
|
Отличие от `infra/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||||||
|
|
||||||
|
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/shared/
|
||||||
|
├── lib/
|
||||||
|
├── types/
|
||||||
|
├── styles/
|
||||||
|
└── sprites/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Не имеет runtime-состояния
|
||||||
284
docs/docs/basics/architecture/modules.md
Normal file
284
docs/docs/basics/architecture/modules.md
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
# Модули
|
||||||
|
|
||||||
|
Раздел описывает модуль как границу ответственности в SLM: что считается модулем, что такое компонент внутри модуля и как модуль взаимодействует с остальным кодом.
|
||||||
|
|
||||||
|
## Определение
|
||||||
|
|
||||||
|
**Модуль — минимальная архитектурная единица SLM. Он живёт на одном из слоёв, владеет конкретной областью ответственности и предоставляет наружу только публичный API.**
|
||||||
|
|
||||||
|
Модуль может содержать всё, что нужно этой области: компоненты, вложенные модули, хуки, сторы, сервисы, типы, стили, конфиги и утилиты. Набор сегментов не фиксирован — модуль включает только то, что реально нужно.
|
||||||
|
|
||||||
|
Модуль не обязан быть UI-блоком. Это может быть страница, виджет, бизнес-домен, инфраструктурный сервис или UI-kit сущность.
|
||||||
|
|
||||||
|
Главная граница модуля — не папка, а ответственность.
|
||||||
|
|
||||||
|
## Компонент
|
||||||
|
|
||||||
|
**Компонент — презентационная единица модуля, которая находится только в `ui/` своего родительского модуля и отвечает за отображение части интерфейса.**
|
||||||
|
|
||||||
|
Компонент не является архитектурной единицей: он не владеет сценарием, зависимостями, данными или внутренней структурой. Он работает только внутри границы родительского модуля.
|
||||||
|
|
||||||
|
> Компонент отображает. Модуль организует.
|
||||||
|
|
||||||
|
Компонент не может:
|
||||||
|
|
||||||
|
- Импортировать код проекта за пределами родительского модуля.
|
||||||
|
- Владеть архитектурными зависимостями.
|
||||||
|
- Содержать любые компоненты.
|
||||||
|
- Содержать любые модули.
|
||||||
|
- Делать внешние запросы.
|
||||||
|
- Самостоятельно получать данные.
|
||||||
|
- Выбирать источник данных.
|
||||||
|
- Композировать данные.
|
||||||
|
- Вызывать сценарные хуки.
|
||||||
|
- Оркестрировать сценарий.
|
||||||
|
- Композировать модули.
|
||||||
|
- Решать, как устроен процесс.
|
||||||
|
- Содержать бизнес-логику.
|
||||||
|
- Содержать сценарную логику.
|
||||||
|
|
||||||
|
Если компоненту требуется что-то из этого списка, он перестаёт быть компонентом и должен быть оформлен как модуль.
|
||||||
|
|
||||||
|
```text
|
||||||
|
auth/
|
||||||
|
├── ui/
|
||||||
|
│ └── logout-button/
|
||||||
|
│ ├── logout-button.tsx
|
||||||
|
│ ├── styles/
|
||||||
|
│ │ └── logout-button.module.css
|
||||||
|
│ ├── types/
|
||||||
|
│ │ └── logout-button-props.type.ts
|
||||||
|
│ └── index.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что считается модулем
|
||||||
|
|
||||||
|
Модулем считается папка, которая представляет самостоятельную область ответственности и имеет публичную границу.
|
||||||
|
|
||||||
|
Примеры модулей:
|
||||||
|
|
||||||
|
- `screens/home/` — модуль страницы.
|
||||||
|
- `widgets/page-heading/` — модуль виджета.
|
||||||
|
- `business/auth/` — модуль бизнес-домена.
|
||||||
|
- `infra/theme/` — модуль инфраструктурного сервиса.
|
||||||
|
- `ui/button/` — модуль UI-kit сущности.
|
||||||
|
- `screens/home/parts/hero-section/` — вложенный модуль страницы.
|
||||||
|
|
||||||
|
Не считаются модулями:
|
||||||
|
|
||||||
|
- `ui/`, `parts/`, `hooks/`, `types/`, `styles/`, `config/` — это сегменты.
|
||||||
|
- `screens/shop/`, `business/commerce/` — это группы, если в них нет `index.ts`.
|
||||||
|
- `screens/home/ui/user-card/` — это компонент, если он находится в `ui/` и соблюдает ограничения компонента.
|
||||||
|
|
||||||
|
## Типы модулей
|
||||||
|
|
||||||
|
Тип модуля определяет обязательный корневой файл и стартовую структуру.
|
||||||
|
|
||||||
|
### UI-модуль
|
||||||
|
|
||||||
|
Модуль строится вокруг основного UI-компонента и обязан иметь основной `.tsx` файл в корне:
|
||||||
|
|
||||||
|
```text
|
||||||
|
header/
|
||||||
|
├── header.tsx
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
`ui/` внутри такого модуля используется только для компонентов, которые помогают корневому `.tsx` файлу.
|
||||||
|
|
||||||
|
### Бизнес-модуль
|
||||||
|
|
||||||
|
Бизнес-модуль — модуль, который строится вокруг публичного runtime API.
|
||||||
|
|
||||||
|
Бизнес-модуль обязан иметь фабрику в корне:
|
||||||
|
|
||||||
|
```text
|
||||||
|
auth/
|
||||||
|
├── auth.factory.ts
|
||||||
|
├── index.ts
|
||||||
|
└── types/
|
||||||
|
```
|
||||||
|
|
||||||
|
Фабрика возвращает публичный runtime API модуля.
|
||||||
|
|
||||||
|
### Инфраструктурный модуль
|
||||||
|
|
||||||
|
Инфраструктурный модуль — модуль, который строится вокруг технического сервиса или интеграции.
|
||||||
|
|
||||||
|
Инфраструктурный модуль не обязан иметь фиксированный корневой файл. Его структура определяется природой сервиса.
|
||||||
|
|
||||||
|
```text
|
||||||
|
theme/
|
||||||
|
├── index.ts
|
||||||
|
├── config/
|
||||||
|
├── hooks/
|
||||||
|
├── styles/
|
||||||
|
└── ui/
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
backend-api/
|
||||||
|
├── backend-api.client.ts
|
||||||
|
├── config/
|
||||||
|
├── types/
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль включает только те части, которые нужны его ответственности.
|
||||||
|
|
||||||
|
```text
|
||||||
|
{module-name}/
|
||||||
|
├── {module-name}.factory.ts # фабрика (для business-модулей)
|
||||||
|
├── {module-name}.tsx # корневой файл модуля (опционален)
|
||||||
|
├── ui/ # компоненты модуля
|
||||||
|
├── parts/ # вложенные модули
|
||||||
|
├── hooks/ # хуки
|
||||||
|
├── stores/ # сторы состояния
|
||||||
|
├── services/ # внешние источники данных
|
||||||
|
├── mappers/ # трансформация данных между форматами
|
||||||
|
├── types/ # типы
|
||||||
|
├── styles/ # стили
|
||||||
|
├── lib/ # утилиты модуля
|
||||||
|
├── config/ # константы и конфигурация
|
||||||
|
└── index.ts # публичный API
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробное описание сегментов — в разделе [Сегменты](./segments.md).
|
||||||
|
|
||||||
|
## Публичный API
|
||||||
|
|
||||||
|
Внешний код импортирует модуль только через публичный API.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Хорошо
|
||||||
|
import { customerFactory } from '@/business/customer'
|
||||||
|
import type { Customer } from '@/business/customer'
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Плохо
|
||||||
|
import { validateToken } from '@/business/auth/lib/tokens'
|
||||||
|
```
|
||||||
|
|
||||||
|
`index.ts` модуля не обязан экспортировать всё содержимое. Он экспортирует только то, что действительно нужно снаружи.
|
||||||
|
|
||||||
|
Внутренние сегменты модуля остаются деталями реализации.
|
||||||
|
|
||||||
|
Business-модуль экспортирует из `index.ts` только фабрику и type-only экспорты. Хуки, компоненты, сервисы, мапперы и утилиты напрямую из `index.ts` не экспортируются — они доступны через API, который возвращает фабрика.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/index.ts
|
||||||
|
export { customerFactory } from './customer.factory'
|
||||||
|
|
||||||
|
export type { Customer } from './types/customer.type'
|
||||||
|
export type { CustomerApi } from './types/customer-api.type'
|
||||||
|
export type { CustomerDeps } from './types/customer-deps.type'
|
||||||
|
export type { CustomerFactory } from './types/customer-factory.type'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Фабрика
|
||||||
|
|
||||||
|
Business-модуль всегда экспортирует фабрику. Фабрика лежит в корне модуля (`{name}.factory.ts`), типизируется через `{Name}Factory` и возвращает публичный runtime API модуля.
|
||||||
|
|
||||||
|
Всё, что нужно внешнему коду в runtime, должно быть частью API, который возвращает фабрика.
|
||||||
|
|
||||||
|
Модуль без cross-domain зависимостей экспортирует фабрику без аргументов. Модуль с зависимостями — фабрику, принимающую зависимости доменными именами. Типы всегда экспортируются напрямую через `export type` — `import type` не является runtime-зависимостью.
|
||||||
|
|
||||||
|
Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция».
|
||||||
|
|
||||||
|
### Структура business-модуля
|
||||||
|
|
||||||
|
```text
|
||||||
|
business/customer/
|
||||||
|
├── customer.factory.ts
|
||||||
|
├── index.ts
|
||||||
|
└── types/
|
||||||
|
├── customer.type.ts
|
||||||
|
├── customer-api.type.ts
|
||||||
|
├── customer-deps.type.ts
|
||||||
|
└── customer-factory.type.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Типы
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/types/customer-api.type.ts
|
||||||
|
export type CustomerApi = {
|
||||||
|
useCustomer: () => Customer
|
||||||
|
CustomerCard: (props: CustomerCardProps) => ReactNode
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/order/types/order-deps.type.ts
|
||||||
|
export type OrderDeps = {
|
||||||
|
customer: Pick<CustomerApi, 'useCustomer'>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/order/types/order-factory.type.ts
|
||||||
|
export type OrderFactory = (deps: OrderDeps) => OrderApi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Фабрика без зависимостей
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/customer/customer.factory.ts
|
||||||
|
import type { CustomerFactory } from './types/customer-factory.type'
|
||||||
|
|
||||||
|
export const customerFactory: CustomerFactory = () => {
|
||||||
|
return {
|
||||||
|
useCustomer,
|
||||||
|
CustomerCard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Фабрика с зависимостями
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/order/order.factory.ts
|
||||||
|
import type { OrderFactory } from './types/order-factory.type'
|
||||||
|
|
||||||
|
export const orderFactory: OrderFactory = (deps) => {
|
||||||
|
return {
|
||||||
|
useOrder,
|
||||||
|
OrderCard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Композиция на уровне screen
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// screens/home/home.screen.tsx
|
||||||
|
import { customerFactory } from '@/business/customer'
|
||||||
|
import { orderFactory } from '@/business/order'
|
||||||
|
|
||||||
|
const customer = customerFactory()
|
||||||
|
const order = orderFactory({ customer })
|
||||||
|
|
||||||
|
const { useOrder, OrderCard } = order
|
||||||
|
|
||||||
|
export const HomeScreen = () => {
|
||||||
|
const currentOrder = useOrder()
|
||||||
|
|
||||||
|
return <OrderCard order={currentOrder} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Жизненный цикл
|
||||||
|
|
||||||
|
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
|
||||||
|
|
||||||
|
- Нужен на одной странице → `screens/{name}/parts/`
|
||||||
|
- Появился в 2+ местах → поднимается по природе:
|
||||||
|
- абстрактный UI → `ui/`
|
||||||
|
- блок с данными/логикой → `widgets/`
|
||||||
|
- представление бизнес-домена → `business/{area}/parts/`
|
||||||
|
|
||||||
|
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
|
||||||
176
docs/docs/basics/architecture/segments.md
Normal file
176
docs/docs/basics/architecture/segments.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# Сегменты
|
||||||
|
|
||||||
|
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
|
||||||
|
|
||||||
|
## Определение
|
||||||
|
|
||||||
|
**Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.**
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
| Сегмент | Содержимое |
|
||||||
|
|---------|------------|
|
||||||
|
| `ui/` | Презентационные компоненты родительского модуля |
|
||||||
|
| `parts/` | Вложенные модули со своими сегментами |
|
||||||
|
| `hooks/` | React-хуки |
|
||||||
|
| `stores/` | Сторы состояния |
|
||||||
|
| `services/` | Работа с внешними источниками данных |
|
||||||
|
| `mappers/` | Трансформация данных между форматами |
|
||||||
|
| `types/` | TypeScript-типы и интерфейсы |
|
||||||
|
| `styles/` | Стили |
|
||||||
|
| `lib/` | Утилиты и хелперы модуля |
|
||||||
|
| `config/` | Константы и конфигурация |
|
||||||
|
|
||||||
|
## Сегмент ui/
|
||||||
|
|
||||||
|
Презентационные компоненты родительского модуля. `ui/` содержит только компоненты, которые отвечают за отображение части интерфейса и не выходят за границы своего модуля.
|
||||||
|
|
||||||
|
Компонент в `ui/`:
|
||||||
|
|
||||||
|
- Находится в собственной папке.
|
||||||
|
- Может содержать только `{name}.tsx`, `index.ts`, `styles/`, `types/`.
|
||||||
|
- Не содержит любые компоненты.
|
||||||
|
- Не содержит любые модули.
|
||||||
|
- Не импортирует код проекта за пределами родительского модуля.
|
||||||
|
- Не делает внешние запросы.
|
||||||
|
- Не вызывает сценарные хуки.
|
||||||
|
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
|
||||||
|
- Не содержит бизнес-логику или сценарную логику.
|
||||||
|
|
||||||
|
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](./modules.md#компонент).
|
||||||
|
|
||||||
|
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
user/
|
||||||
|
├── ui/
|
||||||
|
│ ├── user-avatar/
|
||||||
|
│ │ ├── user-avatar.tsx
|
||||||
|
│ │ ├── styles/
|
||||||
|
│ │ │ └── user-avatar.module.css
|
||||||
|
│ │ ├── types/
|
||||||
|
│ │ │ └── user-avatar-props.type.ts
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ └── user-status/
|
||||||
|
│ ├── user-status.tsx
|
||||||
|
│ └── index.ts
|
||||||
|
├── types/
|
||||||
|
├── hooks/
|
||||||
|
├── user.tsx
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Если UI-сущности нужна внутренняя декомпозиция, сценарная логика, получение данных или собственные архитектурные зависимости — это уже не компонент в `ui/`, а модуль в `parts/`.
|
||||||
|
|
||||||
|
## Сегмент parts/
|
||||||
|
|
||||||
|
Вложенные модули со своими сегментами. `parts/` содержит только модули: каждый элемент `parts/` — папка полноценного модуля с собственным публичным API. Отдельные `.tsx`, стили, хуки или произвольные файлы в `parts/` не размещаются.
|
||||||
|
|
||||||
|
```text
|
||||||
|
home/
|
||||||
|
├── parts/
|
||||||
|
│ ├── hero-section/
|
||||||
|
│ │ ├── hero-section.tsx
|
||||||
|
│ │ ├── styles/
|
||||||
|
│ │ ├── parts/
|
||||||
|
│ │ │ └── top-banner/
|
||||||
|
│ │ │ ├── top-banner.tsx
|
||||||
|
│ │ │ └── index.ts
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ └── features-section/
|
||||||
|
│ ├── features-section.tsx
|
||||||
|
│ ├── hooks/
|
||||||
|
│ └── index.ts
|
||||||
|
├── home.screen.tsx
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Отличие от `ui/`: элемент `parts/` — модульная папка со своими сегментами. Элемент `ui/` — компонент родительского модуля без собственной архитектурной ответственности.
|
||||||
|
|
||||||
|
Вложенность `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
|
||||||
|
```
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Стиль кода
|
title: Стиль кода
|
||||||
|
description: Как оформляется код в проекте.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Стиль кода
|
# Стиль кода
|
||||||
|
|
||||||
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
|
Как оформляется код в проекте.
|
||||||
|
|
||||||
## Отступы
|
## Отступы
|
||||||
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Документирование
|
title: Документирование
|
||||||
|
description: Что и как документировать в коде.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Документирование
|
# Документирование
|
||||||
|
|
||||||
Этот раздел описывает правила документирования кода: когда и как писать
|
Что и как документировать в коде.
|
||||||
комментарии к компонентам, функциям, типам и интерфейсам.
|
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ export const buildCategoryTree = (categories: Category[]): CategoryTree[] => { .
|
|||||||
* - обёртки контента страниц с ограничением ширины
|
* - обёртки контента страниц с ограничением ширины
|
||||||
* - центрирования блоков в лейауте
|
* - центрирования блоков в лейауте
|
||||||
*/
|
*/
|
||||||
export const Container: FC<ContainerProps> = (props) => { ... }
|
export const Container = (props: ContainerProps) => { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
**Плохо**
|
**Плохо**
|
||||||
@@ -90,7 +90,7 @@ export const Container: FC<ContainerProps> = (props) => { ... }
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Плохо: нет описания вообще.
|
// Плохо: нет описания вообще.
|
||||||
export const Container: FC<ContainerProps> = (props) => { ... }
|
export const Container = (props: ContainerProps) => { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Типы, интерфейсы, enum
|
## Типы, интерфейсы, enum
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Именование
|
title: Именование
|
||||||
|
description: Как называть переменные, файлы и прочие сущности в коде.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Именование
|
# Именование
|
||||||
|
|
||||||
Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту.
|
Как называть переменные, файлы и прочие сущности в коде.
|
||||||
|
|
||||||
## Базовые правила
|
## Базовые правила
|
||||||
|
|
||||||
@@ -33,6 +34,12 @@ title: Именование
|
|||||||
- `.store.ts` — стор
|
- `.store.ts` — стор
|
||||||
- `.service.ts` — сервис
|
- `.service.ts` — сервис
|
||||||
|
|
||||||
|
**Корневые компоненты слоёв**
|
||||||
|
- `.screen.tsx` — корневой компонент screen-модуля: `screens/profile/profile.screen.tsx`, компонент `ProfileScreen`
|
||||||
|
- `.layout.tsx` — корневой компонент layout-модуля: `layouts/main/main.layout.tsx`, компонент `MainLayout`
|
||||||
|
|
||||||
|
Обычные и вложенные модули не получают суффикс слоя: `ui/button/button.tsx`, `screens/profile/parts/activity-feed/activity-feed.tsx`.
|
||||||
|
|
||||||
**Типы и контракты**
|
**Типы и контракты**
|
||||||
- `.type.ts` — типы и интерфейсы
|
- `.type.ts` — типы и интерфейсы
|
||||||
- `.interface.ts` — интерфейсы
|
- `.interface.ts` — интерфейсы
|
||||||
@@ -53,7 +60,7 @@ title: Именование
|
|||||||
|
|
||||||
**Хорошо**
|
**Хорошо**
|
||||||
```text
|
```text
|
||||||
features/
|
business/
|
||||||
└── auth-by-email/
|
└── auth-by-email/
|
||||||
├── ui/
|
├── ui/
|
||||||
│ └── login-form.tsx
|
│ └── login-form.tsx
|
||||||
@@ -62,14 +69,14 @@ features/
|
|||||||
├── stores/
|
├── stores/
|
||||||
│ └── auth.store.ts
|
│ └── auth.store.ts
|
||||||
├── types/
|
├── types/
|
||||||
│ └── auth.interface.ts
|
│ └── auth.type.ts
|
||||||
├── auth-by-email.feature.tsx
|
├── auth-by-email.tsx
|
||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
**Плохо**
|
**Плохо**
|
||||||
```text
|
```text
|
||||||
features/
|
business/
|
||||||
└── authByEmail/
|
└── authByEmail/
|
||||||
├── LoginForm.tsx
|
├── LoginForm.tsx
|
||||||
├── useAuth.ts
|
├── useAuth.ts
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Технологии и библиотеки
|
title: Технологии и библиотеки
|
||||||
|
description: Какие библиотеки и инструменты используются в проекте.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Технологии и библиотеки
|
# Технологии и библиотеки
|
||||||
|
|
||||||
Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте.
|
Какие библиотеки и инструменты используются в проекте.
|
||||||
|
|
||||||
## Что используем
|
## Что используем
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ title: Технологии и библиотеки
|
|||||||
- `Next.js` — для продуктовых сайтов.
|
- `Next.js` — для продуктовых сайтов.
|
||||||
|
|
||||||
### Архитектура
|
### Архитектура
|
||||||
- `FSD (Feature-Sliced Design)` — структура проекта и границы модулей. Используется кастомизированная версия — подробнее в разделе [Архитектура](/basics/architecture).
|
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/docs/basics/architecture/).
|
||||||
|
|
||||||
### UI компоненты
|
### UI компоненты
|
||||||
- `Mantine UI` — базовые UI-компоненты.
|
- `Mantine UI` — базовые UI-компоненты.
|
||||||
@@ -1,18 +1,24 @@
|
|||||||
---
|
---
|
||||||
title: Типизация
|
title: Типизация
|
||||||
|
description: Как типизируется код в проекте.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Типизация
|
# Типизация
|
||||||
|
|
||||||
Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с `any`/`unknown`.
|
Как типизируется код в проекте.
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
|
- Указывать типы для параметров компонентов и параметров функций.
|
||||||
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
|
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
|
||||||
- Избегать `any` и `unknown` без необходимости.
|
- Избегать `any` и `unknown` без необходимости.
|
||||||
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
|
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
|
||||||
|
|
||||||
|
## React-компоненты
|
||||||
|
|
||||||
|
- Пропсы компонента типизировать через отдельный `Props`.
|
||||||
|
- Возвращаемый тип компонента не указывать: TypeScript корректно выводит JSX-результат, а явный `ReactElement` сужает допустимые варианты возврата.
|
||||||
|
|
||||||
## Функции
|
## Функции
|
||||||
|
|
||||||
- Для публичных функций указывать возвращаемый тип.
|
- Для публичных функций указывать возвращаемый тип.
|
||||||
51
docs/docs/creating-project/from-template.md
Normal file
51
docs/docs/creating-project/from-template.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
title: Создание проекта из шаблона
|
||||||
|
description: Создание нового проекта на основе готового шаблона.
|
||||||
|
keywords: [создать проект из шаблона, шаблон, template, tiged, degit, клонировать шаблон, эталонный шаблон, быстрый старт, scaffold, новый проект]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Создание проекта из шаблона
|
||||||
|
|
||||||
|
Создание нового проекта на основе готового шаблона.
|
||||||
|
|
||||||
|
## Что внутри
|
||||||
|
|
||||||
|
Шаблон — готовый скелет проекта с применёнными правилами стайлгайда:
|
||||||
|
|
||||||
|
- **Стек:** Next.js (App Router), TypeScript, React.
|
||||||
|
- **Архитектура:** структура папок по SLM, алиасы импортов.
|
||||||
|
- **Качество кода:** Biome (линтер и форматер), настройки VS Code.
|
||||||
|
- **Стили:** PostCSS Modules с плагинами, токены, медиа-брейкпоинты.
|
||||||
|
- **Ассеты:** генерация SVG-спрайтов.
|
||||||
|
- **Кодогенерация:** шаблоны для страниц, компонентов, хуков, сторов.
|
||||||
|
в
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
1. Склонировать шаблон в родительском каталоге будущего проекта:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx tiged git@gromlab.ru:templates/nextjs.git my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
`tiged` копирует снимок репозитория без истории git. Имя каталога (`my-app`) заменяется на нужное.
|
||||||
|
|
||||||
|
2. Установить зависимости:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd my-app
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Проверить сборку:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Сборка должна завершиться без ошибок.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- **Шаблон — источник истины.** Не добавлять, не удалять и не переименовывать файлы шаблона «для приведения к канону»: шаблон уже канонический. Любое несоответствие — баг шаблона, а не проекта.
|
||||||
|
- **Менеджер пакетов — npm.** Отклонение (pnpm, yarn, bun) — только по явному решению с пониманием, что стайлгайд этого не предусматривает.
|
||||||
|
- **Не инициализировать git заново** автоматически. `tiged` намеренно не создаёт `.git/` — решение о репозитории принимает разработчик.
|
||||||
90
docs/docs/creating-project/manual.md
Normal file
90
docs/docs/creating-project/manual.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
title: Создание проекта вручную
|
||||||
|
description: Поэтапное создание нового проекта без использования шаблона.
|
||||||
|
keywords: [создать проект, новый проект, с нуля, init, initialize, scaffold, create-next-app, начать проект, поднять проект, эталонный проект, ручная установка]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Создание проекта вручную
|
||||||
|
|
||||||
|
Поэтапное создание нового проекта без использования шаблона.
|
||||||
|
|
||||||
|
## Состав эталонного проекта
|
||||||
|
|
||||||
|
| Компонент | Роль | Раздел |
|
||||||
|
|-----------|------|--------|
|
||||||
|
| Next.js | Фреймворк (роутинг, сборка, SSR) | [Next.js](/docs/creating-project/nextjs) |
|
||||||
|
| Алиасы | Импорты по слоям SLM | [Алиасы](/docs/applied/aliases) |
|
||||||
|
| Biome | Линтер и форматтер (замена ESLint + Prettier) | [Biome](/docs/applied/biome) |
|
||||||
|
| Стили | Глобальные токены и breakpoints | [Стили](/docs/applied/styles/styles-setup) |
|
||||||
|
| PostCSS | CSS-процессор для custom-media и вложенности | [PostCSS](/docs/applied/postcss) |
|
||||||
|
| SVG-спрайты | Иконки через `<SvgSprite/>`, управление цветом | [SVG-спрайты](/docs/applied/svg-sprites/svg-sprites-setup) |
|
||||||
|
| VS Code | Настройки редактора и расширения | [VS Code](/docs/applied/vscode) |
|
||||||
|
| Шаблоны генерации | `.templates/` для `@gromlab/create` | [Шаблоны генерации](/docs/applied/templates/templates-setup) |
|
||||||
|
|
||||||
|
Убрать компонент из состава — значит согласованно отказаться от части стайлгайда. Частичные проекты возможны (только Next.js, Next.js + стили и т.п.), но не являются эталоном.
|
||||||
|
|
||||||
|
## Канон раскладки
|
||||||
|
|
||||||
|
В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](/docs/applied/project-structure), [Архитектура](/docs/basics/architecture/)).
|
||||||
|
|
||||||
|
В частности: `src/app/` содержит только файлы роутинга Next.js и инициализации, без каталогов `styles/`, `assets/`, `components/`.
|
||||||
|
|
||||||
|
## Порядок установки
|
||||||
|
|
||||||
|
Подсистемы ставятся в фиксированном порядке — он отражает зависимости между шагами.
|
||||||
|
|
||||||
|
### 1. Next.js
|
||||||
|
|
||||||
|
Скелет фреймворка — обязательный первый шаг, остальное опирается на него.
|
||||||
|
|
||||||
|
См. [Next.js](/docs/creating-project/nextjs). После выполнения проверки этого раздела `npm run build` должен проходить.
|
||||||
|
|
||||||
|
### 2. Алиасы
|
||||||
|
|
||||||
|
Заменить дефолтный `"@/*"` в `tsconfig.json` на канонический список из восьми слой-префиксов.
|
||||||
|
|
||||||
|
См. [Алиасы](/docs/applied/aliases).
|
||||||
|
|
||||||
|
### 3. Biome
|
||||||
|
|
||||||
|
Линтер и форматтер. Подключается **до** написания кода, иначе в проекте копятся несогласованные правки.
|
||||||
|
|
||||||
|
См. [Biome](/docs/applied/biome).
|
||||||
|
|
||||||
|
### 4. Стили (базовая инфраструктура)
|
||||||
|
|
||||||
|
Файлы `variables.css`, `media.css`, `global.css` в `src/shared/styles/` и подключение `global.css` в `src/app/layout.tsx`. CSS-процессор на этом шаге не ставится.
|
||||||
|
|
||||||
|
См. [Стили](/docs/applied/styles/styles-setup).
|
||||||
|
|
||||||
|
### 5. PostCSS
|
||||||
|
|
||||||
|
CSS-процессор поверх базовых стилей: `@custom-media`, вложенность, autoprefixer. Ставится **только после шага 4** — опирается на `src/shared/styles/media.css`.
|
||||||
|
|
||||||
|
См. [PostCSS](/docs/applied/postcss).
|
||||||
|
|
||||||
|
### 6. SVG-спрайты
|
||||||
|
|
||||||
|
Пакет `@gromlab/svg-sprites`, генерация спрайт-файла и React-компонента `<SvgSprite/>`.
|
||||||
|
|
||||||
|
См. [SVG-спрайты](/docs/applied/svg-sprites/svg-sprites-setup).
|
||||||
|
|
||||||
|
### 7. VS Code
|
||||||
|
|
||||||
|
Расширения и настройки редактора. Опирается на установленный Biome (форматирование при сохранении) и PostCSS (ассоциация `*.css`).
|
||||||
|
|
||||||
|
См. [VS Code](/docs/applied/vscode).
|
||||||
|
|
||||||
|
### 8. Шаблоны генерации
|
||||||
|
|
||||||
|
Папка `.templates/` для генератора модулей `@gromlab/create`.
|
||||||
|
|
||||||
|
См. [Шаблоны генерации](/docs/applied/templates/templates-setup).
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- **Порядок шагов фиксирован.** Перестановка ломает зависимости (PostCSS требует базовых стилей, VS Code — установленного Biome).
|
||||||
|
- **Между шагами обязательна проверка** из соответствующего раздела. Не переходить дальше, пока чеклист текущего шага не пройден.
|
||||||
|
- **Слои `src/`** (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) не создавать авансом. Появляются по мере первого модуля. Исключения — `src/app/` (создаётся `create-next-app`), `src/shared/styles/` (шаг 1) и `src/shared/sprites/icons/` (шаг 6).
|
||||||
|
- **Посторонние каталоги в `src/`** (`assets/`, `utils/`, `lib/`, `components/` и т.п.) — запрещены.
|
||||||
|
- **Подмножество шагов допустимо.** Можно ставить только Next.js и часть инструментов; полный набор — это эталон, а не обязательство.
|
||||||
112
docs/docs/creating-project/nextjs.md
Normal file
112
docs/docs/creating-project/nextjs.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
title: Чистая установка Next.js
|
||||||
|
description: "Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку."
|
||||||
|
keywords: [next.js, create-next-app, npx, установка, инициализация, фреймворк, скаффолдинг, app router, typescript]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Чистая установка Next.js
|
||||||
|
|
||||||
|
Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Node.js 18.18+ (рекомендуется LTS 20+).
|
||||||
|
- npm 10+.
|
||||||
|
- Рабочая папка пуста, либо для установки выбрана подпапка (`create-next-app@16+` отказывается ставиться в непустую директорию).
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
### 1. Инициализация через `create-next-app`
|
||||||
|
|
||||||
|
Флаги зафиксированы и не согласовываются — это канон стайлгайда:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx create-next-app@latest my-app \
|
||||||
|
--typescript \
|
||||||
|
--app \
|
||||||
|
--src-dir \
|
||||||
|
--import-alias "@/*" \
|
||||||
|
--no-eslint \
|
||||||
|
--no-tailwind \
|
||||||
|
--use-npm
|
||||||
|
```
|
||||||
|
|
||||||
|
| Флаг | Значение | Почему так |
|
||||||
|
|------|----------|------------|
|
||||||
|
| `--typescript` | TS включён | Стайлгайд требует TypeScript ([Типизация](/docs/basics/typing)) |
|
||||||
|
| `--app` | App Router | Pages Router не используется |
|
||||||
|
| `--src-dir` | Код в `src/` | Архитектура SLM требует `src/` ([Структура проекта](/docs/applied/project-structure)) |
|
||||||
|
| `--import-alias "@/*"` | Placeholder | Требуется флагом; после установки `paths` полностью переписывается на слой-префиксы (см. [Алиасы](/docs/applied/aliases)) |
|
||||||
|
| `--no-eslint` | ESLint не ставится | Линтер и форматтер — Biome ([Biome](/docs/applied/biome)) |
|
||||||
|
| `--no-tailwind` | Tailwind не ставится | Стилизация — PostCSS Modules ([Стили](/docs/applied/styles/styles-usage)) |
|
||||||
|
| `--use-npm` | Пакетный менеджер — npm | Единый инструмент в проектах |
|
||||||
|
|
||||||
|
### 2. Очистить дефолтный шаблон
|
||||||
|
|
||||||
|
`create-next-app` генерирует демо-страницу со стилями и ассетами, а Next.js 16+ дополнительно кладёт в корень собственные `AGENTS.md` и `CLAUDE.md` — всё это удаляется.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm src/app/page.module.css
|
||||||
|
rm src/app/globals.css
|
||||||
|
rm public/next.svg public/vercel.svg public/file.svg public/globe.svg public/window.svg
|
||||||
|
rm -f AGENTS.md CLAUDE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Заменить `src/app/page.tsx` на минимальный:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/page.tsx
|
||||||
|
export default function HomePage() {
|
||||||
|
return <h1>Home</h1>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Очистить `src/app/layout.tsx` от импорта шрифтов и `globals.css`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/layout.tsx
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'App',
|
||||||
|
description: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="ru">
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Создать папку `src/shared/styles/`
|
||||||
|
|
||||||
|
Глобальные стили в SLM-архитектуре живут в слое `shared`, а не в `src/app/` ([Структура проекта](/docs/applied/project-structure)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p src/shared/styles
|
||||||
|
```
|
||||||
|
|
||||||
|
Остальные слои (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) заводятся при появлении первого модуля в них. `src/shared/styles/` — единственный подкаталог `shared/`, который заводится сразу: без него не настроить стили на следующих шагах.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- **Конфликт с непустой директорией** — не удалять файлы пользователя автоматически. Ставить в подпапку или временно перенести посторонние файлы.
|
||||||
|
- **Отклонение от канонических флагов** (pnpm, Tailwind, ESLint и т.п.) — только осознанное, с пониманием, что стайлгайд этого не предусматривает.
|
||||||
|
- **Слои `src/`** не создавать авансом — появляются при первом модуле. Алиасы прописываются сразу на все восемь слоёв (см. [Алиасы](/docs/applied/aliases)).
|
||||||
|
- **`AGENTS.md` от Next.js** удаляется в шаге 2. Повторно не создаётся.
|
||||||
|
- **Biome, стили, PostCSS, SVG-спрайты, VS Code** — отдельные шаги установки, не в этом разделе.
|
||||||
|
|
||||||
|
## Проверка установки
|
||||||
|
|
||||||
|
- В корне проекта: `next.config.ts`, `tsconfig.json`, `package.json`.
|
||||||
|
- В `package.json`: Next.js установлен, нет `eslint`, `tailwindcss`.
|
||||||
|
- В `src/app/` присутствуют минимальные `page.tsx` и `layout.tsx`. `globals.css`, `page.module.css` отсутствуют. Каталогов `styles/`, `assets/`, `providers/`, `components/` в `src/app/` нет.
|
||||||
|
- Папка `src/shared/styles/` создана (пустая).
|
||||||
|
- В `src/` из слоёв SLM присутствуют только `app/` и `shared/` (с `styles/`). Посторонних каталогов нет.
|
||||||
|
- В `public/` удалены `next.svg`, `vercel.svg`, `file.svg`, `globe.svg`, `window.svg`.
|
||||||
|
- В корне проекта нет `AGENTS.md` и `CLAUDE.md` от Next.js.
|
||||||
|
- `npm run build` завершается успешно.
|
||||||
|
- Пакетный менеджер — npm (нет `pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`).
|
||||||
60
docs/docs/data/index.md
Normal file
60
docs/docs/data/index.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: Источники данных
|
||||||
|
description: Какие источники данных используются в проекте и как с ними работать.
|
||||||
|
keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, введение, карта раздела]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Источники данных
|
||||||
|
|
||||||
|
Какие источники данных используются в проекте и как с ними работать.
|
||||||
|
|
||||||
|
## Принципы раздела
|
||||||
|
|
||||||
|
- **Клиент — в `infrastructure/`.** Каждый внешний сервис — отдельный модуль слоя `infrastructure/{service-name}/`.
|
||||||
|
- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
|
||||||
|
- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
|
||||||
|
- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые GET-хуки REST-клиента (`useGetUserList`, `useGetPostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
|
||||||
|
|
||||||
|
## Карта раздела
|
||||||
|
|
||||||
|
### REST
|
||||||
|
|
||||||
|
Канал «запрос-ответ» по HTTP. Покрывает большинство API.
|
||||||
|
|
||||||
|
- [REST](/docs/data/rest/) — обзор раздела: создание клиента и использование.
|
||||||
|
- **Создание клиента** — как оформляется REST API в проекте:
|
||||||
|
- [Обзор](/docs/data/rest/clients/) — когда нужен клиент и как выбрать подход.
|
||||||
|
- [Автогенерация из OpenAPI](/docs/data/rest/clients/auto) — для API с OpenAPI-спецификацией, через `@gromlab/api-codegen`.
|
||||||
|
- [Ручное создание](/docs/data/rest/clients/manual) — для API без схемы, клиент пишется и поддерживается руками.
|
||||||
|
- [GET-хуки REST-клиента](/docs/data/rest/clients/hooks) — прозрачные SWR-обёртки над GET-методами клиента.
|
||||||
|
- **Использование** — как получать данные через готовый клиент:
|
||||||
|
- [Стратегии получения данных](/docs/data/rest/strategies/) — как выбрать способ получения данных под ситуацию.
|
||||||
|
- [Серверный await](/docs/data/rest/strategies/server-await) — прямой `await` метода клиента в Server Components.
|
||||||
|
- [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests) — запуск независимых серверных запросов без waterfall.
|
||||||
|
- [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down) — серверный стриминг через промис и `Suspense`.
|
||||||
|
- [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data) — серверный промис в `SWRConfig fallback`.
|
||||||
|
- [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook) — получение данных в Client Components через готовый GET-хук.
|
||||||
|
- [Business-композиция](/docs/data/rest/strategies/business-composition) — доменная интерпретация и композиция REST-данных.
|
||||||
|
|
||||||
|
### Realtime
|
||||||
|
|
||||||
|
Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
|
||||||
|
|
||||||
|
- [Realtime](/docs/data/realtime) — клиент realtime в `infrastructure/`, потребление через `useSWRSubscription` или прямые подписки.
|
||||||
|
|
||||||
|
## Что даёт раздел
|
||||||
|
|
||||||
|
После прочтения раздела понятно:
|
||||||
|
|
||||||
|
- Где живёт код работы с API и почему именно там.
|
||||||
|
- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
|
||||||
|
- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infrastructure/{service-name}/hooks/`.
|
||||||
|
- Как выбрать стратегию получения REST-данных под конкретную ситуацию.
|
||||||
|
- Как подключать realtime-источники в общую модель работы с данными.
|
||||||
|
- Какие правила обязательны и какие отклонения допустимы.
|
||||||
|
|
||||||
|
## Что не входит в раздел
|
||||||
|
|
||||||
|
- **Глобальное состояние UI** — Stores, формы, фичефлаги. Это [Stores](/docs/applied/stores).
|
||||||
|
- **Доменная логика** — как данные превращаются в сценарии бизнеса. Это слой `business/` в [Архитектуре](/docs/basics/architecture/).
|
||||||
|
- **Хуки общего назначения** — переиспользуемые хуки UI, не привязанные к конкретному API. Отдельный прикладной раздел для них пока не ведётся.
|
||||||
79
docs/docs/data/realtime.md
Normal file
79
docs/docs/data/realtime.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
title: Realtime
|
||||||
|
description: "Работа с push-данными от сервера: подписки и события."
|
||||||
|
keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRSubscription, push, события]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Realtime
|
||||||
|
|
||||||
|
Работа с push-данными от сервера: подписки и события.
|
||||||
|
|
||||||
|
## Принципы
|
||||||
|
|
||||||
|
- **Клиент realtime — в `infrastructure/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения.
|
||||||
|
- **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт.
|
||||||
|
- **Использование на клиенте — два сценария:**
|
||||||
|
- **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST.
|
||||||
|
- **Прямая подписка** — для побочных эффектов (тосты, нотификации, аналитика), не привязанных к рендеру.
|
||||||
|
|
||||||
|
## Размещение клиента
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/
|
||||||
|
└── {channel-name}/
|
||||||
|
├── connection.ts # установление соединения, реконнект
|
||||||
|
├── subscribe.ts # subscribe(topic, handler) → unsubscribe
|
||||||
|
├── types.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Использование через SWR
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import useSWRSubscription from 'swr/subscription'
|
||||||
|
import { subscribe } from 'infrastructure/notifications'
|
||||||
|
|
||||||
|
export function NotificationCounter() {
|
||||||
|
const { data: count } = useSWRSubscription(
|
||||||
|
['notifications', 'count'],
|
||||||
|
(key, { next }) =>
|
||||||
|
subscribe('notifications.count', (value: number) => next(null, value)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return <span>{count ?? 0}</span>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Плюсы: кеш и дедупликация подписки между несколькими местами рендера; единая модель данных с REST.
|
||||||
|
|
||||||
|
## Прямая подписка
|
||||||
|
|
||||||
|
Для побочных эффектов, которые не влияют на состояние UI напрямую:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { subscribe } from 'infrastructure/notifications'
|
||||||
|
import { showToast } from 'ui/toast'
|
||||||
|
|
||||||
|
export function NotificationsToaster() {
|
||||||
|
useEffect(() => {
|
||||||
|
return subscribe('notifications.new', (notification) => {
|
||||||
|
showToast(notification.message)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Возврат `unsubscribe` из `useEffect` обязателен — иначе утечка подписки.
|
||||||
|
|
||||||
|
## Запрет прямых соединений
|
||||||
|
|
||||||
|
Создавать `new WebSocket(...)`, `new EventSource(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infrastructure/`.
|
||||||
|
|
||||||
|
Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.
|
||||||
193
docs/docs/data/rest/clients/auto.md
Normal file
193
docs/docs/data/rest/clients/auto.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
---
|
||||||
|
title: Автогенерация из OpenAPI
|
||||||
|
description: Генерация REST-клиента из OpenAPI-спецификации через @gromlab/api-codegen.
|
||||||
|
keywords: [rest, openapi, api-codegen, автогенерация, generated, npx]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Автогенерация из OpenAPI
|
||||||
|
|
||||||
|
Автогенерация используется, когда у API есть актуальная OpenAPI-спецификация. Генератор создаёт TypeScript-клиент, типы и методы API, а разработчик вручную добавляет настройку клиента и GET-хуки.
|
||||||
|
|
||||||
|
## Пример API
|
||||||
|
|
||||||
|
В примерах используется Swagger Petstore:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://petstore3.swagger.io/api/v3/openapi.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Имена модуля:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/pet-store-api/
|
||||||
|
petStoreApi
|
||||||
|
pet-store-api.generated.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Скрипт генерации
|
||||||
|
|
||||||
|
`@gromlab/api-codegen` не устанавливается в `devDependencies`. Используем `npx @gromlab/api-codegen@latest`, чтобы запускать свежую версию.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infrastructure/pet-store-api/generated -n pet-store-api.generated"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Параметры:
|
||||||
|
|
||||||
|
- `-i` — путь к OpenAPI-спецификации: URL или локальный файл.
|
||||||
|
- `-o` — директория для сгенерированного файла.
|
||||||
|
- `-n` — имя сгенерированного файла без `.ts`.
|
||||||
|
|
||||||
|
Ключ `--swr` не используется. GET-хуки REST-клиента пишутся вручную, чтобы сохранить проектный контракт: один GET-хук = один GET-метод, без бизнес-логики и композиции.
|
||||||
|
|
||||||
|
## Генерация
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run codegen:pet-store-api
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/pet-store-api/generated/
|
||||||
|
└── pet-store-api.generated.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Сгенерированный файл не правится руками и коммитится в репозиторий.
|
||||||
|
|
||||||
|
## Проверка методов
|
||||||
|
|
||||||
|
После генерации откройте `generated/pet-store-api.generated.ts` и проверьте фактические имена методов.
|
||||||
|
|
||||||
|
Для Petstore нужны GET-операции вида:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
petStoreApi.pet.findPetsByStatus(...)
|
||||||
|
petStoreApi.pet.getPetById(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Точные сигнатуры зависят от OpenAPI-схемы и версии генератора. В рабочих задачах всегда сверяйтесь с generated-файлом.
|
||||||
|
|
||||||
|
## `client.ts`
|
||||||
|
|
||||||
|
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-store-api/client.ts
|
||||||
|
import { Api, HttpClient } from './generated/pet-store-api.generated'
|
||||||
|
|
||||||
|
const httpClient = new HttpClient({
|
||||||
|
baseUrl: 'https://petstore3.swagger.io/api/v3',
|
||||||
|
baseApiParams: {
|
||||||
|
secure: false,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const petStoreApi = new Api(httpClient)
|
||||||
|
```
|
||||||
|
|
||||||
|
В реальном проекте вместо строки `baseUrl` обычно берётся из env-переменной, нормализуется в `client.ts` и передаётся в `HttpClient` через конфиг.
|
||||||
|
|
||||||
|
`client.ts` не содержит расширения типов, `declare module` и `Extended`-типы. Он только настраивает транспорт и экспортирует инстанс клиента.
|
||||||
|
|
||||||
|
## Расширение сгенерированных типов
|
||||||
|
|
||||||
|
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/biocad-less-api/
|
||||||
|
├── generated/
|
||||||
|
│ └── biocad-less-api.generated.ts
|
||||||
|
├── types/
|
||||||
|
│ ├── term.ts
|
||||||
|
│ └── index.ts
|
||||||
|
├── client.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример расширения generated-типа:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/biocad-less-api/types/term.ts
|
||||||
|
import type { TermRecordItem } from '../generated/biocad-less-api.generated'
|
||||||
|
|
||||||
|
declare module '../generated/biocad-less-api.generated' {
|
||||||
|
interface TermRecordItem {
|
||||||
|
media?: {
|
||||||
|
file?: string
|
||||||
|
title?: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TermRecordItemExtended = Omit<
|
||||||
|
TermRecordItem,
|
||||||
|
'categories' | 'tags' | 'fields'
|
||||||
|
> & {
|
||||||
|
categories?: Array<{
|
||||||
|
_id?: string
|
||||||
|
id?: string
|
||||||
|
slug?: string
|
||||||
|
name?: string
|
||||||
|
}>
|
||||||
|
tags?: Array<{
|
||||||
|
_id?: string
|
||||||
|
id?: string
|
||||||
|
slug?: string
|
||||||
|
name?: string
|
||||||
|
}>
|
||||||
|
fields?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/biocad-less-api/types/index.ts
|
||||||
|
export type { TermRecordItemExtended } from './term'
|
||||||
|
```
|
||||||
|
|
||||||
|
`declare module` используется для добавления отсутствующих полей в generated-интерфейс. `Extended`-тип используется, когда нужно переопределить неточные поля, не трогая generated-файл.
|
||||||
|
|
||||||
|
## Публичный API
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-store-api/index.ts
|
||||||
|
export { petStoreApi } from './client'
|
||||||
|
export type { Pet } from './generated/pet-store-api.generated'
|
||||||
|
export * from './hooks'
|
||||||
|
```
|
||||||
|
|
||||||
|
Наружу импортируют только из `infrastructure/pet-store-api`, не из `generated/`.
|
||||||
|
|
||||||
|
Если у модуля есть расширенные типы, они тоже реэкспортируются через `index.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/biocad-less-api/index.ts
|
||||||
|
export type { TermRecordItemExtended } from './types'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Регенерация
|
||||||
|
|
||||||
|
При изменении OpenAPI-схемы:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run codegen:pet-store-api
|
||||||
|
```
|
||||||
|
|
||||||
|
Что меняется:
|
||||||
|
|
||||||
|
- `generated/pet-store-api.generated.ts` — перезаписывается генератором.
|
||||||
|
- `client.ts`, `hooks/`, `types/`, `index.ts` — не трогаются автоматически.
|
||||||
|
|
||||||
|
Если после регенерации поменялись сигнатуры методов или типы, это исправляется в ручном коде модуля.
|
||||||
|
|
||||||
|
## Следующий шаг
|
||||||
|
|
||||||
|
После генерации и настройки `client.ts` проверьте серверный вызов метода клиента или добавьте [GET-хук REST-клиента](/docs/data/rest/clients/hooks) для Client Components.
|
||||||
206
docs/docs/data/rest/clients/hooks.md
Normal file
206
docs/docs/data/rest/clients/hooks.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
---
|
||||||
|
title: GET-хуки REST-клиента
|
||||||
|
description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
||||||
|
keywords: [rest, swr, get-хуки, client components, infrastructure]
|
||||||
|
---
|
||||||
|
|
||||||
|
# GET-хуки REST-клиента
|
||||||
|
|
||||||
|
GET-хуки REST-клиента — прозрачные SWR-обёртки над GET-методами API-клиента. Они нужны, чтобы Client Components получали данные с кешированием, дедупликацией и ревалидацией, не работая с `useSWR` напрямую.
|
||||||
|
|
||||||
|
## Где лежат
|
||||||
|
|
||||||
|
GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/
|
||||||
|
└── pet-store-api/
|
||||||
|
├── client.ts
|
||||||
|
├── generated/
|
||||||
|
├── hooks/
|
||||||
|
│ ├── use-get-pet-list.hook.ts
|
||||||
|
│ ├── use-get-pet-detail.hook.ts
|
||||||
|
│ └── index.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Контракт
|
||||||
|
|
||||||
|
- Один GET-хук = один GET-метод клиента.
|
||||||
|
- Имя GET-хука начинается с `useGet`: `useGetPetList`, `useGetPetDetail`.
|
||||||
|
- Имя файла начинается с `use-get`: `use-get-pet-list.hook.ts`.
|
||||||
|
- Хук принимает только параметры GET-метода и `config?: SWRConfiguration`.
|
||||||
|
- Что передали хуку, то он передаёт в GET-метод.
|
||||||
|
- Внутри только SWR-механика: key, fetcher, `useSWR`, `config`.
|
||||||
|
- Хук возвращает тип ответа API: generated-тип или DTO из `types/`.
|
||||||
|
- Хук не объединяет несколько запросов.
|
||||||
|
- Хук не маппит DTO в доменную модель.
|
||||||
|
- Хук не вычисляет бизнес-флаги: `isAuth`, `canEdit`, `hasAccess`, `hasPets`.
|
||||||
|
- Хук не вызывает тосты, модалки, редиректы и не пишет UI-состояние.
|
||||||
|
|
||||||
|
## Пример списка
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import type { SWRConfiguration } from 'swr'
|
||||||
|
import { petStoreApi } from '../client'
|
||||||
|
import type { Pet } from '../generated/pet-store-api.generated'
|
||||||
|
|
||||||
|
export type PetStatus = 'available' | 'pending' | 'sold'
|
||||||
|
|
||||||
|
export const getPetListKey = (status: PetStatus) =>
|
||||||
|
['pet-store-api', 'pet', 'list', status] as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение списка питомцев по статусу.
|
||||||
|
*/
|
||||||
|
export const useGetPetList = (status: PetStatus | null, config?: SWRConfiguration) => {
|
||||||
|
const isReady = status !== null
|
||||||
|
const key = isReady ? getPetListKey(status) : null
|
||||||
|
const fetcher = () => petStoreApi.pet.findPetsByStatus({ status })
|
||||||
|
|
||||||
|
return useSWR<Pet[]>(key, fetcher, config)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Функция `getPetListKey` нужна, чтобы один и тот же SWR-ключ использовался внутри GET-хука и при передаче начальных данных через `SWRConfig fallback`.
|
||||||
|
|
||||||
|
Пример начальных данных для клиентского хука:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { SWRConfig, unstable_serialize } from 'swr'
|
||||||
|
import {
|
||||||
|
getPetListKey,
|
||||||
|
petStoreApi,
|
||||||
|
} from 'infrastructure/pet-store-api'
|
||||||
|
|
||||||
|
export default function PetsLayout({ children }: { children: ReactNode }) {
|
||||||
|
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SWRConfig
|
||||||
|
value={{
|
||||||
|
fallback: {
|
||||||
|
[unstable_serialize(getPetListKey('available'))]: petsPromise,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SWRConfig>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Клиентский компонент при этом ничего не знает про preload/fallback и продолжает вызывать обычный хук:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const { data: pets } = useGetPetList('available')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Пример detail-запроса
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-store-api/hooks/use-get-pet-detail.hook.ts
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import type { SWRConfiguration } from 'swr'
|
||||||
|
import { petStoreApi } from '../client'
|
||||||
|
import type { Pet } from '../generated/pet-store-api.generated'
|
||||||
|
|
||||||
|
export const getPetDetailKey = (id: number) =>
|
||||||
|
['pet-store-api', 'pet', 'detail', id] as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение питомца по идентификатору.
|
||||||
|
*/
|
||||||
|
export const useGetPetDetail = (id: number | null, config?: SWRConfiguration) => {
|
||||||
|
const isReady = id !== null
|
||||||
|
const key = isReady ? getPetDetailKey(id) : null
|
||||||
|
const fetcher = () => petStoreApi.pet.getPetById(id)
|
||||||
|
|
||||||
|
return useSWR<Pet>(key, fetcher, config)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Отложенный запрос через `null`
|
||||||
|
|
||||||
|
GET-хук может принимать `null` для обязательного параметра. `null` означает, что параметр ещё не готов и запрос выполнять нельзя.
|
||||||
|
|
||||||
|
Внутри хука это выражается через `isReady`: если параметр не готов, ключ SWR становится `null`, и SWR не вызывает fetcher.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const isReady = id !== null
|
||||||
|
const key = isReady ? getPetDetailKey(id) : null
|
||||||
|
```
|
||||||
|
|
||||||
|
`null` не передаётся в метод клиента. Key-функция принимает только готовые параметры, поэтому её можно безопасно использовать для начальных данных через `SWRConfig fallback`.
|
||||||
|
|
||||||
|
Для числовых идентификаторов не используйте проверку `if (id)`: значение `0` тоже валидное число. Проверяйте явно: `id !== null`.
|
||||||
|
|
||||||
|
## Экспорт
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-store-api/hooks/index.ts
|
||||||
|
export { getPetListKey, useGetPetList } from './use-get-pet-list.hook'
|
||||||
|
export type { PetStatus } from './use-get-pet-list.hook'
|
||||||
|
export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook'
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-store-api/index.ts
|
||||||
|
export { petStoreApi } from './client'
|
||||||
|
export type { Pet } from './generated/pet-store-api.generated'
|
||||||
|
export * from './hooks'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Где заканчивается infrastructure
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Хорошо: infrastructure, прозрачный GET-хук
|
||||||
|
const { data: pets } = useGetPetList('available')
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Хорошо: business, доменная интерпретация
|
||||||
|
export const useAvailablePets = () => {
|
||||||
|
const query = useGetPetList('available')
|
||||||
|
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
hasPets: Boolean(query.data?.length),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`hasPets` — не часть GET-запроса, поэтому он не добавляется в `useGetPetList`.
|
||||||
|
|
||||||
|
## Что запрещено
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Плохо — useSWR в компоненте
|
||||||
|
const { data } = useSWR(
|
||||||
|
['pet-store-api', 'pet', 'list', status],
|
||||||
|
() => petStoreApi.pet.findPetsByStatus({ status }),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Плохо — несколько GET внутри infrastructure-хука
|
||||||
|
export const usePetDashboard = () => {
|
||||||
|
const available = useGetPetList('available')
|
||||||
|
const sold = useGetPetList('sold')
|
||||||
|
|
||||||
|
return { available, sold }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Плохо — бизнес-флаг внутри GET-хука REST-клиента
|
||||||
|
export const useGetPetList = (status: PetStatus) => {
|
||||||
|
const query = useSWR(...)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
hasPets: Boolean(query.data?.length),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробное потребление таких хуков описано в стратегии [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook).
|
||||||
75
docs/docs/data/rest/clients/index.md
Normal file
75
docs/docs/data/rest/clients/index.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
title: Создание клиента
|
||||||
|
description: Из чего состоит REST-клиент и какие части нужно подготовить перед использованием API.
|
||||||
|
keywords: [rest, клиент, infrastructure, методы, openapi, get-хуки, swr]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Создание клиента
|
||||||
|
|
||||||
|
REST-клиент — это infrastructure-модуль, через который проект работает с внешним REST API.
|
||||||
|
|
||||||
|
На этом этапе нужно подготовить клиент сервиса: создать оболочку клиента, получить методы API и добавить GET-хуки для клиентских компонентов.
|
||||||
|
|
||||||
|
## Из чего состоит клиент
|
||||||
|
|
||||||
|
REST-клиент состоит из трёх основных частей:
|
||||||
|
|
||||||
|
1. **Клиент** — самописная оболочка над транспортом.
|
||||||
|
2. **Методы** — сгенерированные из OpenAPI или написанные вручную вызовы API.
|
||||||
|
3. **GET-хуки** — SWR-обёртки для GET-запросов.
|
||||||
|
|
||||||
|
Эти части живут в одном REST-модуле, потому что относятся к одному внешнему сервису.
|
||||||
|
|
||||||
|
## Клиент
|
||||||
|
|
||||||
|
Клиент — ручной слой, который настраивает работу с API: `baseUrl`, заголовки, авторизацию, обработку ошибок и создание инстанса сервиса.
|
||||||
|
|
||||||
|
Даже если методы генерируются из OpenAPI, `client.ts` остаётся ручным файлом проекта.
|
||||||
|
|
||||||
|
`client.ts` — только сборочная точка клиента. В нём не размещаются DTO, `declare module`, `Extended`-типы, GET-хуки и бизнес-логика.
|
||||||
|
|
||||||
|
## Методы
|
||||||
|
|
||||||
|
Методы описывают конкретные запросы к API.
|
||||||
|
|
||||||
|
Они появляются одним из двух способов:
|
||||||
|
|
||||||
|
- генерируются из OpenAPI в `generated/`;
|
||||||
|
- создаются вручную в `methods/`.
|
||||||
|
|
||||||
|
Подробности:
|
||||||
|
|
||||||
|
- [Автогенерация из OpenAPI](/docs/data/rest/clients/auto)
|
||||||
|
- [Ручное создание](/docs/data/rest/clients/manual)
|
||||||
|
|
||||||
|
## GET-хуки
|
||||||
|
|
||||||
|
Для GET-запросов добавляются GET-хуки REST-клиента.
|
||||||
|
|
||||||
|
Это прозрачные SWR-обёртки над GET-методами клиента. Они живут в `hooks/` этого же REST-модуля и нужны для использования данных в Client Components.
|
||||||
|
|
||||||
|
GET-хуки именуются с префиксом `useGet`: `useGetPetList`, `useGetPetDetail`, `useGetCurrentUser`.
|
||||||
|
|
||||||
|
Подробности:
|
||||||
|
|
||||||
|
- [GET-хуки REST-клиента](/docs/data/rest/clients/hooks)
|
||||||
|
|
||||||
|
## Структура модуля
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/{service-name}/
|
||||||
|
├── client.ts # самописная оболочка и инстанс клиента
|
||||||
|
├── generated/ или methods/ # методы API
|
||||||
|
├── hooks/ # GET-хуки REST-клиента
|
||||||
|
├── types/ # DTO, типы API и расширения типов
|
||||||
|
├── errors/ # ошибки API, если нужны
|
||||||
|
└── index.ts # публичный API
|
||||||
|
```
|
||||||
|
|
||||||
|
`index.ts` — единственная точка входа в REST-модуль для внешнего кода.
|
||||||
|
|
||||||
|
## Что делаем дальше
|
||||||
|
|
||||||
|
1. Создайте методы клиента: [Автогенерация из OpenAPI](/docs/data/rest/clients/auto) или [Ручное создание](/docs/data/rest/clients/manual).
|
||||||
|
2. Добавьте GET-хуки для GET-запросов: [GET-хуки REST-клиента](/docs/data/rest/clients/hooks).
|
||||||
|
3. После создания клиента переходите к [Стратегиям получения данных](/docs/data/rest/strategies/).
|
||||||
187
docs/docs/data/rest/clients/manual.md
Normal file
187
docs/docs/data/rest/clients/manual.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
title: Ручное создание
|
||||||
|
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
||||||
|
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infrastructure]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ручное создание
|
||||||
|
|
||||||
|
Ручной REST-клиент используется, когда у API нет OpenAPI-спецификации или она недостаточно точная для автогенерации.
|
||||||
|
|
||||||
|
Задача ручного клиента — дать такую же точку входа, как у автогенерированного клиента: именованный API-объект, методы по сущностям, DTO и GET-хуки рядом с клиентом.
|
||||||
|
|
||||||
|
## Что нужно создать
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/
|
||||||
|
└── pet-project-api/
|
||||||
|
├── methods/
|
||||||
|
│ └── posts.ts
|
||||||
|
├── hooks/
|
||||||
|
│ └── index.ts
|
||||||
|
├── types/
|
||||||
|
│ ├── client.ts
|
||||||
|
│ ├── post.ts
|
||||||
|
│ └── index.ts
|
||||||
|
├── errors/
|
||||||
|
│ └── pet-project-api.error.ts
|
||||||
|
├── client.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
| Файл | Роль |
|
||||||
|
|------|------|
|
||||||
|
| `client.ts` | Базовый транспорт и создание инстанса клиента |
|
||||||
|
| `methods/` | Методы API по сущностям |
|
||||||
|
| `types/` | DTO запросов, ответов и типы клиента |
|
||||||
|
| `errors/` | Ошибки конкретного API |
|
||||||
|
| `hooks/` | GET-хуки REST-клиента, если данные нужны в Client Components |
|
||||||
|
| `index.ts` | Публичный API REST-модуля |
|
||||||
|
|
||||||
|
## DTO и типы API
|
||||||
|
|
||||||
|
DTO запросов и ответов живут в `types/`. `client.ts` не содержит DTO и доменные типы.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/types/post.ts
|
||||||
|
export type PostDto = {
|
||||||
|
id: string
|
||||||
|
slug: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PostListQueryDto = {
|
||||||
|
limit?: number
|
||||||
|
category?: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/types/index.ts
|
||||||
|
export type { PostDto, PostListQueryDto } from './post'
|
||||||
|
```
|
||||||
|
|
||||||
|
Типы, которые нужны только базовому транспорту, можно держать отдельно:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/types/client.ts
|
||||||
|
export type QueryParams = Record<string, string | number | boolean>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ошибка API
|
||||||
|
|
||||||
|
Ошибка API тоже относится к REST-модулю.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/errors/pet-project-api.error.ts
|
||||||
|
export class PetProjectApiError extends Error {
|
||||||
|
constructor(
|
||||||
|
public readonly status: number,
|
||||||
|
message: string,
|
||||||
|
) {
|
||||||
|
super(message)
|
||||||
|
this.name = 'PetProjectApiError'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Базовый клиент
|
||||||
|
|
||||||
|
`client.ts` содержит только транспортную оболочку и сборку инстанса. Прямой `fetch` живёт здесь, а не в компонентах и не в методах верхних слоёв.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/client.ts
|
||||||
|
import { PetProjectApiError } from './errors/pet-project-api.error'
|
||||||
|
import type { QueryParams } from './types/client'
|
||||||
|
|
||||||
|
export class PetProjectApiClient {
|
||||||
|
constructor(
|
||||||
|
private readonly baseUrl: string,
|
||||||
|
private readonly defaultHeaders: Record<string, string> = {},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async get<T>(path: string, params: QueryParams = {}): Promise<T> {
|
||||||
|
const base = `${this.baseUrl.replace(/\/+$/, '')}/`
|
||||||
|
const url = new URL(path.replace(/^\/+/, ''), base)
|
||||||
|
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
url.searchParams.set(key, String(value))
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
...this.defaultHeaders,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new PetProjectApiError(response.status, response.statusText)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Это минимальный шаблон. Авторизация, дополнительные заголовки, `next.revalidate`, `post`, `formdata` и другие детали добавляются только когда они реально нужны API.
|
||||||
|
|
||||||
|
## Методы API
|
||||||
|
|
||||||
|
Методы группируются по сущностям в `methods/`. Они не знают про React, SWR и UI.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/methods/posts.ts
|
||||||
|
import type { PetProjectApiClient } from '../client'
|
||||||
|
import type { PostDto, PostListQueryDto } from '../types/post'
|
||||||
|
|
||||||
|
export function postsMethods(client: PetProjectApiClient) {
|
||||||
|
return {
|
||||||
|
/** GET /posts */
|
||||||
|
list: (query: PostListQueryDto = {}) =>
|
||||||
|
client.get<PostDto[]>('posts', query),
|
||||||
|
|
||||||
|
/** GET /posts/{slug} */
|
||||||
|
get: (slug: string) =>
|
||||||
|
client.get<PostDto>(`posts/${slug}`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Метод возвращает DTO в форме API. Если данным нужен доменный смысл, маппинг делается выше, в `business/`.
|
||||||
|
|
||||||
|
## Публичный API
|
||||||
|
|
||||||
|
`index.ts` собирает именованный API-объект и открывает наружу только публичные части модуля.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/index.ts
|
||||||
|
import { PetProjectApiClient } from './client'
|
||||||
|
import { postsMethods } from './methods/posts'
|
||||||
|
|
||||||
|
const client = new PetProjectApiClient(
|
||||||
|
process.env.NEXT_PUBLIC_API_URL ?? '',
|
||||||
|
{ 'Content-Type': 'application/json' },
|
||||||
|
)
|
||||||
|
|
||||||
|
export const petProjectApi = {
|
||||||
|
posts: postsMethods(client),
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PetProjectApiError } from './errors/pet-project-api.error'
|
||||||
|
export type { PostDto, PostListQueryDto } from './types'
|
||||||
|
export * from './hooks'
|
||||||
|
```
|
||||||
|
|
||||||
|
Внешний код импортирует только из `infrastructure/pet-project-api`, не из внутренних файлов модуля.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- `fetch` используется только внутри базового клиента.
|
||||||
|
- DTO запросов и ответов живут в `types/`.
|
||||||
|
- `client.ts` не содержит DTO, GET-хуки и бизнес-логику.
|
||||||
|
- Методы лежат в `methods/` и возвращают DTO.
|
||||||
|
- GET-хуки добавляются отдельно в `hooks/`, если данные нужны в Client Components.
|
||||||
|
- Доменные типы и маппинг DTO живут не в REST-клиенте, а в `business/`.
|
||||||
|
|
||||||
|
Следующий шаг: [GET-хуки REST-клиента](/docs/data/rest/clients/hooks) или [Стратегии получения данных](/docs/data/rest/strategies/).
|
||||||
74
docs/docs/data/rest/index.md
Normal file
74
docs/docs/data/rest/index.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
title: REST
|
||||||
|
description: Как правильно работать с REST API в проекте.
|
||||||
|
keywords: [rest, api, данные, infrastructure, клиент, swr, стратегии]
|
||||||
|
---
|
||||||
|
|
||||||
|
# REST
|
||||||
|
|
||||||
|
Раздел описывает, как правильно работать с REST API в проекте: создать клиент сервиса и выбрать способ получения данных в приложении.
|
||||||
|
|
||||||
|
REST в проекте проходит через два главных этапа:
|
||||||
|
|
||||||
|
1. Создание клиента.
|
||||||
|
2. Использование.
|
||||||
|
|
||||||
|
## 1. Создание клиента
|
||||||
|
|
||||||
|
На этом этапе внешний API оформляется как модуль слоя `infrastructure/`.
|
||||||
|
|
||||||
|
Клиент отвечает за:
|
||||||
|
|
||||||
|
- генерацию или ручное описание методов API;
|
||||||
|
- настройку `baseUrl`;
|
||||||
|
- заголовки и авторизацию;
|
||||||
|
- обработку ошибок;
|
||||||
|
- кастомизацию и расширение типов;
|
||||||
|
- GET-хуки для клиентских компонентов;
|
||||||
|
- публичный API модуля.
|
||||||
|
|
||||||
|
Если у API есть OpenAPI-спецификация — клиент генерируется автоматически. Если OpenAPI нет или он неполный — клиент создаётся вручную.
|
||||||
|
|
||||||
|
GET-хуки относятся к клиенту, потому что это прозрачные SWR-обёртки над GET-методами этого клиента.
|
||||||
|
|
||||||
|
Подробнее:
|
||||||
|
|
||||||
|
- [Создание клиента](/docs/data/rest/clients/)
|
||||||
|
- [Автогенерация из OpenAPI](/docs/data/rest/clients/auto)
|
||||||
|
- [Ручное создание](/docs/data/rest/clients/manual)
|
||||||
|
- [GET-хуки REST-клиента](/docs/data/rest/clients/hooks)
|
||||||
|
|
||||||
|
## 2. Использование
|
||||||
|
|
||||||
|
После создания клиента нужно определить рендер страницы и выбрать, как получать данные в конкретном месте приложения.
|
||||||
|
|
||||||
|
Раздел использования отвечает на вопросы:
|
||||||
|
|
||||||
|
- как понять, можно ли сохранить static/ISR;
|
||||||
|
- когда страница становится dynamic/SSR;
|
||||||
|
- когда получать данные через серверный `await`;
|
||||||
|
- когда запускать несколько серверных запросов параллельно;
|
||||||
|
- когда передавать промис ниже по дереву;
|
||||||
|
- когда передавать начальные данные клиентским GET-хукам;
|
||||||
|
- когда использовать GET-хук в клиентском компоненте;
|
||||||
|
- когда выносить композицию и бизнес-смысл в `business/`.
|
||||||
|
|
||||||
|
Подробнее:
|
||||||
|
|
||||||
|
- [Стратегии получения данных](/docs/data/rest/strategies/)
|
||||||
|
- [Серверный await](/docs/data/rest/strategies/server-await)
|
||||||
|
- [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests)
|
||||||
|
- [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down)
|
||||||
|
- [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data)
|
||||||
|
- [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook)
|
||||||
|
- [Business-композиция](/docs/data/rest/strategies/business-composition)
|
||||||
|
|
||||||
|
## Как читать раздел
|
||||||
|
|
||||||
|
Если API ещё не подключён — начните с [Создания клиента](/docs/data/rest/clients/).
|
||||||
|
|
||||||
|
Если клиент уже есть, но непонятно как получить данные — начните со [Стратегий получения данных](/docs/data/rest/strategies/).
|
||||||
|
|
||||||
|
Если данные нужны в Client Component — сначала проверьте, есть ли [GET-хук REST-клиента](/docs/data/rest/clients/hooks).
|
||||||
|
|
||||||
|
Если в коде появляется бизнес-смысл вроде `isAuth`, `canEdit`, `hasAccess` — это уже не REST-клиент, а `business/`.
|
||||||
121
docs/docs/data/rest/strategies/business-composition.md
Normal file
121
docs/docs/data/rest/strategies/business-composition.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
title: Business-композиция
|
||||||
|
description: Когда REST-данные нужно объединить или интерпретировать в бизнес-модуле.
|
||||||
|
keywords: [rest, business, композиция, hooks, domain, isAuth]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Business-композиция
|
||||||
|
|
||||||
|
Business-композиция используется, когда простого GET-метода или прозрачного GET-хука недостаточно: нужно объединить несколько источников, преобразовать DTO или вычислить доменное состояние.
|
||||||
|
|
||||||
|
## Когда использовать
|
||||||
|
|
||||||
|
- Нужно объединить несколько GET-запросов.
|
||||||
|
- Нужно вычислить `isAuth`, `canEdit`, `hasAccess`, `hasPets`.
|
||||||
|
- Нужно преобразовать DTO в доменную модель.
|
||||||
|
- Нужно спрятать бизнес-сценарий за доменным API.
|
||||||
|
|
||||||
|
Такая логика не пишется в `infrastructure/`. REST-клиент остаётся прозрачным адаптером к API.
|
||||||
|
|
||||||
|
## Пример поверх одного GET-хука
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/business/pets/hooks/use-available-pets.hook.ts
|
||||||
|
import { useGetPetList } from 'infrastructure/pet-store-api'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Доменный список доступных питомцев.
|
||||||
|
*/
|
||||||
|
export const useAvailablePets = () => {
|
||||||
|
const query = useGetPetList('available')
|
||||||
|
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
hasPets: Boolean(query.data?.length),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`useGetPetList` — infrastructure-хук. `hasPets` — бизнес-интерпретация, поэтому она появляется в `business/pets`.
|
||||||
|
|
||||||
|
## Пример композиции нескольких GET-хуков
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/business/pets/hooks/use-pets-dashboard.hook.ts
|
||||||
|
import { useGetPetList } from 'infrastructure/pet-store-api'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Данные dashboard по питомцам.
|
||||||
|
*/
|
||||||
|
export const usePetsDashboard = () => {
|
||||||
|
const availablePets = useGetPetList('available')
|
||||||
|
const pendingPets = useGetPetList('pending')
|
||||||
|
const soldPets = useGetPetList('sold')
|
||||||
|
|
||||||
|
return {
|
||||||
|
availablePets,
|
||||||
|
pendingPets,
|
||||||
|
soldPets,
|
||||||
|
total:
|
||||||
|
(availablePets.data?.length ?? 0) +
|
||||||
|
(pendingPets.data?.length ?? 0) +
|
||||||
|
(soldPets.data?.length ?? 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Композиция нескольких запросов не добавляется в `infrastructure/pet-store-api/hooks/`, потому что это уже сценарий потребления данных.
|
||||||
|
|
||||||
|
## Пример auth-состояния
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/business/auth/hooks/use-auth-state.hook.ts
|
||||||
|
import { useGetCurrentUser } from 'infrastructure/backend-api'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Состояние авторизации текущего пользователя.
|
||||||
|
*/
|
||||||
|
export const useAuthState = () => {
|
||||||
|
const currentUser = useGetCurrentUser()
|
||||||
|
const user = currentUser.data
|
||||||
|
|
||||||
|
return {
|
||||||
|
...currentUser,
|
||||||
|
user,
|
||||||
|
isAuth: Boolean(user),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`isAuth` не является частью REST-клиента. Это доменный смысл результата запроса.
|
||||||
|
|
||||||
|
## Где размещать
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/business/
|
||||||
|
└── pets/
|
||||||
|
├── hooks/
|
||||||
|
│ └── use-available-pets.hook.ts
|
||||||
|
├── mappers/
|
||||||
|
│ └── map-pet-dto-to-pet.ts
|
||||||
|
├── types/
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Модуль `business/` экспортирует наружу готовый доменный API через `index.ts`.
|
||||||
|
|
||||||
|
## Что запрещено
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Плохо — business-смысл внутри infrastructure-хука
|
||||||
|
export const useGetPetList = (status: PetStatus) => {
|
||||||
|
const query = useSWR(...)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
hasPets: Boolean(query.data?.length),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
REST-модуль отвечает за доступ к API. Business-модуль отвечает за смысл этих данных в продукте.
|
||||||
89
docs/docs/data/rest/strategies/client-get-hook.md
Normal file
89
docs/docs/data/rest/strategies/client-get-hook.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
title: Клиентский GET-хук
|
||||||
|
description: Получение REST-данных в Client Components через готовые GET-хуки REST-клиента.
|
||||||
|
keywords: [rest, client components, swr, get-хук, client state]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Клиентский GET-хук
|
||||||
|
|
||||||
|
Клиентский GET-хук используется, когда данные зависят от состояния браузера: вкладки, фильтра, поиска, пагинации, модалки или действия пользователя.
|
||||||
|
|
||||||
|
## Когда использовать
|
||||||
|
|
||||||
|
- Запрос зависит от client state.
|
||||||
|
- Данные не обязательны для первого HTML.
|
||||||
|
- Пользователь меняет параметры запроса на клиенте.
|
||||||
|
- Нужны SWR-кеширование, дедупликация и ревалидация.
|
||||||
|
|
||||||
|
## Пример с вкладками
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useGetPetList } from 'infrastructure/pet-store-api'
|
||||||
|
import type { PetStatus } from 'infrastructure/pet-store-api'
|
||||||
|
|
||||||
|
const statuses: PetStatus[] = ['available', 'pending', 'sold']
|
||||||
|
|
||||||
|
export function PetTabs() {
|
||||||
|
const [status, setStatus] = useState<PetStatus>('available')
|
||||||
|
const { data: pets, isLoading, error } = useGetPetList(status)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<div>
|
||||||
|
{statuses.map((item) => (
|
||||||
|
<button key={item} type="button" onClick={() => setStatus(item)}>
|
||||||
|
{item}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isLoading && <div>Загрузка...</div>}
|
||||||
|
{error && <div>Ошибка загрузки</div>}
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{pets?.map((pet) => (
|
||||||
|
<li key={pet.id}>{pet.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Компонент выбирает параметр `status`, но не знает про SWR-ключ и fetcher. Запрос выполняет готовый GET-хук REST-клиента.
|
||||||
|
|
||||||
|
## Если хука нет
|
||||||
|
|
||||||
|
Хук добавляется в REST-модуль сервиса:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Не создавайте локальный `useSWR` в компоненте.
|
||||||
|
|
||||||
|
## Плохо
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Плохо — прямой вызов клиента в useEffect
|
||||||
|
useEffect(() => {
|
||||||
|
petStoreApi.pet.findPetsByStatus({ status }).then(setPets)
|
||||||
|
}, [status])
|
||||||
|
|
||||||
|
// Плохо — useSWR в компоненте
|
||||||
|
const { data } = useSWR(
|
||||||
|
['pet-store-api', 'pet', 'list', status],
|
||||||
|
() => petStoreApi.pet.findPetsByStatus({ status }),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Такой код теряет единое место для ключей, дублирует fetcher и разносит инфраструктурные детали по UI.
|
||||||
|
|
||||||
|
## Когда выбрать другую стратегию
|
||||||
|
|
||||||
|
- Данные нужны до первого HTML — [Серверный await](/docs/data/rest/strategies/server-await).
|
||||||
|
- Клиентский хук должен получить начальные данные сразу — [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
|
||||||
|
- Нужно вычислить бизнес-состояние — [Business-композиция](/docs/data/rest/strategies/business-composition).
|
||||||
109
docs/docs/data/rest/strategies/client-hooks-initial-data.md
Normal file
109
docs/docs/data/rest/strategies/client-hooks-initial-data.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
title: Начальные данные для клиентских хуков
|
||||||
|
description: Как дать клиентским GET-хукам начальные REST-данные.
|
||||||
|
keywords: [rest, swr, fallback, initial data, client hooks, unstable_serialize, isr, ssr]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Начальные данные для клиентских хуков
|
||||||
|
|
||||||
|
Как дать клиентским GET-хукам начальные REST-данные.
|
||||||
|
|
||||||
|
Эта стратегия используется, когда данные должны быть запущены на сервере, но потребляться на клиенте через GET-хуки REST-клиента.
|
||||||
|
|
||||||
|
Технически это делается через `SWRConfig fallback`: сервер передаёт промис в fallback, а клиентский хук использует тот же SWR-ключ.
|
||||||
|
|
||||||
|
## Когда использовать
|
||||||
|
|
||||||
|
- Внутри страницы есть Client Components с GET-хуками.
|
||||||
|
- Нужно начать загрузку данных на сервере раньше.
|
||||||
|
- Клиентский компонент должен остаться обычным потребителем `useGetPetList(...)`.
|
||||||
|
- Не нужно писать отдельный prop-drilling для начальных данных.
|
||||||
|
|
||||||
|
## Рендер страницы
|
||||||
|
|
||||||
|
Перед этой стратегией сначала определите рендер маршрута. Серверный preload для `fallback` подчиняется тем же правилам, что и любой серверный запрос в `page.tsx` или `layout.tsx`.
|
||||||
|
|
||||||
|
Если данные общие и могут обновляться по интервалу, сохраняйте static/ISR. Если preload зависит от cookie, headers, `searchParams`, `no-store` или персональных данных пользователя, маршрут становится dynamic/SSR.
|
||||||
|
|
||||||
|
`SWRConfig fallback` не должен быть причиной отключать ISR на всякий случай. Он только передаёт клиентскому GET-хуку данные, которые уже были запущены на сервере.
|
||||||
|
|
||||||
|
## Ключ хука
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
|
export const getPetListKey = (status: PetStatus) =>
|
||||||
|
['pet-store-api', 'pet', 'list', status] as const
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключ экспортируется из REST-модуля, потому что он нужен и GET-хуку, и серверному `SWRConfig fallback`.
|
||||||
|
|
||||||
|
## Пример layout
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/(routes)/pets/layout.tsx
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { SWRConfig, unstable_serialize } from 'swr'
|
||||||
|
import {
|
||||||
|
getPetListKey,
|
||||||
|
petStoreApi,
|
||||||
|
} from 'infrastructure/pet-store-api'
|
||||||
|
|
||||||
|
type PetsLayoutProps = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function PetsLayout({ children }: PetsLayoutProps) {
|
||||||
|
const availablePetsPromise = petStoreApi.pet.findPetsByStatus({
|
||||||
|
status: 'available',
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SWRConfig
|
||||||
|
value={{
|
||||||
|
fallback: {
|
||||||
|
[unstable_serialize(getPetListKey('available'))]: availablePetsPromise,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SWRConfig>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Если GET-хук использует array-key, ключ для `fallback` сериализуется через `unstable_serialize`.
|
||||||
|
|
||||||
|
## Клиентский компонент
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useGetPetList } from 'infrastructure/pet-store-api'
|
||||||
|
|
||||||
|
export function PetList() {
|
||||||
|
const { data: pets, isLoading } = useGetPetList('available')
|
||||||
|
|
||||||
|
if (isLoading) return <div>Загрузка...</div>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{pets?.map((pet) => (
|
||||||
|
<li key={pet.id}>{pet.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Компонент не знает, что данные были запущены на сервере. Он использует обычный GET-хук REST-клиента.
|
||||||
|
|
||||||
|
## Что важно
|
||||||
|
|
||||||
|
- Ключ `fallback` должен совпадать с ключом GET-хука.
|
||||||
|
- Серверный код вызывает метод клиента, а не GET-хук.
|
||||||
|
- Клиентский компонент вызывает GET-хук, а не `useSWR` напрямую.
|
||||||
|
- Эта стратегия не означает ручную работу с кешем в компонентах.
|
||||||
|
|
||||||
|
## Когда не использовать
|
||||||
|
|
||||||
|
Если данные нужны только серверному компоненту, используйте [Серверный await](/docs/data/rest/strategies/server-await). Если данные зависят от состояния браузера, используйте [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook).
|
||||||
100
docs/docs/data/rest/strategies/index.md
Normal file
100
docs/docs/data/rest/strategies/index.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
title: Стратегии получения данных
|
||||||
|
description: Как выбрать получение REST-данных с учётом рендера страницы.
|
||||||
|
keywords: [rest, стратегии, render, isr, ssr, server components, swr, promise, business]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Стратегии получения данных
|
||||||
|
|
||||||
|
Как выбрать получение REST-данных с учётом рендера страницы.
|
||||||
|
|
||||||
|
Перед выбором стратегии должен быть создан REST-клиент сервиса. Если клиента ещё нет, начните с раздела [Создание клиента](/docs/data/rest/clients/).
|
||||||
|
|
||||||
|
## Сначала определите рендер страницы
|
||||||
|
|
||||||
|
В Next.js выбор начинается не с `await`, `Suspense` или SWR. Сначала нужно понять, какой рендер получится у маршрута: static/ISR или dynamic/SSR.
|
||||||
|
|
||||||
|
Next.js может перевести страницу в dynamic rendering автоматически, если в маршруте используются API текущего запроса. Поэтому первый вопрос такой:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Можно ли сохранить ISR, или странице нужны данные на каждый request?
|
||||||
|
```
|
||||||
|
|
||||||
|
ISR — приоритет. Если данные общие для пользователей и их можно обновлять с интервалом, не переводите страницу в SSR без необходимости.
|
||||||
|
|
||||||
|
SSR/dynamic rendering выбирается только когда данные действительно зависят от текущего request или должны пересчитываться на каждый запрос.
|
||||||
|
|
||||||
|
## Что переводит страницу в dynamic rendering
|
||||||
|
|
||||||
|
Проверьте, нужны ли странице API и настройки, которые делают маршрут динамическим:
|
||||||
|
|
||||||
|
- `cookies()` — данные зависят от cookie текущего пользователя.
|
||||||
|
- `headers()` — данные зависят от request headers.
|
||||||
|
- `draftMode()` — нужен preview/draft-режим.
|
||||||
|
- `searchParams` в `page.tsx` — данные зависят от query string.
|
||||||
|
- `cache: 'no-store'` или `revalidate: 0` в методе клиента — запрос нельзя кешировать.
|
||||||
|
- `connection()` — рендер явно ждёт request.
|
||||||
|
- `export const dynamic = 'force-dynamic'` — SSR включён вручную.
|
||||||
|
|
||||||
|
Если ничего из этого не нужно, сначала проектируйте страницу как static/ISR. Серверный `await` сам по себе не означает SSR: режим зависит от кеширования запроса и dynamic API маршрута.
|
||||||
|
|
||||||
|
## Рендер перед стратегией
|
||||||
|
|
||||||
|
| Рендер | Когда подходит | Что выбирать дальше |
|
||||||
|
|--------|----------------|---------------------|
|
||||||
|
| Static/ISR | Данные общие и могут обновляться по интервалу | Серверные стратегии: `await`, `Promise.all`, передача промиса ниже, SWR `fallback` |
|
||||||
|
| SSR/dynamic | Данные зависят от request, пользователя или должны быть свежими на каждый запрос | Серверные стратегии с учётом блокировки первого HTML |
|
||||||
|
| После гидрации | Данные зависят от вкладки, фильтра, поиска, пагинации или действия пользователя | Клиентский GET-хук |
|
||||||
|
|
||||||
|
## Как выбрать стратегию
|
||||||
|
|
||||||
|
Когда режим рендера понятен, выбирайте конкретный способ получения данных:
|
||||||
|
|
||||||
|
| Ситуация после выбора рендера | Стратегия | Где читать |
|
||||||
|
|-------------------------------|-----------|------------|
|
||||||
|
| Данные обязательны для первого HTML, SEO, `notFound()` или `redirect()` | Серверный `await` | [Серверный await](/docs/data/rest/strategies/server-await) |
|
||||||
|
| Несколько независимых данных нужны до рендера | Запуск промисов + `Promise.all` | [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests) |
|
||||||
|
| Часть UI можно загрузить отдельно | Передача промиса ниже + `Suspense` | [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down) |
|
||||||
|
| Client Component должен получить данные сразу из SWR | Начальные данные для клиентских хуков | [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data) |
|
||||||
|
| Данные зависят от client state | Клиентский GET-хук | [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook) |
|
||||||
|
| Нужно объединить несколько запросов или вычислить `isAuth`, `canEdit`, `hasPets` | Business-композиция | [Business-композиция](/docs/data/rest/strategies/business-composition) |
|
||||||
|
|
||||||
|
## Правило выбора
|
||||||
|
|
||||||
|
Не выбирайте стратегию по любимому инструменту. Выбирайте её по двум вопросам:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Можно ли сохранить ISR?
|
||||||
|
Где нужны данные и что должно произойти до первого HTML?
|
||||||
|
```
|
||||||
|
|
||||||
|
Если данные можно кешировать между пользователями — сохраняйте static/ISR. Если данные request-specific — используйте SSR/dynamic rendering. Если данные зависят от состояния браузера — используйте GET-хук REST-клиента. Если простой GET превращается в доменный сценарий — переходите в `business/`.
|
||||||
|
|
||||||
|
## Общие запреты
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Плохо — SSR включён на всякий случай
|
||||||
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
|
// Плохо — ISR отключён без требования к свежести на каждый request
|
||||||
|
export const revalidate = 0
|
||||||
|
|
||||||
|
// Плохо — прямой fetch в компоненте
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/pets').then(...)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Плохо — useSWR в компоненте
|
||||||
|
const { data } = useSWR(
|
||||||
|
['pet-store-api', 'pet', 'list', status],
|
||||||
|
() => petStoreApi.pet.findPetsByStatus({ status }),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Плохо — бизнес-флаг внутри GET-хука REST-клиента
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
hasPets: Boolean(query.data?.length),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Не отключайте ISR без причины. В компонентах используются готовые методы клиента или готовые хуки. SWR-ключи, fetcher и транспорт остаются внутри REST-модуля.
|
||||||
82
docs/docs/data/rest/strategies/parallel-server-requests.md
Normal file
82
docs/docs/data/rest/strategies/parallel-server-requests.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
title: Параллельные серверные запросы
|
||||||
|
description: Как запускать независимые REST-запросы на сервере без waterfall.
|
||||||
|
keywords: [rest, promise.all, параллельные запросы, server components]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Параллельные серверные запросы
|
||||||
|
|
||||||
|
Если серверному компоненту нужно несколько независимых данных, запускайте запросы до ожидания результата. Последовательный `await` создаёт waterfall и замедляет рендер.
|
||||||
|
|
||||||
|
## Когда использовать
|
||||||
|
|
||||||
|
- Запросы независимы друг от друга.
|
||||||
|
- Все данные нужны текущему серверному компоненту перед возвратом UI.
|
||||||
|
- Нельзя или не нужно стримить часть UI отдельно.
|
||||||
|
|
||||||
|
## Хорошо
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { petStoreApi } from 'infrastructure/pet-store-api'
|
||||||
|
import { PetsDashboardScreen } from 'screens/pets-dashboard'
|
||||||
|
|
||||||
|
export default async function PetsDashboardPage() {
|
||||||
|
const availablePetsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
||||||
|
const pendingPetsPromise = petStoreApi.pet.findPetsByStatus({ status: 'pending' })
|
||||||
|
const soldPetsPromise = petStoreApi.pet.findPetsByStatus({ status: 'sold' })
|
||||||
|
|
||||||
|
const [availablePets, pendingPets, soldPets] = await Promise.all([
|
||||||
|
availablePetsPromise,
|
||||||
|
pendingPetsPromise,
|
||||||
|
soldPetsPromise,
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PetsDashboardScreen
|
||||||
|
availablePets={availablePets}
|
||||||
|
pendingPets={pendingPets}
|
||||||
|
soldPets={soldPets}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Плохо
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export default async function PetsDashboardPage() {
|
||||||
|
const availablePets = await petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
||||||
|
const pendingPets = await petStoreApi.pet.findPetsByStatus({ status: 'pending' })
|
||||||
|
const soldPets = await petStoreApi.pet.findPetsByStatus({ status: 'sold' })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PetsDashboardScreen
|
||||||
|
availablePets={availablePets}
|
||||||
|
pendingPets={pendingPets}
|
||||||
|
soldPets={soldPets}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Во втором примере каждый следующий запрос ждёт предыдущий, хотя они независимы.
|
||||||
|
|
||||||
|
## Зависимые запросы
|
||||||
|
|
||||||
|
Если второй запрос зависит от результата первого, последовательный `await` допустим:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export default async function OrderPage({ params }: OrderPageProps) {
|
||||||
|
const { id } = await params
|
||||||
|
const order = await petStoreApi.store.getOrderById(Number(id))
|
||||||
|
const pet = await petStoreApi.pet.getPetById(order.petId)
|
||||||
|
|
||||||
|
return <OrderScreen order={order} pet={pet} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Не превращайте зависимый сценарий в `Promise.all` искусственно.
|
||||||
|
|
||||||
|
## Когда выбрать другую стратегию
|
||||||
|
|
||||||
|
Если часть данных не обязательна для первого блока UI, можно запустить промис выше и передать его ниже: [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down).
|
||||||
62
docs/docs/data/rest/strategies/pass-promise-down.md
Normal file
62
docs/docs/data/rest/strategies/pass-promise-down.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
title: Передача промиса ниже
|
||||||
|
description: Как запускать серверный REST-запрос выше и ожидать его во вложенном server-компоненте.
|
||||||
|
keywords: [rest, promise, suspense, streaming, server components]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Передача промиса ниже
|
||||||
|
|
||||||
|
Серверный компонент может запустить запрос и передать промис вложенному server-компоненту. Это полезно, когда часть UI можно загрузить отдельно через `Suspense`.
|
||||||
|
|
||||||
|
## Когда использовать
|
||||||
|
|
||||||
|
- Верхняя часть страницы может отрендериться без этих данных.
|
||||||
|
- Данные нужны только вложенному server-компоненту.
|
||||||
|
- Нужна `Suspense`-граница и серверный стриминг.
|
||||||
|
|
||||||
|
## Пример
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/(routes)/pets/page.tsx
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
import { petStoreApi } from 'infrastructure/pet-store-api'
|
||||||
|
import { PetListSection } from 'widgets/pet-list-section'
|
||||||
|
import { PetListSkeleton } from 'widgets/pet-list-section'
|
||||||
|
import type { Pet } from 'infrastructure/pet-store-api'
|
||||||
|
|
||||||
|
export default function PetsPage() {
|
||||||
|
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<h1>Питомцы</h1>
|
||||||
|
<Suspense fallback={<PetListSkeleton />}>
|
||||||
|
<AvailablePets petsPromise={petsPromise} />
|
||||||
|
</Suspense>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function AvailablePets({ petsPromise }: { petsPromise: Promise<Pet[]> }) {
|
||||||
|
const pets = await petsPromise
|
||||||
|
|
||||||
|
return <PetListSection pets={pets} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Запрос стартует в `PetsPage`, но ожидание происходит внутри `AvailablePets`. `Suspense` управляет fallback для этой части UI.
|
||||||
|
|
||||||
|
## Граница стратегии
|
||||||
|
|
||||||
|
Эта стратегия остаётся серверной. Не используйте её как замену GET-хукам в Client Components.
|
||||||
|
|
||||||
|
Если данные должны попасть в клиентский SWR-хук, используйте [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
|
||||||
|
|
||||||
|
## Что не делать
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Плохо — передавать промис в произвольный клиентский компонент без ясной стратегии
|
||||||
|
return <PetListClient petsPromise={petsPromise} />
|
||||||
|
```
|
||||||
|
|
||||||
|
Для клиентского потребления есть отдельная стратегия через `SWRConfig fallback` и готовые GET-хуки REST-клиента.
|
||||||
88
docs/docs/data/rest/strategies/server-await.md
Normal file
88
docs/docs/data/rest/strategies/server-await.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
title: Серверный await
|
||||||
|
description: Получение REST-данных на сервере до первого HTML.
|
||||||
|
keywords: [rest, server components, await, nextjs, isr, ssr, notFound, redirect]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Серверный await
|
||||||
|
|
||||||
|
Получение REST-данных на сервере до первого HTML.
|
||||||
|
|
||||||
|
Серверный `await` — базовая стратегия для данных, которые нужны до рендера страницы или серверного блока.
|
||||||
|
|
||||||
|
## Когда использовать
|
||||||
|
|
||||||
|
- Данные нужны для первого HTML.
|
||||||
|
- Данные влияют на `metadata`.
|
||||||
|
- По результату запроса нужно вызвать `notFound()` или `redirect()`.
|
||||||
|
- Компонент серверный и данные не зависят от состояния браузера.
|
||||||
|
|
||||||
|
## Влияние на рендер
|
||||||
|
|
||||||
|
Серверный `await` сам по себе не означает SSR. В App Router страница может остаться static/ISR, если маршрут не использует dynamic API и запросы можно кешировать.
|
||||||
|
|
||||||
|
ISR — приоритет для общих данных. Если список или детальная страница могут обновляться по интервалу, сохраняйте кеширование и не добавляйте `no-store`, `revalidate: 0` или `force-dynamic` без требования.
|
||||||
|
|
||||||
|
SSR/dynamic rendering нужен, когда данные зависят от текущего request: cookie, headers, `searchParams`, preview-режим или персональные данные пользователя.
|
||||||
|
|
||||||
|
## Пример страницы списка
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/(routes)/pets/page.tsx
|
||||||
|
import { petStoreApi } from 'infrastructure/pet-store-api'
|
||||||
|
import { PetsScreen } from 'screens/pets'
|
||||||
|
|
||||||
|
export default async function PetsPage() {
|
||||||
|
const pets = await petStoreApi.pet.findPetsByStatus({
|
||||||
|
status: 'available',
|
||||||
|
})
|
||||||
|
|
||||||
|
return <PetsScreen pets={pets} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`page.tsx` получает данные первого рендера и передаёт их ниже. UI страницы остаётся в `screens/`, а не пишется прямо в `app/`.
|
||||||
|
|
||||||
|
## Пример детальной страницы
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/(routes)/pets/[id]/page.tsx
|
||||||
|
import { notFound } from 'next/navigation'
|
||||||
|
import { petStoreApi } from 'infrastructure/pet-store-api'
|
||||||
|
import { PetDetailScreen } from 'screens/pet-detail'
|
||||||
|
|
||||||
|
type PetPageProps = {
|
||||||
|
params: Promise<{ id: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function PetPage({ params }: PetPageProps) {
|
||||||
|
const { id } = await params
|
||||||
|
const pet = await petStoreApi.pet.getPetById(Number(id)).catch(() => null)
|
||||||
|
|
||||||
|
if (!pet) {
|
||||||
|
notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <PetDetailScreen pet={pet} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Обработка 404 зависит от API-клиента и класса ошибок. В примере показана идея: решение о `notFound()` принимается на уровне маршрута, а не внутри REST-клиента.
|
||||||
|
|
||||||
|
## Что не делать
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Плохо — хуки нельзя вызывать в Server Component
|
||||||
|
const { data } = useGetPetList('available')
|
||||||
|
|
||||||
|
// Плохо — прямой fetch в обход клиента
|
||||||
|
const response = await fetch('https://petstore3.swagger.io/api/v3/pet/findByStatus')
|
||||||
|
```
|
||||||
|
|
||||||
|
Если данные нужны на сервере, вызывайте метод REST-клиента напрямую.
|
||||||
|
|
||||||
|
## Когда выбрать другую стратегию
|
||||||
|
|
||||||
|
- Несколько независимых запросов — [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests).
|
||||||
|
- Часть UI можно грузить отдельно — [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down).
|
||||||
|
- Данные нужны клиентскому хуку сразу после гидрации — [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
|
||||||
97
docs/docs/index.md
Normal file
97
docs/docs/index.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
title: NextJS Style Guide
|
||||||
|
description: Стандарты разработки фронтенд-приложений на Next.js и TypeScript.
|
||||||
|
---
|
||||||
|
|
||||||
|
# NextJS Style Guide
|
||||||
|
|
||||||
|
Стандарты разработки фронтенд-приложений на Next.js и TypeScript.
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
**Для AI-агентов:**
|
||||||
|
|
||||||
|
- [llms.txt](/llms.txt) — Карта разделов, оглавление со ссылками на разделы.
|
||||||
|
- [llms-full.txt](/llms-full.txt) — Вся документация одним файлом.
|
||||||
|
|
||||||
|
**Для проекта:**
|
||||||
|
|
||||||
|
- [nextjs-style-guide.zip](/nextjs-style-guide.zip) — Набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
|
||||||
|
|
||||||
|
## Структура документации
|
||||||
|
|
||||||
|
### Подсказки
|
||||||
|
|
||||||
|
[Подсказки](/docs/workflow) — короткие ответы на типовые вопросы и решения для спорных ситуаций.
|
||||||
|
|
||||||
|
### Базовые правила
|
||||||
|
|
||||||
|
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Технологии и библиотеки | Какой стек используем? |
|
||||||
|
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
||||||
|
| SLM Design | Что такое SLM и зачем она нужна? |
|
||||||
|
| Архитектура: Слои | Какие слои есть и как между ними устроены зависимости? |
|
||||||
|
| Архитектура: Модули | Что такое модуль и как он устроен? |
|
||||||
|
| Архитектура: Сегменты | Какие сегменты есть внутри модуля? |
|
||||||
|
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
||||||
|
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||||
|
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||||
|
|
||||||
|
### Создание проекта
|
||||||
|
|
||||||
|
**Как начать новый проект** — варианты установки и эталонный набор инструментов.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Создание проекта из шаблона | Как начать проект из готового шаблона? |
|
||||||
|
| Создание проекта вручную | Как поднять проект с нуля без шаблона? |
|
||||||
|
| Чистая установка Next.js | Как поставить голый Next.js под дальнейшую сборку? |
|
||||||
|
|
||||||
|
### Настройка
|
||||||
|
|
||||||
|
**Как сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Алиасы импортов | Как настроить алиасы импортов? |
|
||||||
|
| Biome | Как настроить линтер и форматтер? |
|
||||||
|
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
|
||||||
|
| Стили | Как подключить базовые стили и токены? |
|
||||||
|
| SVG-спрайты | Как подключить генерацию SVG-спрайтов? |
|
||||||
|
| Шаблоны генерации | Как подключить шаблоны для кодогенерации? |
|
||||||
|
| VS Code | Как настроить редактор для проекта? |
|
||||||
|
|
||||||
|
### Использование
|
||||||
|
|
||||||
|
**Как это устроено и как этим пользоваться** — структура, примеры и правила для конкретных областей.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||||
|
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||||
|
| Файлы роутинга | Как описывать layout, page, loading, error, not-found? |
|
||||||
|
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||||
|
| Стили | Как писать CSS: вложенность, медиа, токены? |
|
||||||
|
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
|
||||||
|
| Изображения | _(не заполнен)_ |
|
||||||
|
| Видео | _(не заполнен)_ |
|
||||||
|
| Stores | _(не заполнен)_ |
|
||||||
|
| Хуки | _(не заполнен)_ |
|
||||||
|
| Шрифты | _(не заполнен)_ |
|
||||||
|
| Локализация | _(не заполнен)_ |
|
||||||
|
|
||||||
|
### Данные
|
||||||
|
|
||||||
|
**Как работать с источниками данных** — REST, realtime и потребление в компонентах.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Источники данных | Как устроена работа с данными в проекте? |
|
||||||
|
| REST: Автоматическая генерация | Как сгенерировать REST-клиент автоматически из OpenAPI? |
|
||||||
|
| REST: Ручное создание | Как написать REST-клиент вручную? |
|
||||||
|
| REST: Серверные компоненты | Как получать данные в серверных компонентах? |
|
||||||
|
| REST: Клиентские компоненты | Как получать данные в клиентских компонентах? |
|
||||||
|
| Realtime | Как работать с realtime-каналами и сокетами? |
|
||||||
8
docs/docs/workflow.md
Normal file
8
docs/docs/workflow.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Подсказки
|
||||||
|
description: Короткие ответы на типовые вопросы и решения для спорных ситуаций.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Подсказки
|
||||||
|
|
||||||
|
Короткие ответы на типовые вопросы и решения для спорных ситуаций.
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: API
|
|
||||||
---
|
|
||||||
|
|
||||||
# API
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Components
|
|
||||||
---
|
|
||||||
|
|
||||||
# Components
|
|
||||||
|
|
||||||
Rules for creating UI components across all FSD layers.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: Fonts
|
|
||||||
---
|
|
||||||
|
|
||||||
# Fonts
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: Hooks
|
|
||||||
---
|
|
||||||
|
|
||||||
# Hooks
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: Images
|
|
||||||
---
|
|
||||||
|
|
||||||
# Images
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: Localization
|
|
||||||
---
|
|
||||||
|
|
||||||
# Localization
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Page-level Components
|
|
||||||
---
|
|
||||||
|
|
||||||
# Page-level Components
|
|
||||||
|
|
||||||
Next.js App Router special files used by the framework by convention: `layout.tsx`, `page.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`, `template.tsx`.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Project Structure
|
|
||||||
---
|
|
||||||
|
|
||||||
# Project Structure
|
|
||||||
|
|
||||||
Base project structure and principles of module organization at folder and file level.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: Stores
|
|
||||||
---
|
|
||||||
|
|
||||||
# Stores
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Styles
|
|
||||||
---
|
|
||||||
|
|
||||||
# Styles
|
|
||||||
|
|
||||||
CSS writing rules: PostCSS Modules, nesting, media queries, variables, formatting.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: SVG Sprites
|
|
||||||
---
|
|
||||||
|
|
||||||
# SVG Sprites
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Templates & Code Generation
|
|
||||||
---
|
|
||||||
|
|
||||||
# Templates & Code Generation
|
|
||||||
|
|
||||||
Template tools, syntax, and examples for code generation.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: Video
|
|
||||||
---
|
|
||||||
|
|
||||||
# Video
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Architecture
|
|
||||||
---
|
|
||||||
|
|
||||||
# Architecture
|
|
||||||
|
|
||||||
Architecture based on FSD (Feature-Sliced Design) and strict module boundaries.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Code Style
|
|
||||||
---
|
|
||||||
|
|
||||||
# Code Style
|
|
||||||
|
|
||||||
Unified code formatting rules: indentation, line breaks, quotes, import order, and readability.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Documentation
|
|
||||||
---
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
|
|
||||||
Documentation should help understand the purpose of an entity, not duplicate its types or obvious details.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Naming
|
|
||||||
---
|
|
||||||
|
|
||||||
# Naming
|
|
||||||
|
|
||||||
Naming should be predictable, concise, and reflect the meaning of the entity.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Tech Stack
|
|
||||||
---
|
|
||||||
|
|
||||||
# Tech Stack
|
|
||||||
|
|
||||||
Base technology stack and libraries used in projects.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Typing
|
|
||||||
---
|
|
||||||
|
|
||||||
# Typing
|
|
||||||
|
|
||||||
Typing is required for all public interfaces, functions, and components.
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# NextJS Style Guide
|
|
||||||
|
|
||||||
Rules and standards for NextJS and TypeScript development: architecture, typing, styles, components, API, and infrastructure.
|
|
||||||
|
|
||||||
## Documentation Structure
|
|
||||||
|
|
||||||
### Processes
|
|
||||||
|
|
||||||
**What to do** in a specific situation — step-by-step instructions.
|
|
||||||
|
|
||||||
| Section | Answers the question |
|
|
||||||
|---------|---------------------|
|
|
||||||
| Getting Started | What tools to install before starting development? |
|
|
||||||
| Creating an App | How to create a new project, where to get a template? |
|
|
||||||
| Creating Pages | How to add a page: routing and screen? |
|
|
||||||
| Creating Components | How to generate components using templates? |
|
|
||||||
| Styling | What to use: Mantine, tokens, or PostCSS? |
|
|
||||||
| Data Fetching | How to fetch data: SWR, codegen, sockets? |
|
|
||||||
| State Management | When and how to create a store (Zustand)? |
|
|
||||||
| Localization | How to add translations and work with i18next? |
|
|
||||||
|
|
||||||
### Basic Rules
|
|
||||||
|
|
||||||
**What the code should look like** — standards not tied to a specific technology.
|
|
||||||
|
|
||||||
| Section | Answers the question |
|
|
||||||
|---------|---------------------|
|
|
||||||
| Tech Stack | What stack do we use? |
|
|
||||||
| Architecture | How are FSD layers, dependencies, and public API structured? |
|
|
||||||
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
|
||||||
| Naming | How to name files, variables, components, hooks? |
|
|
||||||
| Documentation | How to write JSDoc: what to document and what not? |
|
|
||||||
| Typing | How to type: type vs interface, any/unknown, FC? |
|
|
||||||
|
|
||||||
### Applied Sections
|
|
||||||
|
|
||||||
**How a specific area works** — rules, structure, and code examples for specific technologies and tools.
|
|
||||||
|
|
||||||
| Section | Answers the question |
|
|
||||||
|---------|---------------------|
|
|
||||||
| Project Structure | How are folders and files organized by FSD? |
|
|
||||||
| Components | How is a component structured: files, props, clsx, FC? |
|
|
||||||
| Page-level Components | How to define layout, page, loading, error, not-found? |
|
|
||||||
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
|
|
||||||
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
|
|
||||||
| Images | _(not filled)_ |
|
|
||||||
| SVG Sprites | _(not filled)_ |
|
|
||||||
| Video | _(not filled)_ |
|
|
||||||
| API | _(not filled)_ |
|
|
||||||
| Stores | _(not filled)_ |
|
|
||||||
| Hooks | _(not filled)_ |
|
|
||||||
| Fonts | _(not filled)_ |
|
|
||||||
| Localization | _(not filled)_ |
|
|
||||||
|
|
||||||
## For Assistants
|
|
||||||
|
|
||||||
Full documentation in a single MD file: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/en/RULES.md
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Creating an App
|
|
||||||
---
|
|
||||||
|
|
||||||
# Creating an App
|
|
||||||
|
|
||||||
How to create a new application: choosing a project template and initialization.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Creating Components
|
|
||||||
---
|
|
||||||
|
|
||||||
# Creating Components
|
|
||||||
|
|
||||||
Generating components using templates, working with child components.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Creating Pages
|
|
||||||
---
|
|
||||||
|
|
||||||
# Creating Pages
|
|
||||||
|
|
||||||
Page creation pattern: routing (page.tsx) and screen.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: Data Fetching
|
|
||||||
---
|
|
||||||
|
|
||||||
# Data Fetching
|
|
||||||
|
|
||||||
How to fetch data: SWR, API client codegen, sockets.
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user