Compare commits
23 Commits
v2
...
028a69f3ac
| Author | SHA1 | Date | |
|---|---|---|---|
| 028a69f3ac | |||
| a9ea898220 | |||
| 7965ec2a96 | |||
| 7c224fed99 | |||
| 74cbd43a23 | |||
| ef58a02609 | |||
| e265799c26 | |||
| 781efc52f1 | |||
| ec01ae2e1c | |||
| 3d93efd90a | |||
| e5e4ace91a | |||
| 5a773a5b4f | |||
| f645b2ad40 | |||
| 90bf360c06 | |||
| 5cf0f0f8ba | |||
| 464c709859 | |||
| 64db18917b | |||
| ae103e962e | |||
| 99c0995cb6 | |||
| d621e6b57d | |||
| 787223010f | |||
| f5732904f4 | |||
| 36304c14f0 |
@@ -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
|
||||||
|
|
||||||
@@ -73,7 +78,7 @@ jobs:
|
|||||||
ssh -i ~/.ssh/deploy_key root@188.225.47.78 bash -s <<'SCRIPT'
|
ssh -i ~/.ssh/deploy_key root@188.225.47.78 bash -s <<'SCRIPT'
|
||||||
set -e
|
set -e
|
||||||
IMAGE="${{ env.REGISTRY_IMAGE }}:latest"
|
IMAGE="${{ env.REGISTRY_IMAGE }}:latest"
|
||||||
CONTAINER="frontend-style-guide"
|
CONTAINER="nextjs-style-guide"
|
||||||
|
|
||||||
# Логин в реестр
|
# Логин в реестр
|
||||||
echo '${{ secrets.CR_TOKEN }}' | docker login ${{ env.DOCKER_REGISTRY }} -u '${{ secrets.CR_USER }}' --password-stdin
|
echo '${{ secrets.CR_TOKEN }}' | docker login ${{ env.DOCKER_REGISTRY }} -u '${{ secrets.CR_USER }}' --password-stdin
|
||||||
|
|||||||
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,161 @@
|
|||||||
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/reference/layers' },
|
||||||
|
{ text: 'Модули', link: '/docs/basics/architecture/reference/modules' },
|
||||||
|
{ text: 'Сегменты', link: '/docs/basics/architecture/reference/segments' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ text: 'Стиль кода', link: '/docs/basics/code-style' },
|
||||||
|
{ text: 'Документирование', link: '/docs/basics/documentation' },
|
||||||
|
{ text: 'Типизация', link: '/docs/basics/typing' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Прикладные разделы',
|
text: 'Создание проекта',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Структура проекта', link: '/applied/project-structure' },
|
{ text: 'Из шаблона', link: '/docs/creating-project/from-template' },
|
||||||
{ text: 'Компоненты', link: '/applied/components' },
|
{ text: 'По гайду вручную', link: '/docs/creating-project/manual' },
|
||||||
{ text: 'Страницы (App Router)', link: '/applied/page-level' },
|
{ text: 'Чистый Next.js', link: '/docs/creating-project/nextjs' },
|
||||||
{ text: 'Шаблоны и генерация кода', link: '/applied/templates-generation' },
|
],
|
||||||
{ text: 'Стили', link: '/applied/styles' },
|
},
|
||||||
{ text: 'Изображения', link: '/applied/images-sprites' },
|
{
|
||||||
{ text: 'SVG-спрайты', link: '/applied/svg-sprites' },
|
text: 'Настройка',
|
||||||
{ text: 'Видео', link: '/applied/video' },
|
items: [
|
||||||
{ text: 'API', link: '/applied/api' },
|
{ text: 'Алиасы импортов', link: '/docs/setup/aliases' },
|
||||||
{ text: 'Stores', link: '/applied/stores' },
|
{ text: 'Biome', link: '/docs/setup/biome' },
|
||||||
{ text: 'Хуки', link: '/applied/hooks' },
|
{ text: 'PostCSS', link: '/docs/setup/postcss' },
|
||||||
{ text: 'Шрифты', link: '/applied/fonts' },
|
{ text: 'Стили', link: '/docs/setup/styles' },
|
||||||
{ text: 'Локализация', link: '/applied/localization' },
|
{ text: 'SVG-спрайты', link: '/docs/setup/svg-sprites' },
|
||||||
{ text: 'Настройка VS Code', link: '/applied/vscode' },
|
{ text: 'Шаблоны генерации', link: '/docs/setup/templates' },
|
||||||
|
{ text: 'VS Code', link: '/docs/setup/vscode' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Использование',
|
||||||
|
items: [
|
||||||
|
{ text: 'Структура проекта', link: '/docs/usage/project-structure' },
|
||||||
|
{ text: 'Компоненты', link: '/docs/usage/components' },
|
||||||
|
{ text: 'Страницы (App Router)', link: '/docs/usage/page-level' },
|
||||||
|
{ text: 'Шаблоны и генерация кода', link: '/docs/usage/templates-generation' },
|
||||||
|
{ text: 'Стили', link: '/docs/usage/styles' },
|
||||||
|
// Неактивные разделы: страницы существуют, но пока пустые.
|
||||||
|
// Оставляем в sidebar без `link`, чтобы видеть план, но без перехода.
|
||||||
|
{ text: 'Изображения · в разработке' },
|
||||||
|
{ text: 'SVG-спрайты', link: '/docs/usage/svg-sprites' },
|
||||||
|
{ text: 'Видео · в разработке' },
|
||||||
|
{ text: 'Stores · в разработке' },
|
||||||
|
{ text: 'Хуки · в разработке' },
|
||||||
|
{ text: 'Шрифты · в разработке' },
|
||||||
|
{ text: 'Локализация · в разработке' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Данные',
|
||||||
|
items: [
|
||||||
|
{ text: 'Введение', link: '/docs/usage/data/' },
|
||||||
|
{
|
||||||
|
text: 'REST',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Клиенты',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Автоматическая генерация', link: '/docs/usage/data/rest/clients/auto' },
|
||||||
|
{ text: 'Ручное создание', link: '/docs/usage/data/rest/clients/manual' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Получение данных',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Серверные компоненты', link: '/docs/usage/data/rest/fetching/server' },
|
||||||
|
{ text: 'Клиентские компоненты', link: '/docs/usage/data/rest/fetching/client' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ text: 'Realtime', link: '/docs/usage/data/realtime' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
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',
|
||||||
|
// `docs/public/` содержит сгенерированные `.md`-копии и `llms.txt` для LLM
|
||||||
|
// (попадают в корень `dist/` как статика). Исключаем из сканирования
|
||||||
|
// страниц, иначе VitePress рендерит их как HTML-страницы.
|
||||||
|
srcExclude: ['public/**'],
|
||||||
|
lang: 'ru-RU',
|
||||||
title: 'NextJS Style Guide',
|
title: 'NextJS Style Guide',
|
||||||
description: 'Правила и стандарты разработки на NextJS и TypeScript',
|
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
||||||
|
|
||||||
rewrites: {
|
// Чистые URL без `.html` — канон для индексации.
|
||||||
'ru/:rest*': ':rest*',
|
// Серверная поддержка реализована в Caddyfile (try_files + редирект).
|
||||||
},
|
cleanUrls: true,
|
||||||
|
|
||||||
locales: {
|
// Дублируем указатель на llms.txt в <head> — для агентов,
|
||||||
root: {
|
// которые читают HTML, но не парсят полный DOM/href.
|
||||||
label: 'Русский',
|
head: [
|
||||||
lang: 'ru-RU',
|
['link', { rel: 'alternate', type: 'text/plain', href: '/llms.txt', title: 'llms.txt' }],
|
||||||
themeConfig: {
|
['link', { rel: 'alternate', type: 'text/plain', href: '/llms-full.txt', title: 'llms-full.txt' }],
|
||||||
sidebar: ruSidebar,
|
],
|
||||||
},
|
|
||||||
},
|
vite: {
|
||||||
en: {
|
plugins: [utf8TextPlugin],
|
||||||
label: 'English',
|
define: {
|
||||||
lang: 'en-US',
|
__BUILD_VERSION__: JSON.stringify(process.env.BUILD_VERSION || 'dev'),
|
||||||
link: '/en/',
|
|
||||||
themeConfig: {
|
|
||||||
sidebar: enSidebar,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
themeConfig: {
|
||||||
|
sidebar,
|
||||||
|
socialLinks: [
|
||||||
|
{ icon: 'github', link: 'https://gromlab.ru/docs/nextjs-style-guide' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Расширенный блок описания для llms.txt — даёт LLM полный
|
||||||
|
// технический контекст: стек, методология, охват тем.
|
||||||
|
// Используется в generate-llms.ts.
|
||||||
|
llmsBlockquote:
|
||||||
|
'Стандарты разработки frontend-приложений на Next.js (App Router) + TypeScript + React с архитектурой SLM (Scoped Layered Module Design — модульная архитектура со слоями ответственности, где каждый модуль содержит всё необходимое: компоненты, хуки, сторы, типы, стили).',
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
11
AGENTS.md
11
AGENTS.md
@@ -3,11 +3,6 @@
|
|||||||
При работе с документацией следовать правилам из CONTRIBUTING.md.
|
При работе с документацией следовать правилам из CONTRIBUTING.md.
|
||||||
|
|
||||||
- Язык документации и коммитов — русский.
|
- Язык документации и коммитов — русский.
|
||||||
- Исходники документации — в `src/`, только `.md` файлы.
|
- После изменений в `.md`-файлах — запустить `npm run llms` для обновления `llms.txt` и README.
|
||||||
- Скрипты и манифесты сборки — в `scripts/`.
|
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`):
|
||||||
- Общие правила — в `src/base/`.
|
он же является источником порядка и группировки для `llms.txt`.
|
||||||
- Фреймворк-специфичные — в `src/{framework}/`.
|
|
||||||
- Точки входа (`DEVELOP.md`, `REVIEW.md`) — в `src/{framework}/`.
|
|
||||||
- После изменений в `.md`-файлах — запустить `npm run build:ai` для пересборки `dist/ai/`.
|
|
||||||
- При добавлении нового раздела — добавить файл в `src/` и путь в манифест `scripts/{fw}.build.js`.
|
|
||||||
- Frontmatter каждого `.md`-файла содержит поля `title`, `scope`, `keywords`, `when`.
|
|
||||||
|
|||||||
361
CONTRIBUTING.md
361
CONTRIBUTING.md
@@ -1,129 +1,322 @@
|
|||||||
# Правила написания документации
|
# Правила работы над документацией
|
||||||
|
|
||||||
Как писать и редактировать разделы стайлгайда.
|
Мета-документ: как устроен проект, как писать и редактировать разделы.
|
||||||
|
|
||||||
## Типы разделов
|
## О проекте
|
||||||
|
|
||||||
### Базовые правила (`basics/`)
|
Документационный сайт с правилами и стандартами фронтенд-разработки на Next.js + TypeScript.
|
||||||
|
|
||||||
|
- Движок: VitePress
|
||||||
|
- Язык: русский
|
||||||
|
- Контент: `docs/docs/`
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
| Команда | Что делает |
|
||||||
|
|---------|-----------|
|
||||||
|
| `npm run dev` | Локальный сервер разработки |
|
||||||
|
| `npm run build` | Сборка статического сайта |
|
||||||
|
| `npm run llms` | Генерация `llms.txt` (карта документации для LLM) и README |
|
||||||
|
|
||||||
|
## Структура файлов
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/
|
||||||
|
├── index.md # Лендинг (URL `/`)
|
||||||
|
└── docs/ # Контент документации (URL `/docs/...`)
|
||||||
|
├── index.md # Главная страница
|
||||||
|
├── workflow.md
|
||||||
|
├── workflow/ # Процессы разработки
|
||||||
|
├── basics/ # Базовые правила
|
||||||
|
│ ├── tech-stack.md
|
||||||
|
│ ├── architecture/
|
||||||
|
│ ├── code-style.md
|
||||||
|
│ ├── naming.md
|
||||||
|
│ ├── documentation.md
|
||||||
|
│ └── typing.md
|
||||||
|
├── setup/ # Установка: разовая настройка проекта
|
||||||
|
│ ├── aliases.md
|
||||||
|
│ ├── biome.md
|
||||||
|
│ ├── postcss.md
|
||||||
|
│ ├── svg-sprites.md
|
||||||
|
│ └── vscode.md
|
||||||
|
└── usage/ # Использование: повседневная работа
|
||||||
|
├── project-structure.md
|
||||||
|
├── components.md
|
||||||
|
├── page-level.md
|
||||||
|
├── templates-generation.md
|
||||||
|
├── styles.md
|
||||||
|
├── images-sprites.md
|
||||||
|
├── svg-sprites.md
|
||||||
|
├── video.md
|
||||||
|
├── data/
|
||||||
|
├── stores.md
|
||||||
|
├── hooks.md
|
||||||
|
├── fonts.md
|
||||||
|
└── localization.md
|
||||||
|
.vitepress/
|
||||||
|
└── config.ts # Конфигурация VitePress, сайдбар
|
||||||
|
generate-llms.ts # Скрипт генерации llms.txt и README
|
||||||
|
```
|
||||||
|
|
||||||
|
Сгенерированные артефакты (`docs/public/`): `llms.txt`, `llms-full.txt`,
|
||||||
|
`nextjs-style-guide.zip`, `manifest.json`, копии `.md` в `docs/public/docs/`.
|
||||||
|
|
||||||
|
### Добавление нового раздела
|
||||||
|
|
||||||
|
1. Создать `.md`-файл в нужной папке (`docs/docs/basics/`, `docs/docs/setup/` или `docs/docs/usage/`).
|
||||||
|
2. Добавить пункт в сайдбар — `.vitepress/config.ts`.
|
||||||
|
Сайдбар — единственный источник порядка и группировки для `llms.txt`.
|
||||||
|
3. Запустить `npm run llms` для обновления `llms.txt` и README.
|
||||||
|
|
||||||
|
## Два типа документации
|
||||||
|
|
||||||
|
### Базовые правила
|
||||||
|
|
||||||
**Отвечает на вопрос:** «Каким должен быть любой код?»
|
**Отвечает на вопрос:** «Каким должен быть любой код?»
|
||||||
|
|
||||||
Универсальные стандарты, не привязанные к конкретной области.
|
Универсальные стандарты, **не привязанные к конкретной области**.
|
||||||
Правило базовое, если оно применимо ко всему коду одинаково.
|
Правило базовое, если оно применимо ко всему коду одинаково: именование переменных, оформление импортов, когда использовать `type` vs `interface`.
|
||||||
|
|
||||||
**Граница:** если правило касается только одной области — оно прикладное.
|
Примеры в базовых правилах допускаются, но служат иллюстрацией принципа, а не инструкцией по конкретной области.
|
||||||
|
|
||||||
### Прикладные разделы (`applied/`)
|
**Граница:** если правило касается только одной области (только стили, только компоненты, только API) — оно живёт в прикладном разделе, не в базовых.
|
||||||
|
|
||||||
|
### Прикладные разделы
|
||||||
|
|
||||||
**Отвечает на вопрос:** «Как работать с X?»
|
**Отвечает на вопрос:** «Как работать с X?»
|
||||||
|
|
||||||
Полное описание конкретной области: структура файлов, правила, именование, типизация, примеры.
|
Полное описание конкретной области: структура файлов, правила, именование, типизация, примеры.
|
||||||
|
|
||||||
**Граница:** не дублирует базовые правила. Если правило уже описано в базовых — ссылается, но не повторяет.
|
**Граница:** прикладной раздел не дублирует базовые правила.
|
||||||
|
Если правило уже описано в базовых — прикладной раздел ссылается на него, но не повторяет.
|
||||||
|
|
||||||
### Триггеры (`triggers/`)
|
## Структура прикладного раздела
|
||||||
|
|
||||||
**Отвечает на вопрос:** «Как выполнить задачу X?»
|
Шаблон ниже описывает все допустимые секции. Раздел включает только те секции, которые для него релевантны — пустые секции не создаются.
|
||||||
|
|
||||||
Конкретная инструкция: какие разделы прочитать, какие шаги выполнить. Триггер ссылается на basics/ и applied/, но не дублирует их. Группируются по роли: `triggers/develop/`, `triggers/review/`, `triggers/architect/`.
|
```markdown
|
||||||
|
# {Название}
|
||||||
|
|
||||||
Структура триггера:
|
{Одно-два предложения, по которым читатель за секунду решает, нужен ли ему раздел.
|
||||||
|
Правила оформления — секция «Заголовок и описание» ниже.}
|
||||||
|
|
||||||
- **Заголовок** — глагол + объект ("Создать компонент", "Добавить иконку")
|
## Что нужно знать
|
||||||
- **Описание** — одно предложение: что делает триггер
|
|
||||||
- **Прочитай перед началом** — ссылки на basics/ и applied/
|
|
||||||
- **Шаги** — нумерованный список действий со ссылками
|
|
||||||
- **Смежные триггеры** — ссылки на связанные задачи
|
|
||||||
- **Проверь себя** — чеклист из 2-5 пунктов для самопроверки
|
|
||||||
|
|
||||||
### Фреймворк-специфичные (`{framework}/`)
|
Неочевидная информация, которую читатель должен знать перед чтением раздела.
|
||||||
|
Если для раздела нет такой вводной — секция не создаётся.
|
||||||
|
|
||||||
Разделы и триггеры, которые применимы только к конкретному фреймворку. Те же категории — `applied/` и `triggers/`.
|
## Структура
|
||||||
|
|
||||||
**Граница:** если правило одинаково для всех фреймворков — оно в `base/`.
|
Файловая организация: какие файлы создавать и куда класть.
|
||||||
|
Обязательно — дерево файлов через code-block.
|
||||||
|
|
||||||
## Frontmatter
|
## Правила
|
||||||
|
|
||||||
|
Конкретные требования, специфичные для области. Делятся на две подсекции:
|
||||||
|
|
||||||
|
### Реализация
|
||||||
|
|
||||||
|
Как написан код внутри файла: синтаксис, паттерны, API.
|
||||||
|
Отвечает на вопрос: «Как писать код?»
|
||||||
|
|
||||||
|
Примеры: объявление через `const`, деструктуризация пропсов, формат вызова `cl()`, способ подключения стилей, структура хука.
|
||||||
|
|
||||||
|
### Организация
|
||||||
|
|
||||||
|
Как компонент/модуль встроен в проект: файловые границы, зоны ответственности, экспорт.
|
||||||
|
Отвечает на вопрос: «Где что лежит и за что отвечает?»
|
||||||
|
|
||||||
|
Примеры: один компонент — один файл, вложенные компоненты в `ui/`, логика выносится в `model/`.
|
||||||
|
|
||||||
|
Формат обеих подсекций — маркированный список.
|
||||||
|
Для неочевидных случаев — блоки «Хорошо / Плохо».
|
||||||
|
Если в области нет правил одной из категорий — подсекция не создаётся.
|
||||||
|
|
||||||
|
## Именование
|
||||||
|
|
||||||
|
Соглашения по именам, специфичные для этой области.
|
||||||
|
Только то, что НЕ покрыто в базовом разделе «Именование».
|
||||||
|
|
||||||
|
## Типизация
|
||||||
|
|
||||||
|
Правила типизации, специфичные для этой области.
|
||||||
|
Только то, что НЕ покрыто в базовом разделе «Типизация».
|
||||||
|
|
||||||
|
## Документирование
|
||||||
|
|
||||||
|
Что и как документировать в этой области.
|
||||||
|
Только то, что НЕ покрыто в базовом разделе «Документирование».
|
||||||
|
|
||||||
|
## Примеры
|
||||||
|
|
||||||
|
Полноценные примеры кода.
|
||||||
|
Каждый пример с путём к файлу и пояснениями.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Порядок секций
|
||||||
|
|
||||||
|
Порядок фиксированный: контекст → структура → правила → специализации базовых правил → примеры.
|
||||||
|
|
||||||
|
Логика: читатель сначала понимает «что это», потом «где это лежит», потом «как это делать», и в конце видит полный пример.
|
||||||
|
|
||||||
|
### Секции-расширения базовых правил
|
||||||
|
|
||||||
|
«Именование», «Типизация», «Документирование» в прикладном разделе — это **точки расширения** базовых правил.
|
||||||
|
|
||||||
|
- В базовых описано общее: `camelCase` для переменных, `type` vs `interface`, формат JSDoc.
|
||||||
|
- В прикладном разделе описано специфичное: как именовать CSS-классы (стили), как типизировать пропсы компонентов (компоненты), как документировать хуки (хуки).
|
||||||
|
|
||||||
|
Если для области нет специфики по именованию, типизации или документированию — секция не создаётся.
|
||||||
|
|
||||||
|
## Конвенции оформления
|
||||||
|
|
||||||
|
### Frontmatter
|
||||||
|
|
||||||
Каждый `.md`-файл начинается с YAML frontmatter:
|
Каждый `.md`-файл начинается с YAML frontmatter:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
title: Название раздела
|
title: Название раздела
|
||||||
scope: basics | applied | triggers
|
|
||||||
keywords: [ключевое слово 1, ключевое слово 2]
|
|
||||||
when: "Когда агенту читать этот раздел"
|
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
| Поле | Описание |
|
Значение `title` совпадает с текстом `h1`-заголовка в файле.
|
||||||
|------|----------|
|
|
||||||
| `title` | Название раздела. Совпадает с `h1` в файле |
|
|
||||||
| `scope` | Тип: `basics`, `applied` или `triggers` |
|
|
||||||
| `keywords` | Ключевые слова для поиска агентом |
|
|
||||||
| `when` | Описание ситуации, когда раздел релевантен |
|
|
||||||
|
|
||||||
## Структура прикладного раздела
|
### Заголовок и описание
|
||||||
|
|
||||||
Раздел включает только релевантные секции — пустые не создаются.
|
Каждая страница начинается с `h1`-заголовка и абзаца-описания сразу под ним.
|
||||||
|
Эта пара — **навигационный маркер**: попадает в сайдбар, `llms.txt`,
|
||||||
|
`README.md` архива и должна за секунду давать читателю или LLM понять,
|
||||||
|
**когда сюда нужно идти**.
|
||||||
|
|
||||||
|
#### Структура заголовков
|
||||||
|
|
||||||
|
- Один `h1` на файл, совпадает с `title` во frontmatter.
|
||||||
|
- Сразу после `h1` — описание (одно-два предложения, см. ниже).
|
||||||
|
- Основные секции — `h2`.
|
||||||
|
- Подсекции внутри `h2` — `h3`.
|
||||||
|
- `h4` не используется.
|
||||||
|
|
||||||
|
#### Имя `h1` (заголовок страницы)
|
||||||
|
|
||||||
|
- Называет предметную область, а не реализацию.
|
||||||
|
- Без имён пакетов, опций, конфигов и путей.
|
||||||
|
- Самодостаточен — читается без контекста сайдбара.
|
||||||
|
- Исключение: имя инструмента допустимо, если оно — единственное
|
||||||
|
устойчивое имя самой области (`PostCSS`, `Biome`, `VS Code`).
|
||||||
|
|
||||||
|
**Хорошо:** «Алиасы импортов», «Структура проекта», «SVG-спрайты».
|
||||||
|
|
||||||
|
**Плохо:** «Установка и настройка» (что устанавливаем?),
|
||||||
|
«Использование» (что используем?), «Введение» (во что?).
|
||||||
|
|
||||||
|
#### Описание
|
||||||
|
|
||||||
|
Описание — короткий ответ на вопрос «у меня задача 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
|
||||||
# {Название}
|
Какие алиасы импортов есть в проекте и как ими пользоваться.
|
||||||
|
|
||||||
Краткое описание: о чём раздел.
|
|
||||||
|
|
||||||
## Что нужно знать
|
|
||||||
|
|
||||||
Неочевидная вводная информация (если есть).
|
|
||||||
|
|
||||||
## Структура
|
|
||||||
|
|
||||||
Файловая организация. Обязательно — дерево файлов.
|
|
||||||
|
|
||||||
## Правила
|
|
||||||
|
|
||||||
### Реализация
|
|
||||||
|
|
||||||
Как писать код: синтаксис, паттерны, API.
|
|
||||||
|
|
||||||
### Организация
|
|
||||||
|
|
||||||
Где что лежит: файловые границы, зоны ответственности, экспорт.
|
|
||||||
|
|
||||||
## Именование
|
|
||||||
|
|
||||||
Специфичные для области соглашения (не покрытые в basics/naming).
|
|
||||||
|
|
||||||
## Типизация
|
|
||||||
|
|
||||||
Специфичные для области правила (не покрытые в basics/typing).
|
|
||||||
|
|
||||||
## Документирование
|
|
||||||
|
|
||||||
Специфичные для области правила (не покрытые в basics/documentation).
|
|
||||||
|
|
||||||
## Примеры
|
|
||||||
|
|
||||||
Полноценные примеры кода с путями к файлам.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Порядок фиксированный: контекст -> структура -> правила -> специализации базовых -> примеры.
|
```markdown
|
||||||
|
Установка и настройка линтера-форматтера в новом проекте.
|
||||||
|
```
|
||||||
|
|
||||||
## Конвенции оформления
|
```markdown
|
||||||
|
Из чего состоит проект и где что лежит.
|
||||||
|
```
|
||||||
|
|
||||||
### Заголовки
|
```markdown
|
||||||
|
Получение REST-данных в серверных компонентах.
|
||||||
|
```
|
||||||
|
|
||||||
- Один `h1` на файл — совпадает с `title` из frontmatter.
|
**Плохо:**
|
||||||
- Сразу после `h1` — вводный абзац.
|
|
||||||
- Основные секции — `h2`. Подсекции — `h3`. `h4` не используется.
|
```markdown
|
||||||
|
Раздел описывает, какие алиасы используются в проекте: их полный список,
|
||||||
|
где они объявлены и как ими пользоваться между модулями и внутри модуля.
|
||||||
|
```
|
||||||
|
|
||||||
|
_Начинается с «Раздел описывает», пересказывает содержимое._
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Установка и настройка CSS-процессора PostCSS в проекте: набор плагинов,
|
||||||
|
конфиг `postcss.config.mjs`. Выполняется один раз при заведении проекта.
|
||||||
|
```
|
||||||
|
|
||||||
|
_Упомянут конкретный файл, перечисление аспектов превратилось в оглавление._
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Правила работы с React-компонентами: файловая структура,
|
||||||
|
типизация пропсов, документирование, реализация.
|
||||||
|
```
|
||||||
|
|
||||||
|
_Дежурный префикс «Правила работы с...» плюс оглавление подсекций._
|
||||||
|
|
||||||
### Примеры кода
|
### Примеры кода
|
||||||
|
|
||||||
- Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `.
|
- Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `.
|
||||||
- Путь к файлу — перед блоком кода или комментарием внутри.
|
- Путь к файлу указывается перед блоком кода текстом или комментарием внутри блока.
|
||||||
- Дерево файлов — ` ```text ` с символами `├──`, `└──`, `│`.
|
- Дерево файлов — ` ```text ` с символами `├──`, `└──`, `│`.
|
||||||
|
|
||||||
### Блоки «Хорошо / Плохо»
|
### Блоки «Хорошо / Плохо»
|
||||||
|
|
||||||
|
Используются для контрастного сравнения правильного и неправильного подхода.
|
||||||
|
|
||||||
|
Формат:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
**Хорошо:**
|
**Хорошо:**
|
||||||
|
|
||||||
@@ -140,16 +333,16 @@ when: "Когда агенту читать этот раздел"
|
|||||||
|
|
||||||
### Таблицы
|
### Таблицы
|
||||||
|
|
||||||
|
Используются для структурированных перечислений: настройки, команды, соответствия.
|
||||||
Формат — стандартный Markdown: `| Ключ | Описание |`.
|
Формат — стандартный Markdown: `| Ключ | Описание |`.
|
||||||
|
|
||||||
### Ссылки между разделами
|
### Ссылки между разделами
|
||||||
|
|
||||||
Ссылаться можно, дублировать содержимое — нет.
|
Прикладной раздел может ссылаться на другие разделы, но не дублирует их содержимое.
|
||||||
|
|
||||||
## Принципы
|
## Принципы
|
||||||
|
|
||||||
1. **Не дублировать.** Одна мысль — одно место. Остальные ссылаются.
|
1. **Не дублировать.** Одна мысль живёт в одном месте. Остальные ссылаются.
|
||||||
2. **Базовое vs прикладное.** Применимо ко всему коду — базовое. К одной области — прикладное.
|
2. **Базовое vs прикладное.** Если правило применимо ко всему коду — оно базовое. Если только к одной области — прикладное.
|
||||||
3. **Общее vs специфичное.** Одинаково для всех фреймворков — в `base/`. Для одного — в `{framework}/`.
|
3. **Пустые секции не создавать.** Если для раздела нет специфики по именованию — секции «Именование» в нём нет.
|
||||||
4. **Пустые секции не создавать.**
|
4. **Примеры обязательны.** Прикладной раздел без примеров кода — незавершён.
|
||||||
5. **Примеры обязательны.** Прикладной раздел без примеров — незавершён.
|
|
||||||
|
|||||||
41
Caddyfile
41
Caddyfile
@@ -1,5 +1,44 @@
|
|||||||
:8080 {
|
:8080 {
|
||||||
root * /srv
|
root * /srv
|
||||||
|
|
||||||
|
# Устаревшие пути 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
|
file_server
|
||||||
try_files {path} /index.html
|
# 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
|
||||||
|
|||||||
19
OLD_parts/1-assistent.md
Normal file
19
OLD_parts/1-assistent.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Ассистент
|
||||||
|
|
||||||
|
## Для ассистента
|
||||||
|
|
||||||
|
- Всегда используй Русский язык для общения и генерации документации/комментариев/коммитов.
|
||||||
|
- Всегда следуй этим правилам при генерации кода и ответах.
|
||||||
|
- Всегда пиши план действий перед генерацией кода.
|
||||||
|
- Всегда спрашивай разрешения у пользователя перед генерацией кода.
|
||||||
|
- Всегда проверяй, что код соответствует линтингу и форматированию.
|
||||||
|
- Всегда сверяйся с чек-листом при генерации кода.
|
||||||
|
- Не предлагай решения, которые противоречат этим правилам этого файла.
|
||||||
|
- Если не уверен — уточни у пользователя, не гадай, не придумывай.
|
||||||
|
|
||||||
|
## Обязательность чек-листов
|
||||||
|
|
||||||
|
- Все чек-листы, приведённые в правилах, обязательны к исполнению.
|
||||||
|
- Ассистент обязан сверяться с чек-листом при выполнении любой задачи, связанной с кодом.
|
||||||
|
- Нельзя сокращать, игнорировать или опускать пункты чек-листа — каждый пункт должен быть выполнен или явно отмечен как невыполнимый с объяснением причины.
|
||||||
|
- В каждом ответе, связанном с генерацией или изменением кода, ассистент обязан ссылаться на соответствующий чек-лист и подтверждать его выполнение.
|
||||||
84
OLD_parts/10-stores.md
Normal file
84
OLD_parts/10-stores.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
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 и смысл полей (см. [правила документирования кода](#правило-для-документирования-кода)).
|
||||||
|
- [ ] Нет неиспользуемого или невалидного кода.
|
||||||
|
- [ ] Экспорт через индексный файл.
|
||||||
227
OLD_parts/11-css.md
Normal file
227
OLD_parts/11-css.md
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
---
|
||||||
|
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 или используются переменные.
|
||||||
88
OLD_parts/12-components.md
Normal file
88
OLD_parts/12-components.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
68
OLD_parts/13-hooks.md
Normal file
68
OLD_parts/13-hooks.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Хуки (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.
|
||||||
|
- [ ] Документировано только назначение хука (см. [правила документирования кода](#правило-для-документирования-кода)).
|
||||||
|
- [ ] Нет неиспользуемого или невалидного кода.
|
||||||
|
- [ ] Экспорт только именованный через индексный файл.
|
||||||
124
OLD_parts/14-api-hooks.md
Normal file
124
OLD_parts/14-api-hooks.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# Хуки 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 напрямую в компонентах, только через хуки.**
|
||||||
|
- [ ] Обработка состояний загрузки, ошибки и данных реализована корректно.
|
||||||
|
- [ ] Не происходит дублирования логики, связанной с получением данных.
|
||||||
|
- [ ] Нет неиспользуемого или невалидного кода.
|
||||||
242
OLD_parts/15-api.md
Normal file
242
OLD_parts/15-api.md
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
# 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.
|
||||||
|
- [ ] Нет дублирования логики и неиспользуемого кода.
|
||||||
21
OLD_parts/3-general-principles.md
Normal file
21
OLD_parts/3-general-principles.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
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) для локализации всех пользовательских текстов.
|
||||||
22
OLD_parts/4-arkhitektura.md
Normal file
22
OLD_parts/4-arkhitektura.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title: Архитектура
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектура
|
||||||
|
|
||||||
|
## Архитектура проекта
|
||||||
|
В проекте используется FSD (Feature-Sliced Design) архитектура.
|
||||||
|
|
||||||
|
- **FSD-границы**
|
||||||
|
- Не нарушать границы слоёв (например, feature не может импортировать из widgets).
|
||||||
|
- Бизнес-логика должна быть вынесена в хуки или сервисы.
|
||||||
|
- **Импорты**
|
||||||
|
- Внутри слоя — относительные импорты.
|
||||||
|
- Между слоями — абсолютные импорты.
|
||||||
|
- **Требования**
|
||||||
|
- Не смешивать логику разных слоёв.
|
||||||
|
- Не хранить бизнес-логику в UI-компонентах.
|
||||||
|
- **Именование**
|
||||||
|
- Файлы и папки kebab-case.
|
||||||
|
|
||||||
|
---
|
||||||
74
OLD_parts/5-code-style.md
Normal file
74
OLD_parts/5-code-style.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
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`.
|
||||||
18
OLD_parts/6-naming.md
Normal file
18
OLD_parts/6-naming.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
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`
|
||||||
69
OLD_parts/7-docs.md
Normal file
69
OLD_parts/7-docs.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
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 — статус выполнения
|
||||||
|
*/
|
||||||
|
```
|
||||||
187
OLD_parts/8-typing.md
Normal file
187
OLD_parts/8-typing.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
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`, `{}`).
|
||||||
|
- [ ] Для внешних библиотек используются официальные типы или собственные декларации.
|
||||||
|
- [ ] Нет неявного приведения типов, все типы читаемы и прозрачны.
|
||||||
12
OLD_parts/9-localization.md
Normal file
12
OLD_parts/9-localization.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
title: Локализация
|
||||||
|
---
|
||||||
|
|
||||||
|
# Локализация
|
||||||
|
|
||||||
|
## Правила использования локализации
|
||||||
|
|
||||||
|
- Все пользовательские тексты должны быть вынесены в локализационные файлы.
|
||||||
|
- Для каждого компонента создавать папку `locales/` с файлами `ru.json`, `en.json` и т.д.
|
||||||
|
- Новые namespace обязательно регистрировать в экземпляре i18n (см. `app/i18n.ts`).
|
||||||
|
- В коде использовать только функцию перевода из i18n, не использовать "жёстко" прописанные строки.
|
||||||
105
README.md
105
README.md
@@ -1,53 +1,80 @@
|
|||||||
# Style Guide
|
# NextJS Style Guide
|
||||||
|
|
||||||
Репозиторий с правилами и стандартами фронтенд-разработки. Исходники документации собираются в разные форматы под разные фреймворки.
|
Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.
|
||||||
|
|
||||||
## Структура
|
Сайт: https://nextjs-style-guide.gromlab.ru
|
||||||
|
|
||||||
```text
|
## Использование
|
||||||
src/ # Исходники — только .md файлы
|
|
||||||
├── base/ # Общие правила (не поставляется отдельно)
|
|
||||||
│ ├── basics/ # Базовые: стиль кода, именование, типизация
|
|
||||||
│ ├── applied/ # Прикладные: компоненты, стили, хуки, API
|
|
||||||
│ └── triggers/ # Триггеры: создание компонента, стилизация и т.д.
|
|
||||||
│
|
|
||||||
└── nextjs/ # Next.js — самостоятельная единица
|
|
||||||
├── applied/ # Next.js-специфичные: page-level, project-structure
|
|
||||||
├── triggers/ # Next.js-специфичные триггеры: create-page, create-layout
|
|
||||||
├── DEVELOP.md # Точка входа для агента-разработчика
|
|
||||||
└── REVIEW.md # Точка входа для агента-ревьювера
|
|
||||||
|
|
||||||
scripts/ # Скрипты и манифесты сборки
|
**Для AI-агентов:**
|
||||||
├── build-ai.js # Скрипт сборки
|
|
||||||
└── nextjs.build.js # Манифест: какие файлы, куда, как называются
|
|
||||||
|
|
||||||
dist/ # Собранные версии (gitignore)
|
- [llms.txt](https://nextjs-style-guide.gromlab.ru/llms.txt) — Карта разделов, оглавление со ссылками на разделы.
|
||||||
├── ai/{framework}/ # Для AI-агентов
|
- [llms-full.txt](https://nextjs-style-guide.gromlab.ru/llms-full.txt) — Вся документация одним файлом.
|
||||||
└── vitepress/{framework}/ # Для людей (планируется)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Сборка
|
**Для проекта:**
|
||||||
|
|
||||||
```bash
|
- [nextjs-style-guide.zip](https://nextjs-style-guide.gromlab.ru/nextjs-style-guide.zip) — Набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
|
||||||
npm run build:ai # Собрать все фреймворки
|
|
||||||
```
|
|
||||||
|
|
||||||
## Манифест
|
## Структура документации
|
||||||
|
|
||||||
Каждый фреймворк имеет манифест `scripts/{framework}.build.js`. Ключ — путь в выходной папке, значение — путь исходника в `src/`.
|
### Workflow
|
||||||
|
|
||||||
Скрипт только копирует файлы по манифесту. Никакой генерации.
|
[Workflow](docs/docs/workflow.md) — пошаговая карта типовых задач: с чего начать, как добавить страницу/компонент, как подключить данные, стили, локализацию.
|
||||||
|
|
||||||
## Добавление раздела
|
### Базовые правила
|
||||||
|
|
||||||
1. Создать `.md` в `src/base/` (общий) или `src/{framework}/` (специфичный).
|
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
|
||||||
2. Добавить frontmatter: `title`, `scope`, `keywords`, `when`.
|
|
||||||
3. Добавить путь в манифест `scripts/{framework}.build.js`.
|
|
||||||
4. Обновить точку входа (`DEVELOP.md` и/или `REVIEW.md`).
|
|
||||||
5. `npm run build:ai`.
|
|
||||||
|
|
||||||
## Добавление фреймворка
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Технологии и библиотеки | Какой стек используем? |
|
||||||
|
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
||||||
|
| Архитектура: Обзор | Что такое SLM и зачем она нужна? |
|
||||||
|
| Архитектура: Слои | Какие слои есть и как между ними устроены зависимости? |
|
||||||
|
| Архитектура: Модули | Что такое модуль и как он устроен? |
|
||||||
|
| Архитектура: Сегменты | Какие сегменты есть внутри модуля? |
|
||||||
|
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
||||||
|
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||||
|
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||||
|
|
||||||
1. Создать `src/{framework}/` с `.md` файлами и точками входа.
|
### Установка и настройка
|
||||||
2. Создать `scripts/{framework}.build.js`.
|
|
||||||
3. `npm run build:ai`.
|
**Как поднять и сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Создание проекта: Из шаблона | Как начать проект из готового шаблона? |
|
||||||
|
| Создание проекта: Вручную | Как поднять проект с нуля без шаблона? |
|
||||||
|
| Next.js | Как настроить Next.js под проект? |
|
||||||
|
| Алиасы | Как настроить путевые алиасы импортов? |
|
||||||
|
| Biome | Как настроить Biome (линтер и форматер)? |
|
||||||
|
| Стили | Как подключить и настроить стили проекта? |
|
||||||
|
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
|
||||||
|
| SVG-спрайты | Как настроить генерацию SVG-спрайтов? |
|
||||||
|
| Шаблоны генерации | Как подключить шаблоны для кодогенерации? |
|
||||||
|
| VS Code | Как настроить редактор для проекта? |
|
||||||
|
|
||||||
|
### Использование
|
||||||
|
|
||||||
|
**Как это устроено и как этим пользоваться** — структура, примеры и правила для конкретных областей.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||||
|
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||||
|
| Страницы (App Router) | Как описывать layout, page, loading, error, not-found? |
|
||||||
|
| Данные: Введение | Как устроена работа с данными в проекте? |
|
||||||
|
| Данные: REST: Клиенты: Автоматическая генерация | Как сгенерировать REST-клиент автоматически из OpenAPI? |
|
||||||
|
| Данные: REST: Клиенты: Ручное создание | Как написать REST-клиент вручную? |
|
||||||
|
| Данные: REST: Получение данных: Серверные компоненты | Как получать данные в серверных компонентах? |
|
||||||
|
| Данные: REST: Получение данных: Клиентские компоненты | Как получать данные в клиентских компонентах (SWR)? |
|
||||||
|
| Данные: Realtime | Как работать с realtime-каналами и сокетами? |
|
||||||
|
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||||
|
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
||||||
|
| Изображения | _(не заполнен)_ |
|
||||||
|
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
|
||||||
|
| Видео | _(не заполнен)_ |
|
||||||
|
| Stores | _(не заполнен)_ |
|
||||||
|
| Хуки | _(не заполнен)_ |
|
||||||
|
| Шрифты | _(не заполнен)_ |
|
||||||
|
| Локализация | _(не заполнен)_ |
|
||||||
|
|||||||
174
docs-overview.md
Normal file
174
docs-overview.md
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# Обзор страниц документации
|
||||||
|
|
||||||
|
Список всех `.md`-страниц в `docs/docs/` в порядке сайдбара (`.vitepress/config.ts`).
|
||||||
|
Поля: путь к файлу, заголовок (`h1`), описание (абзац под заголовком).
|
||||||
|
|
||||||
|
## Главная
|
||||||
|
|
||||||
|
### docs/docs/index.md
|
||||||
|
**Заголовок:** NextJS Style Guide
|
||||||
|
**Описание:** Стандарты разработки фронтенд-приложений на Next.js и TypeScript.
|
||||||
|
|
||||||
|
## Подсказки
|
||||||
|
|
||||||
|
### docs/docs/workflow.md
|
||||||
|
**Заголовок:** Подсказки
|
||||||
|
**Описание:** Короткие ответы на типовые вопросы и решения для спорных ситуаций.
|
||||||
|
|
||||||
|
## Базовые правила
|
||||||
|
|
||||||
|
### docs/docs/basics/tech-stack.md
|
||||||
|
**Заголовок:** Технологии и библиотеки
|
||||||
|
**Описание:** Какие библиотеки и инструменты используются в проекте.
|
||||||
|
|
||||||
|
### docs/docs/basics/naming.md
|
||||||
|
**Заголовок:** Именование
|
||||||
|
**Описание:** Как называть переменные, файлы и прочие сущности в коде.
|
||||||
|
|
||||||
|
### docs/docs/basics/architecture/index.md
|
||||||
|
**Заголовок:** SLM Design
|
||||||
|
**Описание:** Архитектурный подход проекта: что такое SLM и как он устроен.
|
||||||
|
|
||||||
|
### docs/docs/basics/architecture/reference/layers.md
|
||||||
|
**Заголовок:** Слои
|
||||||
|
**Описание:** Из каких слоёв состоит архитектура и как они связаны.
|
||||||
|
|
||||||
|
### docs/docs/basics/architecture/reference/modules.md
|
||||||
|
**Заголовок:** Модули
|
||||||
|
**Описание:** Что такое модуль в архитектуре и как он устроен.
|
||||||
|
|
||||||
|
### docs/docs/basics/architecture/reference/segments.md
|
||||||
|
**Заголовок:** Сегменты
|
||||||
|
**Описание:** Что такое сегмент модуля и какие они бывают.
|
||||||
|
|
||||||
|
### docs/docs/basics/code-style.md
|
||||||
|
**Заголовок:** Стиль кода
|
||||||
|
**Описание:** Как оформляется код в проекте.
|
||||||
|
|
||||||
|
### docs/docs/basics/documentation.md
|
||||||
|
**Заголовок:** Документирование
|
||||||
|
**Описание:** Что и как документировать в коде.
|
||||||
|
|
||||||
|
### docs/docs/basics/typing.md
|
||||||
|
**Заголовок:** Типизация
|
||||||
|
**Описание:** Как типизируется код в проекте.
|
||||||
|
|
||||||
|
## Создание проекта
|
||||||
|
|
||||||
|
### docs/docs/creating-project/from-template.md
|
||||||
|
**Заголовок:** Создание проекта из шаблона
|
||||||
|
**Описание:** Создание нового проекта на основе готового шаблона.
|
||||||
|
|
||||||
|
### docs/docs/creating-project/manual.md
|
||||||
|
**Заголовок:** Создание проекта вручную
|
||||||
|
**Описание:** Поэтапное создание нового проекта без использования шаблона.
|
||||||
|
|
||||||
|
### docs/docs/creating-project/nextjs.md
|
||||||
|
**Заголовок:** Чистая установка Next.js
|
||||||
|
**Описание:** Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
|
||||||
|
|
||||||
|
## Настройка
|
||||||
|
|
||||||
|
### docs/docs/setup/aliases.md
|
||||||
|
**Заголовок:** Алиасы импортов
|
||||||
|
**Описание:** Какие алиасы импортов есть в проекте и как ими пользоваться.
|
||||||
|
|
||||||
|
### docs/docs/setup/biome.md
|
||||||
|
**Заголовок:** Biome
|
||||||
|
**Описание:** Установка и настройка линтера-форматтера в новом проекте.
|
||||||
|
|
||||||
|
### docs/docs/setup/postcss.md
|
||||||
|
**Заголовок:** PostCSS
|
||||||
|
**Описание:** Установка и настройка CSS-процессора в новом проекте.
|
||||||
|
|
||||||
|
### docs/docs/setup/styles.md
|
||||||
|
**Заголовок:** Стили
|
||||||
|
**Описание:** Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
|
||||||
|
|
||||||
|
### docs/docs/setup/svg-sprites.md
|
||||||
|
**Заголовок:** SVG-спрайты
|
||||||
|
**Описание:** Подключение SVG-спрайтов в новом проекте.
|
||||||
|
|
||||||
|
### docs/docs/setup/templates.md
|
||||||
|
**Заголовок:** Шаблоны генерации
|
||||||
|
**Описание:** Подключение шаблонов кодогенерации в новом проекте.
|
||||||
|
|
||||||
|
### docs/docs/setup/vscode.md
|
||||||
|
**Заголовок:** VS Code
|
||||||
|
**Описание:** Единые настройки редактора и расширений для команды.
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### docs/docs/usage/project-structure.md
|
||||||
|
**Заголовок:** Структура проекта
|
||||||
|
**Описание:** Из чего состоит проект и где что лежит.
|
||||||
|
|
||||||
|
### docs/docs/usage/components.md
|
||||||
|
**Заголовок:** Компоненты
|
||||||
|
**Описание:** Как устроен и пишется React-компонент в проекте.
|
||||||
|
|
||||||
|
### docs/docs/usage/page-level.md
|
||||||
|
**Заголовок:** Файлы роутинга
|
||||||
|
**Описание:** Что должно лежать в файлах роутинга, а что — в экранах.
|
||||||
|
|
||||||
|
### docs/docs/usage/templates-generation.md
|
||||||
|
**Заголовок:** Шаблоны и генерация кода
|
||||||
|
**Описание:** Как устроены шаблоны кодогенерации и как ими пользоваться.
|
||||||
|
|
||||||
|
### docs/docs/usage/styles.md
|
||||||
|
**Заголовок:** Стили
|
||||||
|
**Описание:** Как пишутся стили в проекте.
|
||||||
|
|
||||||
|
### docs/docs/usage/images-sprites.md
|
||||||
|
**Заголовок:** —
|
||||||
|
**Описание:** _(файл пустой)_
|
||||||
|
|
||||||
|
### docs/docs/usage/svg-sprites.md
|
||||||
|
**Заголовок:** SVG-спрайты
|
||||||
|
**Описание:** Как добавлять и использовать SVG-иконки в коде.
|
||||||
|
|
||||||
|
### docs/docs/usage/video.md
|
||||||
|
**Заголовок:** —
|
||||||
|
**Описание:** _(файл пустой)_
|
||||||
|
|
||||||
|
### docs/docs/usage/stores.md
|
||||||
|
**Заголовок:** —
|
||||||
|
**Описание:** _(файл пустой)_
|
||||||
|
|
||||||
|
### docs/docs/usage/hooks.md
|
||||||
|
**Заголовок:** —
|
||||||
|
**Описание:** _(файл пустой)_
|
||||||
|
|
||||||
|
### docs/docs/usage/fonts.md
|
||||||
|
**Заголовок:** —
|
||||||
|
**Описание:** _(файл пустой)_
|
||||||
|
|
||||||
|
### docs/docs/usage/localization.md
|
||||||
|
**Заголовок:** —
|
||||||
|
**Описание:** _(файл пустой)_
|
||||||
|
|
||||||
|
## Данные
|
||||||
|
|
||||||
|
### docs/docs/usage/data/index.md
|
||||||
|
**Заголовок:** Источники данных
|
||||||
|
**Описание:** Какие источники данных используются в проекте и как с ними работать.
|
||||||
|
|
||||||
|
### docs/docs/usage/data/rest/clients/auto.md
|
||||||
|
**Заголовок:** Автоматическая генерация
|
||||||
|
**Описание:** Генерация REST-клиента из OpenAPI-спецификации.
|
||||||
|
|
||||||
|
### docs/docs/usage/data/rest/clients/manual.md
|
||||||
|
**Заголовок:** Ручное создание
|
||||||
|
**Описание:** Создание REST-клиента вручную, когда нет OpenAPI-спецификации.
|
||||||
|
|
||||||
|
### docs/docs/usage/data/rest/fetching/server.md
|
||||||
|
**Заголовок:** Серверные компоненты
|
||||||
|
**Описание:** Получение REST-данных в серверных компонентах.
|
||||||
|
|
||||||
|
### docs/docs/usage/data/rest/fetching/client.md
|
||||||
|
**Заголовок:** Клиентские компоненты
|
||||||
|
**Описание:** Получение REST-данных в клиентских компонентах.
|
||||||
|
|
||||||
|
### docs/docs/usage/data/realtime.md
|
||||||
|
**Заголовок:** Realtime
|
||||||
|
**Описание:** Работа с push-данными от сервера: подписки и события.
|
||||||
100
docs/docs/basics/architecture/index.md
Normal file
100
docs/docs/basics/architecture/index.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
title: SLM Design
|
||||||
|
description: "Архитектурный подход проекта: что такое SLM и как он устроен."
|
||||||
|
---
|
||||||
|
|
||||||
|
# SLM Design
|
||||||
|
|
||||||
|
Архитектурный подход проекта: что такое SLM и как он устроен.
|
||||||
|
|
||||||
|
## Преимущества
|
||||||
|
|
||||||
|
### Вертикальная организация домена
|
||||||
|
|
||||||
|
Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы.
|
||||||
|
|
||||||
|
### Dependency Injection без фреймворков
|
||||||
|
|
||||||
|
Cross-domain зависимости в бизнес-слое реализуются через фабрики — модуль декларирует что ему нужно, а точка использования предоставляет зависимости. Домены изолированы без DI-контейнеров, провайдеров и шин событий.
|
||||||
|
|
||||||
|
### Разделение ответственности без перегрузки слоёв
|
||||||
|
|
||||||
|
Сервисы приложения (`infrastructure/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
|
||||||
|
|
||||||
|
### Горизонтальная инкапсуляция
|
||||||
|
|
||||||
|
Вложенные модули (`parts/`) и направление зависимостей позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга.
|
||||||
|
|
||||||
|
### Колокация по умолчанию
|
||||||
|
|
||||||
|
Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями.
|
||||||
|
|
||||||
|
### Явное разделение каркаса и контента
|
||||||
|
|
||||||
|
Каркас группы маршрутов (`layouts/`) и контент конкретной страницы (`screens/`) — независимые слои с собственной ответственностью.
|
||||||
|
|
||||||
|
### Масштабирование через группировку
|
||||||
|
|
||||||
|
При росте проекта слои не теряют структуру — модули группируются по естественным признакам: бизнес-домены по субдоменам, страницы по разделам, UI-компоненты по уровню абстракции (примитивы и композиции).
|
||||||
|
|
||||||
|
## Происхождение
|
||||||
|
|
||||||
|
SLM Design вырос на основе:
|
||||||
|
|
||||||
|
- **Feature-Sliced Design** — слоистая структура, публичный API модуля, направление зависимостей
|
||||||
|
- **Vertical Slice Architecture** — модуль как вертикальный срез, содержащий всё необходимое
|
||||||
|
- **Screaming Architecture** — структура проекта «кричит» о назначении: открыл `business/auth` — видишь авторизацию
|
||||||
|
- **Colocation Principle** — код живёт рядом с местом использования
|
||||||
|
|
||||||
|
## Пример структуры проекта
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
├── app/
|
||||||
|
│
|
||||||
|
├── layouts/
|
||||||
|
│ ├── main/
|
||||||
|
│ └── dashboard/
|
||||||
|
│
|
||||||
|
├── screens/
|
||||||
|
│ ├── home/
|
||||||
|
│ ├── products/
|
||||||
|
│ ├── product-detail/
|
||||||
|
│ └── about/
|
||||||
|
│
|
||||||
|
├── widgets/
|
||||||
|
│ ├── page-heading/
|
||||||
|
│ ├── hero-section/
|
||||||
|
│ └── promo-banner/
|
||||||
|
│
|
||||||
|
├── business/
|
||||||
|
│ ├── auth/
|
||||||
|
│ ├── catalog/
|
||||||
|
│ ├── orders/
|
||||||
|
│ └── chat/
|
||||||
|
│
|
||||||
|
├── infrastructure/
|
||||||
|
│ ├── theme/
|
||||||
|
│ ├── i18n/
|
||||||
|
│ ├── backend-api/
|
||||||
|
│ └── logger/
|
||||||
|
│
|
||||||
|
├── ui/
|
||||||
|
│ ├── button/
|
||||||
|
│ ├── input/
|
||||||
|
│ ├── modal/
|
||||||
|
│ ├── toast/
|
||||||
|
│ └── dropdown/
|
||||||
|
│
|
||||||
|
└── shared/
|
||||||
|
├── lib/
|
||||||
|
├── types/
|
||||||
|
└── styles/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Принципы
|
||||||
|
|
||||||
|
- **Домен — единое целое.** Всё, что относится к домену, живёт в одном модуле.
|
||||||
|
- **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости.
|
||||||
|
- **Зависимости однонаправлены.** Импорты только сверху вниз, только через публичный API.
|
||||||
|
- **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.
|
||||||
253
docs/docs/basics/architecture/reference/layers.md
Normal file
253
docs/docs/basics/architecture/reference/layers.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
---
|
||||||
|
title: Слои
|
||||||
|
description: Из каких слоёв состоит архитектура и как они связаны.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Слои
|
||||||
|
|
||||||
|
Из каких слоёв состоит архитектура и как они связаны.
|
||||||
|
|
||||||
|
## Определение
|
||||||
|
|
||||||
|
**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.**
|
||||||
|
|
||||||
|
## Группы слоёв
|
||||||
|
|
||||||
|
Слои делятся на три группы:
|
||||||
|
|
||||||
|
| Группа | Слои | Описание |
|
||||||
|
|--------|------|----------|
|
||||||
|
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
|
||||||
|
| Ядро | `business`, `infrastructure`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
|
||||||
|
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
|
||||||
|
|
||||||
|
## Направление зависимостей
|
||||||
|
|
||||||
|
Любой импорт между модулями — только через публичный API.
|
||||||
|
|
||||||
|
```
|
||||||
|
app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared
|
||||||
|
```
|
||||||
|
|
||||||
|
- `layouts` и `screens` — параллельные слои, не импортируют друг друга
|
||||||
|
- Модули одного слоя в группе «Композиция» изолированы друг от друга
|
||||||
|
- Модули одного слоя `infrastructure` и `ui` могут импортировать друг друга через публичный API
|
||||||
|
- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую
|
||||||
|
- Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях
|
||||||
|
|
||||||
|
|
||||||
|
## Слой App
|
||||||
|
|
||||||
|
Точка входа приложения. Отвечает за запуск, роутинг и композицию маршрутов из layout и screen.
|
||||||
|
|
||||||
|
В отличие от остальных слоёв, `app/` не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации.
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация
|
||||||
|
- Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов
|
||||||
|
- Провайдеры и гарды — только подключает готовые из нижних слоёв, не реализует
|
||||||
|
- Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы
|
||||||
|
- Никем не импортируется
|
||||||
|
|
||||||
|
## Слой Layouts
|
||||||
|
|
||||||
|
Каркас страницы: общие элементы, одинаковые для группы маршрутов (header, footer, sidebar).
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/layouts/
|
||||||
|
├── main/
|
||||||
|
├── dashboard/
|
||||||
|
└── auth/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Содержит только модули
|
||||||
|
- Не содержит бизнес-логику
|
||||||
|
- Контекстно-зависимые блоки принимает через пропсы от `app`, не импортирует напрямую
|
||||||
|
|
||||||
|
## Слой Screens
|
||||||
|
|
||||||
|
Контент конкретной страницы: собирает её из модулей нижних слоёв.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/screens/
|
||||||
|
├── home/
|
||||||
|
├── products/
|
||||||
|
├── product-detail/
|
||||||
|
├── about/
|
||||||
|
└── contacts/
|
||||||
|
```
|
||||||
|
|
||||||
|
Когда количество страниц затрудняет навигацию — вводится группировка по разделам. Группа — папка для организации, не модуль (без `index.ts`).
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/screens/
|
||||||
|
├── shop/
|
||||||
|
│ ├── home/
|
||||||
|
│ ├── products/
|
||||||
|
│ ├── product-detail/
|
||||||
|
│ └── cart/
|
||||||
|
├── account/
|
||||||
|
│ ├── profile/
|
||||||
|
│ ├── settings/
|
||||||
|
│ └── order-history/
|
||||||
|
└── info/
|
||||||
|
├── about/
|
||||||
|
├── contacts/
|
||||||
|
└── faq/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Содержит только модули
|
||||||
|
- Не содержит бизнес-логику
|
||||||
|
- Локальные одноразовые секции живут внутри screen-модуля, не выносятся в `widgets`/`business`
|
||||||
|
|
||||||
|
## Слой Widgets
|
||||||
|
|
||||||
|
Составной блок интерфейса, который компонует модули ядра, но не принадлежит конкретному бизнес-домену. Widget появляется когда блок используется в нескольких screens или layouts.
|
||||||
|
|
||||||
|
Если блок принадлежит домену — он живёт в `business/{area}/`, даже если переиспользуется. Если блок нужен только в одном месте — это `screens/{name}/parts/` или `layouts/{name}/parts/`, а не widget.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/widgets/
|
||||||
|
├── page-heading/
|
||||||
|
├── hero-section/
|
||||||
|
├── onboarding-checklist/
|
||||||
|
├── promo-banner/
|
||||||
|
└── error-boundary/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Не принадлежит конкретному бизнес-домену. Если блок доменный — он живёт в `business/`
|
||||||
|
- Используется в нескольких screens или layouts
|
||||||
|
|
||||||
|
## Слой Business
|
||||||
|
|
||||||
|
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
|
||||||
|
|
||||||
|
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Cross-domain зависимости по коду реализуются через фабрику. `import type` между доменами разрешён напрямую.
|
||||||
|
|
||||||
|
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/business/
|
||||||
|
├── auth/
|
||||||
|
├── catalog/
|
||||||
|
├── orders/
|
||||||
|
├── checkout/
|
||||||
|
└── chat/
|
||||||
|
```
|
||||||
|
|
||||||
|
Когда количество доменов затрудняет навигацию — вводится группировка по субдоменам. Группа — папка для организации, не модуль (без `index.ts`).
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/business/
|
||||||
|
├── commerce/
|
||||||
|
│ ├── catalog/
|
||||||
|
│ ├── cart/
|
||||||
|
│ ├── orders/
|
||||||
|
│ └── checkout/
|
||||||
|
└── communication/
|
||||||
|
├── chat/
|
||||||
|
└── notifications/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Один модуль = один бизнес-домен
|
||||||
|
- Циклические зависимости между доменами запрещены
|
||||||
|
- Импорт кода между доменами — через фабрику. `import type` — напрямую
|
||||||
|
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
|
||||||
|
|
||||||
|
## Слой Infrastructure
|
||||||
|
|
||||||
|
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
|
||||||
|
|
||||||
|
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`.
|
||||||
|
|
||||||
|
Отличие от `shared/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/
|
||||||
|
├── theme/
|
||||||
|
├── i18n/
|
||||||
|
├── backend-api/
|
||||||
|
├── maps-api/
|
||||||
|
├── logger/
|
||||||
|
├── feature-flags/
|
||||||
|
└── realtime/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Один модуль = один техсервис
|
||||||
|
- Импортирует `infrastructure/`, `ui/`, `shared/`
|
||||||
|
|
||||||
|
## Слой UI
|
||||||
|
|
||||||
|
UI-кит без бизнес-логики: button, carousel, toast, modal.
|
||||||
|
|
||||||
|
Слой входит в группу «Ядро». Импортирует `ui/` и `shared/`.
|
||||||
|
|
||||||
|
Компоненты строятся друг на друге: `button` использует `icon`, `carousel` использует `button`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/ui/
|
||||||
|
├── button/
|
||||||
|
├── input/
|
||||||
|
├── icon/
|
||||||
|
├── carousel/
|
||||||
|
├── modal/
|
||||||
|
├── toast/
|
||||||
|
├── dropdown/
|
||||||
|
├── tabs/
|
||||||
|
└── tooltip/
|
||||||
|
```
|
||||||
|
|
||||||
|
Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (`button`, `icon`, `input`) не импортируют композиции. Композиции (`carousel`, `modal`, `dropdown`) строятся на примитивах.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/ui/
|
||||||
|
├── primitives/
|
||||||
|
│ ├── button/
|
||||||
|
│ ├── input/
|
||||||
|
│ ├── icon/
|
||||||
|
│ └── badge/
|
||||||
|
└── composites/
|
||||||
|
├── carousel/
|
||||||
|
├── modal/
|
||||||
|
├── dropdown/
|
||||||
|
├── tabs/
|
||||||
|
└── tooltip/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Не содержит бизнес-логику
|
||||||
|
- Импортирует только `ui/` и `shared/`
|
||||||
|
|
||||||
|
## Слой Shared
|
||||||
|
|
||||||
|
Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене.
|
||||||
|
|
||||||
|
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
|
||||||
|
|
||||||
|
Отличие от `infrastructure/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||||||
|
|
||||||
|
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/shared/
|
||||||
|
├── lib/
|
||||||
|
├── types/
|
||||||
|
├── styles/
|
||||||
|
└── sprites/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Не имеет runtime-состояния
|
||||||
165
docs/docs/basics/architecture/reference/modules.md
Normal file
165
docs/docs/basics/architecture/reference/modules.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
---
|
||||||
|
title: Модули
|
||||||
|
description: Что такое модуль в архитектуре и как он устроен.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Модули
|
||||||
|
|
||||||
|
Что такое модуль в архитектуре и как он устроен.
|
||||||
|
|
||||||
|
## Определение
|
||||||
|
|
||||||
|
**Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.**
|
||||||
|
|
||||||
|
## Модуль vs компонент
|
||||||
|
|
||||||
|
**Компонент** — один `.tsx` файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или `ui/` сегменте модуля.
|
||||||
|
|
||||||
|
**Модуль** — папка, которая может содержать корневой компонент, сегменты (`hooks/`, `types/`, `styles/`, `ui/`, `parts/` и т.д.) и публичный API (`index.ts`).
|
||||||
|
|
||||||
|
```text
|
||||||
|
auth/
|
||||||
|
├── ui/
|
||||||
|
│ ├── auth-guard.tsx
|
||||||
|
│ └── logout-button.tsx
|
||||||
|
├── parts/
|
||||||
|
│ ├── login-form/
|
||||||
|
│ ├── registration-form/
|
||||||
|
│ └── restore-form/
|
||||||
|
├── hooks/
|
||||||
|
├── stores/
|
||||||
|
├── types/
|
||||||
|
├── auth.tsx # корневой компонент (опционален)
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного `index.ts` с реэкспортом типов.
|
||||||
|
|
||||||
|
```text
|
||||||
|
{module-name}/
|
||||||
|
├── {module-name}.tsx # корневой компонент (опционален)
|
||||||
|
├── ui/ # компоненты модуля (только .tsx)
|
||||||
|
├── parts/ # вложенные модули (со своими сегментами)
|
||||||
|
├── hooks/ # хуки
|
||||||
|
├── stores/ # сторы состояния
|
||||||
|
├── services/ # внешние источники данных
|
||||||
|
├── mappers/ # трансформация данных между форматами
|
||||||
|
├── types/ # типы
|
||||||
|
├── styles/ # стили
|
||||||
|
├── lib/ # утилиты модуля
|
||||||
|
├── config/ # константы
|
||||||
|
└── index.ts # публичный API
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробное описание каждого сегмента — в разделе [Сегменты](/docs/basics/architecture/reference/segments).
|
||||||
|
|
||||||
|
## Публичный API
|
||||||
|
|
||||||
|
Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/auth/index.ts
|
||||||
|
export type { User, Session } from './types/user.types'
|
||||||
|
export { useAuth } from './hooks/use-auth.hook'
|
||||||
|
export { AuthGuard } from './ui/auth-guard'
|
||||||
|
```
|
||||||
|
|
||||||
|
Импорт в обход `index.ts` запрещён:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Плохо
|
||||||
|
import { validateToken } from '@/business/auth/lib/tokens'
|
||||||
|
|
||||||
|
// Хорошо
|
||||||
|
import { useAuth } from '@/business/auth'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Фабрика
|
||||||
|
|
||||||
|
Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове.
|
||||||
|
|
||||||
|
Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — `import type` не является runtime-зависимостью.
|
||||||
|
|
||||||
|
### Модуль без зависимостей — прямой экспорт:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/auth/index.ts
|
||||||
|
export { useAuth } from './hooks/use-auth'
|
||||||
|
export { useCurrentUser } from './hooks/use-current-user'
|
||||||
|
export type { User, Session } from './types'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Модуль с зависимостями — фабрика:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/chat/types/deps.ts
|
||||||
|
import type { User } from '@/business/auth'
|
||||||
|
|
||||||
|
export interface ChatDeps {
|
||||||
|
useCurrentUser: () => User | null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// business/chat/index.ts
|
||||||
|
import type { ChatDeps } from './types/deps'
|
||||||
|
|
||||||
|
export function chatFactory(deps: ChatDeps) {
|
||||||
|
return {
|
||||||
|
useMessages: (roomId: string) => {
|
||||||
|
const user = deps.useCurrentUser()
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
useSendMessage: (roomId: string) => {
|
||||||
|
const user = deps.useCurrentUser()
|
||||||
|
return (text: string) => { /* ... */ }
|
||||||
|
},
|
||||||
|
useChatRooms: () => {
|
||||||
|
const user = deps.useCurrentUser()
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
ChatBadge: ({ count }: { count: number }) => { /* ... */ },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { Message, ChatRoom } from './types'
|
||||||
|
export type { ChatDeps } from './types/deps'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Использование на странице:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// screens/support/support.tsx
|
||||||
|
import { useCurrentUser } from '@/business/auth'
|
||||||
|
import { chatFactory } from '@/business/chat'
|
||||||
|
|
||||||
|
const chat = chatFactory({ useCurrentUser })
|
||||||
|
|
||||||
|
export function SupportScreen() {
|
||||||
|
const { useMessages, useSendMessage, ChatBadge } = chat
|
||||||
|
const messages = useMessages('support')
|
||||||
|
const sendMessage = useSendMessage('support')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ChatBadge count={messages.length} />
|
||||||
|
{messages.map(m => <MessageBubble key={m.id} {...m} />)}
|
||||||
|
<MessageInput onSend={sendMessage} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Жизненный цикл
|
||||||
|
|
||||||
|
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
|
||||||
|
|
||||||
|
- Нужен на одной странице → `screens/{name}/parts/`
|
||||||
|
- Появился в 2+ местах → поднимается по природе:
|
||||||
|
- абстрактный UI → `ui/`
|
||||||
|
- блок с данными/логикой → `widgets/`
|
||||||
|
- представление бизнес-домена → `business/{area}/parts/`
|
||||||
|
|
||||||
|
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
|
||||||
154
docs/docs/basics/architecture/reference/segments.md
Normal file
154
docs/docs/basics/architecture/reference/segments.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
---
|
||||||
|
title: Сегменты
|
||||||
|
description: Что такое сегмент модуля и какие они бывают.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Сегменты
|
||||||
|
|
||||||
|
Что такое сегмент модуля и какие они бывают.
|
||||||
|
|
||||||
|
## Определение
|
||||||
|
|
||||||
|
**Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.**
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
| Сегмент | Содержимое |
|
||||||
|
|---------|------------|
|
||||||
|
| `ui/` | Компоненты модуля — только `.tsx` файлы |
|
||||||
|
| `parts/` | Вложенные модули со своими сегментами |
|
||||||
|
| `hooks/` | React-хуки |
|
||||||
|
| `stores/` | Сторы состояния |
|
||||||
|
| `services/` | Работа с внешними источниками данных |
|
||||||
|
| `mappers/` | Трансформация данных между форматами |
|
||||||
|
| `types/` | TypeScript-типы и интерфейсы |
|
||||||
|
| `styles/` | Стили |
|
||||||
|
| `lib/` | Утилиты и хелперы модуля |
|
||||||
|
| `config/` | Константы и конфигурация |
|
||||||
|
|
||||||
|
## Сегмент ui/
|
||||||
|
|
||||||
|
Компоненты, принадлежащие модулю. Содержит только `.tsx` файлы — без своих сегментов, стилей, типов, хуков. Использует сегменты родительского модуля.
|
||||||
|
|
||||||
|
```text
|
||||||
|
auth/
|
||||||
|
├── ui/
|
||||||
|
│ ├── auth-provider.tsx
|
||||||
|
│ ├── auth-guard.tsx
|
||||||
|
│ └── logout-button.tsx
|
||||||
|
├── types/
|
||||||
|
├── hooks/
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Если компоненту нужны собственные сегменты — это уже не `ui/`, а `parts/`.
|
||||||
|
|
||||||
|
## Сегмент parts/
|
||||||
|
|
||||||
|
Вложенные модули со своими сегментами. Каждый элемент `parts/` — полноценный модуль: папка с компонентом, хуками, стилями, типами и т.д.
|
||||||
|
|
||||||
|
```text
|
||||||
|
home/
|
||||||
|
├── parts/
|
||||||
|
│ ├── hero-section/
|
||||||
|
│ │ ├── hero-section.tsx
|
||||||
|
│ │ ├── styles/
|
||||||
|
│ │ └── parts/
|
||||||
|
│ │ └── top-banner/
|
||||||
|
│ │ └── top-banner.tsx
|
||||||
|
│ └── features-section/
|
||||||
|
│ ├── features-section.tsx
|
||||||
|
│ └── hooks/
|
||||||
|
├── home.screen.tsx
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Отличие от `ui/`: элемент `parts/` — модуль со своими сегментами. Элемент `ui/` — компонент, один `.tsx` файл.
|
||||||
|
|
||||||
|
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
|
||||||
|
|
||||||
|
Если вложенный модуль обрастает своими `parts/` — это сигнал, что он достаточно самостоятельный для подъёма на уровень выше.
|
||||||
|
|
||||||
|
## Сегмент hooks/
|
||||||
|
|
||||||
|
React-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты.
|
||||||
|
|
||||||
|
```text
|
||||||
|
hooks/
|
||||||
|
├── use-auth.hook.ts
|
||||||
|
├── use-session.hook.ts
|
||||||
|
└── use-permissions.hook.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сегмент stores/
|
||||||
|
|
||||||
|
Сторы состояния модуля. Конкретная реализация зависит от выбранного стейт-менеджера (Zustand, MobX, Redux и т.д.).
|
||||||
|
|
||||||
|
```text
|
||||||
|
stores/
|
||||||
|
├── auth.store.ts
|
||||||
|
└── session.store.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сегмент services/
|
||||||
|
|
||||||
|
Работа с внешними источниками данных: API-вызовы, запросы, подписки.
|
||||||
|
|
||||||
|
```text
|
||||||
|
services/
|
||||||
|
├── auth.service.ts
|
||||||
|
└── token.service.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сегмент mappers/
|
||||||
|
|
||||||
|
Функции трансформации данных из одного формата в другой: DTO в доменный тип, доменный тип в DTO, доменный тип в ViewModel.
|
||||||
|
|
||||||
|
```text
|
||||||
|
mappers/
|
||||||
|
├── map-user.ts
|
||||||
|
├── map-product.ts
|
||||||
|
└── map-order-to-dto.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сегмент types/
|
||||||
|
|
||||||
|
TypeScript-типы и интерфейсы модуля. Доменные типы, DTO, пропсы компонентов.
|
||||||
|
|
||||||
|
```text
|
||||||
|
types/
|
||||||
|
├── user.type.ts
|
||||||
|
└── session.type.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сегмент styles/
|
||||||
|
|
||||||
|
Стили модуля. Формат зависит от выбранного подхода (CSS Modules, SCSS, CSS-in-JS и т.д.).
|
||||||
|
|
||||||
|
```text
|
||||||
|
styles/
|
||||||
|
├── auth.module.css
|
||||||
|
└── login-form.module.css
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сегмент lib/
|
||||||
|
|
||||||
|
Утилиты и хелперы, специфичные для модуля. Чистые функции без побочных эффектов.
|
||||||
|
|
||||||
|
```text
|
||||||
|
lib/
|
||||||
|
├── validate-email.ts
|
||||||
|
└── format-phone.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Отличие от `shared/lib/`: здесь лежат утилиты, нужные только этому модулю. Общие утилиты — в `shared/lib/`.
|
||||||
|
|
||||||
|
## Сегмент config/
|
||||||
|
|
||||||
|
Константы и конфигурация модуля: маршруты, лимиты, дефолтные значения.
|
||||||
|
|
||||||
|
```text
|
||||||
|
config/
|
||||||
|
├── routes.ts
|
||||||
|
└── constants.ts
|
||||||
|
```
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Стиль кода
|
title: Стиль кода
|
||||||
scope: basics
|
description: Как оформляется код в проекте.
|
||||||
keywords: [форматирование, импорт, отступ, кавычки, early return, точка с запятой, линтер]
|
|
||||||
when: "Написание или ревью любого кода: форматирование, импорты, структура файла"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Стиль кода
|
# Стиль кода
|
||||||
|
|
||||||
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
|
Как оформляется код в проекте.
|
||||||
|
|
||||||
## Отступы
|
## Отступы
|
||||||
|
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Документирование
|
title: Документирование
|
||||||
scope: basics
|
description: Что и как документировать в коде.
|
||||||
keywords: [JSDoc, комментарий, документирование, описание функции, описание компонента]
|
|
||||||
when: "Документирование кода: JSDoc для функций, компонентов, типов"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Документирование
|
# Документирование
|
||||||
|
|
||||||
Этот раздел описывает правила документирования кода: когда и как писать
|
Что и как документировать в коде.
|
||||||
комментарии к компонентам, функциям, типам и интерфейсам.
|
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Именование
|
title: Именование
|
||||||
scope: basics
|
description: Как называть переменные, файлы и прочие сущности в коде.
|
||||||
keywords: [camelCase, kebab-case, PascalCase, имя файла, имя переменной, имя компонента, имя хука]
|
|
||||||
when: "Создание файлов, переменных, компонентов, хуков — выбор имени"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Именование
|
# Именование
|
||||||
|
|
||||||
Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту.
|
Как называть переменные, файлы и прочие сущности в коде.
|
||||||
|
|
||||||
## Базовые правила
|
## Базовые правила
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ when: "Создание файлов, переменных, компоненто
|
|||||||
|
|
||||||
**Хорошо**
|
**Хорошо**
|
||||||
```text
|
```text
|
||||||
features/
|
business/
|
||||||
└── auth-by-email/
|
└── auth-by-email/
|
||||||
├── ui/
|
├── ui/
|
||||||
│ └── login-form.tsx
|
│ └── login-form.tsx
|
||||||
@@ -64,14 +63,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,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Технологии и библиотеки
|
title: Технологии и библиотеки
|
||||||
scope: basics
|
description: Какие библиотеки и инструменты используются в проекте.
|
||||||
keywords: [стек, React, TypeScript, Next.js, Mantine, библиотека, зависимость]
|
|
||||||
when: "Выбор библиотеки или технологии, проверка допустимости зависимости"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Технологии и библиотеки
|
# Технологии и библиотеки
|
||||||
|
|
||||||
Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте.
|
Какие библиотеки и инструменты используются в проекте.
|
||||||
|
|
||||||
## Что используем
|
## Что используем
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ when: "Выбор библиотеки или технологии, провер
|
|||||||
- `Next.js` — для продуктовых сайтов.
|
- `Next.js` — для продуктовых сайтов.
|
||||||
|
|
||||||
### Архитектура
|
### Архитектура
|
||||||
- `FSD (Feature-Sliced Design)` — структура проекта и границы модулей. Используется кастомизированная версия — подробнее в разделе [Архитектура](/basics/architecture).
|
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/docs/basics/architecture/).
|
||||||
|
|
||||||
### UI компоненты
|
### UI компоненты
|
||||||
- `Mantine UI` — базовые UI-компоненты.
|
- `Mantine UI` — базовые UI-компоненты.
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Типизация
|
title: Типизация
|
||||||
scope: basics
|
description: Как типизируется код в проекте.
|
||||||
keywords: [type, interface, generic, any, unknown, enum, типизация, пропсы]
|
|
||||||
when: "Типизация кода: выбор type vs interface, работа с generic, запрет any"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Типизация
|
# Типизация
|
||||||
|
|
||||||
Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с `any`/`unknown`.
|
Как типизируется код в проекте.
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
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/setup/aliases) |
|
||||||
|
| Biome | Линтер и форматтер (замена ESLint + Prettier) | [Biome](/docs/setup/biome) |
|
||||||
|
| Стили | Глобальные токены и breakpoints | [Стили](/docs/setup/styles) |
|
||||||
|
| PostCSS | CSS-процессор для custom-media и вложенности | [PostCSS](/docs/setup/postcss) |
|
||||||
|
| SVG-спрайты | Иконки через `<SvgSprite/>`, управление цветом | [SVG-спрайты](/docs/setup/svg-sprites) |
|
||||||
|
| VS Code | Настройки редактора и расширения | [VS Code](/docs/setup/vscode) |
|
||||||
|
| Шаблоны генерации | `.templates/` для `@gromlab/create` | [Шаблоны генерации](/docs/setup/templates) |
|
||||||
|
|
||||||
|
Убрать компонент из состава — значит согласованно отказаться от части стайлгайда. Частичные проекты возможны (только Next.js, Next.js + стили и т.п.), но не являются эталоном.
|
||||||
|
|
||||||
|
## Канон раскладки
|
||||||
|
|
||||||
|
В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](/docs/usage/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/setup/aliases).
|
||||||
|
|
||||||
|
### 3. Biome
|
||||||
|
|
||||||
|
Линтер и форматтер. Подключается **до** написания кода, иначе в проекте копятся несогласованные правки.
|
||||||
|
|
||||||
|
См. [Biome](/docs/setup/biome).
|
||||||
|
|
||||||
|
### 4. Стили (базовая инфраструктура)
|
||||||
|
|
||||||
|
Файлы `variables.css`, `media.css`, `global.css` в `src/shared/styles/` и подключение `global.css` в `src/app/layout.tsx`. CSS-процессор на этом шаге не ставится.
|
||||||
|
|
||||||
|
См. [Стили](/docs/setup/styles).
|
||||||
|
|
||||||
|
### 5. PostCSS
|
||||||
|
|
||||||
|
CSS-процессор поверх базовых стилей: `@custom-media`, вложенность, autoprefixer. Ставится **только после шага 4** — опирается на `src/shared/styles/media.css`.
|
||||||
|
|
||||||
|
См. [PostCSS](/docs/setup/postcss).
|
||||||
|
|
||||||
|
### 6. SVG-спрайты
|
||||||
|
|
||||||
|
Пакет `@gromlab/svg-sprites`, генерация спрайт-файла и React-компонента `<SvgSprite/>`.
|
||||||
|
|
||||||
|
См. [SVG-спрайты](/docs/setup/svg-sprites).
|
||||||
|
|
||||||
|
### 7. VS Code
|
||||||
|
|
||||||
|
Расширения и настройки редактора. Опирается на установленный Biome (форматирование при сохранении) и PostCSS (ассоциация `*.css`).
|
||||||
|
|
||||||
|
См. [VS Code](/docs/setup/vscode).
|
||||||
|
|
||||||
|
### 8. Шаблоны генерации
|
||||||
|
|
||||||
|
Папка `.templates/` для генератора модулей `@gromlab/create`.
|
||||||
|
|
||||||
|
См. [Шаблоны генерации](/docs/setup/templates).
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- **Порядок шагов фиксирован.** Перестановка ломает зависимости (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/usage/project-structure)) |
|
||||||
|
| `--import-alias "@/*"` | Placeholder | Требуется флагом; после установки `paths` полностью переписывается на слой-префиксы (см. [Алиасы](/docs/setup/aliases)) |
|
||||||
|
| `--no-eslint` | ESLint не ставится | Линтер и форматтер — Biome ([Biome](/docs/setup/biome)) |
|
||||||
|
| `--no-tailwind` | Tailwind не ставится | Стилизация — PostCSS Modules ([Стили](/docs/usage/styles)) |
|
||||||
|
| `--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/usage/project-structure)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p src/shared/styles
|
||||||
|
```
|
||||||
|
|
||||||
|
Остальные слои (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) заводятся при появлении первого модуля в них. `src/shared/styles/` — единственный подкаталог `shared/`, который заводится сразу: без него не настроить стили на следующих шагах.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- **Конфликт с непустой директорией** — не удалять файлы пользователя автоматически. Ставить в подпапку или временно перенести посторонние файлы.
|
||||||
|
- **Отклонение от канонических флагов** (pnpm, Tailwind, ESLint и т.п.) — только осознанное, с пониманием, что стайлгайд этого не предусматривает.
|
||||||
|
- **Слои `src/`** не создавать авансом — появляются при первом модуле. Алиасы прописываются сразу на все восемь слоёв (см. [Алиасы](/docs/setup/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`).
|
||||||
83
docs/docs/index.md
Normal file
83
docs/docs/index.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
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/` или другую папку проекта.
|
||||||
|
|
||||||
|
## Структура документации
|
||||||
|
|
||||||
|
### Workflow
|
||||||
|
|
||||||
|
[Workflow](/docs/workflow) — пошаговая карта типовых задач: с чего начать, как добавить страницу/компонент, как подключить данные, стили, локализацию.
|
||||||
|
|
||||||
|
### Базовые правила
|
||||||
|
|
||||||
|
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Технологии и библиотеки | Какой стек используем? |
|
||||||
|
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
||||||
|
| Архитектура: Обзор | Что такое SLM и зачем она нужна? |
|
||||||
|
| Архитектура: Слои | Какие слои есть и как между ними устроены зависимости? |
|
||||||
|
| Архитектура: Модули | Что такое модуль и как он устроен? |
|
||||||
|
| Архитектура: Сегменты | Какие сегменты есть внутри модуля? |
|
||||||
|
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
||||||
|
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||||
|
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||||
|
|
||||||
|
### Установка и настройка
|
||||||
|
|
||||||
|
**Как поднять и сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Создание проекта: Из шаблона | Как начать проект из готового шаблона? |
|
||||||
|
| Создание проекта: Вручную | Как поднять проект с нуля без шаблона? |
|
||||||
|
| Next.js | Как настроить Next.js под проект? |
|
||||||
|
| Алиасы | Как настроить путевые алиасы импортов? |
|
||||||
|
| Biome | Как настроить Biome (линтер и форматер)? |
|
||||||
|
| Стили | Как подключить и настроить стили проекта? |
|
||||||
|
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
|
||||||
|
| SVG-спрайты | Как настроить генерацию SVG-спрайтов? |
|
||||||
|
| Шаблоны генерации | Как подключить шаблоны для кодогенерации? |
|
||||||
|
| VS Code | Как настроить редактор для проекта? |
|
||||||
|
|
||||||
|
### Использование
|
||||||
|
|
||||||
|
**Как это устроено и как этим пользоваться** — структура, примеры и правила для конкретных областей.
|
||||||
|
|
||||||
|
| Раздел | Отвечает на вопрос |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||||
|
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||||
|
| Страницы (App Router) | Как описывать layout, page, loading, error, not-found? |
|
||||||
|
| Данные: Введение | Как устроена работа с данными в проекте? |
|
||||||
|
| Данные: REST: Клиенты: Автоматическая генерация | Как сгенерировать REST-клиент автоматически из OpenAPI? |
|
||||||
|
| Данные: REST: Клиенты: Ручное создание | Как написать REST-клиент вручную? |
|
||||||
|
| Данные: REST: Получение данных: Серверные компоненты | Как получать данные в серверных компонентах? |
|
||||||
|
| Данные: REST: Получение данных: Клиентские компоненты | Как получать данные в клиентских компонентах (SWR)? |
|
||||||
|
| Данные: Realtime | Как работать с realtime-каналами и сокетами? |
|
||||||
|
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||||
|
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
||||||
|
| Изображения | _(не заполнен)_ |
|
||||||
|
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
|
||||||
|
| Видео | _(не заполнен)_ |
|
||||||
|
| Stores | _(не заполнен)_ |
|
||||||
|
| Хуки | _(не заполнен)_ |
|
||||||
|
| Шрифты | _(не заполнен)_ |
|
||||||
|
| Локализация | _(не заполнен)_ |
|
||||||
77
docs/docs/setup/aliases.md
Normal file
77
docs/docs/setup/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/reference/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/setup/biome.md
Normal file
81
docs/docs/setup/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/setup/vscode).
|
||||||
70
docs/docs/setup/postcss.md
Normal file
70
docs/docs/setup/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/usage/styles), раздел «Импорт стилей»).
|
||||||
177
docs/docs/setup/styles.md
Normal file
177
docs/docs/setup/styles.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
---
|
||||||
|
title: Стили
|
||||||
|
description: "Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили."
|
||||||
|
keywords: [variables.css, media.css, global.css, shared/styles, токены, переменные, custom-media, breakpoints, подключение стилей, базовые стили, инициализация]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Стили
|
||||||
|
|
||||||
|
Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Структура `src/` соответствует SLM ([Структура проекта](/docs/usage/project-structure)). Глобальные стили живут в `src/shared/styles/`, не в `src/app/`.
|
||||||
|
- В проекте нет `globals.css` с кастомным содержимым и не установлен `tailwindcss`.
|
||||||
|
|
||||||
|
## Файлы
|
||||||
|
|
||||||
|
Состав глобальных стилей — три файла:
|
||||||
|
|
||||||
|
| Файл | Роль |
|
||||||
|
|------|------|
|
||||||
|
| `variables.css` | Токены проекта (цвета, отступы, радиусы) |
|
||||||
|
| `media.css` | Custom media queries (брейкпоинты по ширине и высоте) |
|
||||||
|
| `global.css` | Точка сборки глобальных стилей: через `@import` тянет все остальные глобалы, импортируется в приложение один раз |
|
||||||
|
|
||||||
|
Правила подключения:
|
||||||
|
|
||||||
|
- В приложение импортируется **только** `global.css`.
|
||||||
|
- `variables.css` и будущие глобальные файлы (резеты, темы, типографика) подключаются в `global.css` через `@import`.
|
||||||
|
- `media.css` **не импортируется** — ни в `global.css`, ни в компоненты, ни в точку инициализации. Его читает CSS-процессор на этапе сборки (см. [PostCSS](/docs/setup/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/setup/postcss) — подключить процессор, чтобы заработали `@media (--md)` и вложенность.
|
||||||
|
- [Стили: использование](/docs/usage/styles) — правила написания CSS в компонентах.
|
||||||
|
- [SVG-спрайты](/docs/setup/svg-sprites) — стили иконок отдельно от глобальных.
|
||||||
107
docs/docs/setup/svg-sprites.md
Normal file
107
docs/docs/setup/svg-sprites.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
title: SVG-спрайты
|
||||||
|
description: Подключение SVG-спрайтов в новом проекте.
|
||||||
|
keywords: [svg-sprites, установка, настройка, config, пакет, "@gromlab/svg-sprites", svg-sprites.config.ts]
|
||||||
|
---
|
||||||
|
|
||||||
|
# SVG-спрайты
|
||||||
|
|
||||||
|
Подключение SVG-спрайтов в новом проекте.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- React 18+
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
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/usage/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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Стандартный конфиг
|
||||||
|
|
||||||
|
Файл `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`.
|
||||||
105
docs/docs/setup/templates.md
Normal file
105
docs/docs/setup/templates.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
title: Шаблоны генерации
|
||||||
|
description: Подключение шаблонов кодогенерации в новом проекте.
|
||||||
|
keywords: [шаблоны, templates, .templates, tiged, generator, генератор шаблонов, добавить шаблон, скачать шаблоны, scaffold]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Шаблоны генерации
|
||||||
|
|
||||||
|
Подключение шаблонов кодогенерации в новом проекте.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Доступен `npx` (ставится вместе с npm).
|
||||||
|
- Доступ к `gromlab.ru` (SSH-ключ добавлен) — для скачивания эталонного набора.
|
||||||
|
|
||||||
|
## Развилка
|
||||||
|
|
||||||
|
Сценарий зависит от состояния проекта:
|
||||||
|
|
||||||
|
| Состояние | Что делать |
|
||||||
|
|-----------|------------|
|
||||||
|
| В корне проекта **нет** `.templates/` | [Скачать стандартный набор](#скачать-стандартный-набор) |
|
||||||
|
| `.templates/` **есть**, нужен новый специфический шаблон | [Создать шаблон вручную](#создать-шаблон-вручную) |
|
||||||
|
| `.templates/` **есть**, нужно обновить до эталона | Скачать заново с подтверждением перезаписи |
|
||||||
|
|
||||||
|
## Скачать стандартный набор
|
||||||
|
|
||||||
|
### Установка
|
||||||
|
|
||||||
|
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-widget src/ui
|
||||||
|
```
|
||||||
|
|
||||||
|
После проверки — удалить тестовый модуль.
|
||||||
|
|
||||||
|
### Правила
|
||||||
|
|
||||||
|
- **Скачанные файлы не править.** Источник истины — репозиторий `templates/nextjs-template`. Изменения стандартных шаблонов делаются в нём, не в отдельных проектах.
|
||||||
|
- **Расположение — только `.templates/` в корне проекта.** Это требование расширения VS Code и CLI ([Шаблоны и генерация кода](/docs/usage/templates-generation)).
|
||||||
|
- **Если доступа к `gromlab.ru` нет** — не восстанавливать содержимое из памяти: разойдётся с каноном. Запросить шаблоны иным путём.
|
||||||
|
|
||||||
|
## Создать шаблон вручную
|
||||||
|
|
||||||
|
Если стандартного набора недостаточно и нужен специфический шаблон под проект.
|
||||||
|
|
||||||
|
### Установка
|
||||||
|
|
||||||
|
1. Прочитать [Шаблоны и генерация кода](/docs/usage/templates-generation) — секции «Структура шаблонов», «Синтаксис шаблонов», «Как создать новый шаблон».
|
||||||
|
|
||||||
|
2. Определить:
|
||||||
|
- имя шаблона (папка внутри `.templates/`);
|
||||||
|
- состав файлов;
|
||||||
|
- слой SLM предполагаемых потребителей ([Архитектура: слои](/docs/basics/architecture/reference/layers));
|
||||||
|
- минимальное содержимое каждого файла.
|
||||||
|
|
||||||
|
3. Проверить, что имя шаблона не конфликтует с существующей папкой в `.templates/`.
|
||||||
|
|
||||||
|
4. Создать структуру `.templates/{name}/` по канону из [Шаблоны и генерация кода](/docs/usage/templates-generation) — синтаксис переменных, правила именования файлов и содержимого.
|
||||||
|
|
||||||
|
5. Проверить генерацию:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @gromlab/create {name} test-entity {целевой путь}
|
||||||
|
```
|
||||||
|
|
||||||
|
После проверки — удалить тестовый артефакт.
|
||||||
|
|
||||||
|
### Правила
|
||||||
|
|
||||||
|
- **Если запрос покрыт стандартными шаблонами** (`component`, `widget`, `store`, `layout`, `screen`, `business`) — не создавать копию.
|
||||||
|
- **Стандартные шаблоны не трогать.** Правка стандарта — задача репозитория `templates/nextjs-template`, не отдельного проекта.
|
||||||
|
- **Синтаксис переменных, правила регистра, минимальный boilerplate** — в [Шаблоны и генерация кода](/docs/usage/templates-generation). Здесь не дублируется.
|
||||||
|
|
||||||
|
## Общие правила
|
||||||
|
|
||||||
|
- VS Code-расширение `gromlab.vscode-templateFileGenerator` устанавливается разово на машину разработчика, а не через этот раздел ([Шаблоны и генерация кода](/docs/usage/templates-generation)).
|
||||||
|
- CLI `@gromlab/create` вызывается через `npx`, в `package.json` отдельно не добавляется.
|
||||||
|
- Менеджер пакетов (npm/pnpm/yarn/bun) не влияет: `tiged` и `@gromlab/create` запускаются через `npx`.
|
||||||
|
|
||||||
|
## Проверка установки
|
||||||
|
|
||||||
|
- В корне проекта есть папка `.templates/`.
|
||||||
|
- Внутри `.templates/` присутствуют стандартные шаблоны (или согласованный кастомный набор).
|
||||||
|
- В корне проекта нет каталогов `.git/` и `.github/` из репозитория-шаблона.
|
||||||
|
- Пробная генерация через `npx @gromlab/create ...` отрабатывает без ошибок.
|
||||||
|
|
||||||
|
## Дальше
|
||||||
|
|
||||||
|
- [Шаблоны и генерация кода](/docs/usage/templates-generation) — синтаксис, использование, создание новых шаблонов.
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Настройка VS Code
|
title: VS Code
|
||||||
scope: applied
|
description: Единые настройки редактора и расширений для команды.
|
||||||
keywords: [vscode, редактор, расширение, настройка, extension, .vscode]
|
|
||||||
when: "Настройка VS Code: расширения, settings.json, сниппеты"
|
|
||||||
---
|
---
|
||||||
# Настройка VS Code
|
|
||||||
|
|
||||||
Каждый проект содержит папку `.vscode/` с конфигурацией редактора. Это гарантирует, что все участники команды работают с одинаковыми настройками форматирования, линтинга и расширениями.
|
# VS Code
|
||||||
|
|
||||||
|
Единые настройки редактора и расширений для команды.
|
||||||
|
|
||||||
## Структура `.vscode/`
|
## Структура `.vscode/`
|
||||||
|
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Компоненты
|
title: Компоненты
|
||||||
scope: applied
|
description: Как устроен и пишется React-компонент в проекте.
|
||||||
keywords: [компонент, props, jsx, ui, clsx, cl, React, FC]
|
|
||||||
when: "Создание или редактирование React-компонентов: структура, пропсы, стили"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Компоненты
|
# Компоненты
|
||||||
|
|
||||||
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
|
Как устроен и пишется React-компонент в проекте.
|
||||||
|
|
||||||
Архитектурные слои и их назначение описаны в разделе [Архитектура](/basics/architecture).
|
|
||||||
|
|
||||||
|
|
||||||
## Правила организации
|
## Правила организации
|
||||||
|
|
||||||
@@ -45,7 +41,7 @@ container/
|
|||||||
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
|
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
|
||||||
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
|
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
|
||||||
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
|
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
|
||||||
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/basics/typing).
|
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/docs/basics/typing).
|
||||||
|
|
||||||
## Реализация
|
## Реализация
|
||||||
|
|
||||||
@@ -112,7 +108,3 @@ export const Container = (props: ContainerProps) => {
|
|||||||
```ts
|
```ts
|
||||||
export { Container } from './container'
|
export { Container } from './container'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дочерние компоненты
|
|
||||||
|
|
||||||
Если модулю нужны внутренние подкомпоненты — генерировать их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
|
|
||||||
51
docs/docs/usage/data/index.md
Normal file
51
docs/docs/usage/data/index.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
title: Источники данных
|
||||||
|
description: Какие источники данных используются в проекте и как с ними работать.
|
||||||
|
keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, введение, карта раздела]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Источники данных
|
||||||
|
|
||||||
|
Какие источники данных используются в проекте и как с ними работать.
|
||||||
|
|
||||||
|
## Принципы раздела
|
||||||
|
|
||||||
|
- **Клиент — в `infrastructure/`.** Каждый внешний сервис — отдельный модуль слоя `infrastructure/{service-name}/`.
|
||||||
|
- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
|
||||||
|
- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
|
||||||
|
- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые хуки модуля API (`useUserList`, `usePostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
|
||||||
|
|
||||||
|
## Карта раздела
|
||||||
|
|
||||||
|
### REST
|
||||||
|
|
||||||
|
Канал «запрос-ответ» по HTTP. Покрывает большинство API.
|
||||||
|
|
||||||
|
- **Клиенты** — как создаётся клиент REST API:
|
||||||
|
- [Автоматическая генерация](/docs/usage/data/rest/clients/auto) — для API с OpenAPI-спецификацией, через `@gromlab/api-codegen`.
|
||||||
|
- [Ручное создание](/docs/usage/data/rest/clients/manual) — для API без схемы, клиент пишется и поддерживается руками.
|
||||||
|
- **Получение данных** — как клиент используется в приложении:
|
||||||
|
- [Серверные компоненты](/docs/usage/data/rest/fetching/server) — прямой `await` метода клиента в Server Components.
|
||||||
|
- [Клиентские компоненты](/docs/usage/data/rest/fetching/client) — через готовые хуки модуля API; SWR с кешем, дедупликацией и ревалидацией скрыт внутри хука.
|
||||||
|
|
||||||
|
### Realtime
|
||||||
|
|
||||||
|
Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
|
||||||
|
|
||||||
|
- [Realtime](/docs/usage/data/realtime) — клиент realtime в `infrastructure/`, потребление через `useSWRSubscription` или прямые подписки.
|
||||||
|
|
||||||
|
## Что даёт раздел
|
||||||
|
|
||||||
|
После прочтения раздела понятно:
|
||||||
|
|
||||||
|
- Где живёт код работы с API и почему именно там.
|
||||||
|
- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
|
||||||
|
- Как получать данные на сервере и на клиенте, чтобы не ломать кеш и не плодить лишние запросы.
|
||||||
|
- Как подключать realtime-источники в общую модель работы с данными.
|
||||||
|
- Какие правила обязательны и какие отклонения допустимы.
|
||||||
|
|
||||||
|
## Что не входит в раздел
|
||||||
|
|
||||||
|
- **Глобальное состояние UI** — Stores, формы, фичефлаги. Это [Stores](/docs/usage/stores).
|
||||||
|
- **Доменная логика** — как данные превращаются в сценарии бизнеса. Это слой `business/` в [Архитектуре](/docs/basics/architecture/).
|
||||||
|
- **Хуки общего назначения** — переиспользуемые хуки UI, не привязанные к конкретному API. Это [Хуки](/docs/usage/hooks).
|
||||||
79
docs/docs/usage/data/realtime.md
Normal file
79
docs/docs/usage/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/`.
|
||||||
|
|
||||||
|
Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.
|
||||||
279
docs/docs/usage/data/rest/clients/auto.md
Normal file
279
docs/docs/usage/data/rest/clients/auto.md
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
---
|
||||||
|
title: Автоматическая генерация
|
||||||
|
description: Генерация REST-клиента из OpenAPI-спецификации.
|
||||||
|
keywords: [api, rest, openapi, codegen, генерация, клиент, api-codegen, gromlab, infrastructure, swagger-typescript-api]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Автоматическая генерация
|
||||||
|
|
||||||
|
Генерация REST-клиента из OpenAPI-спецификации.
|
||||||
|
|
||||||
|
В примерах ниже используется условный API `pet-project-api` (kebab-case в путях) / `petProjectApi` (camelCase в коде). В реальном проекте имена выбираются по конкретному API.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D @gromlab/api-codegen
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт генерации в `package.json` — по одному на каждый API:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"codegen:pet-project-api": "api-codegen --config src/infrastructure/pet-project-api/config/pet-project-api.config.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Конфиг и опции — в репозитории [@gromlab/api-codegen](https://gromlab.ru/gromov/api-codegen).
|
||||||
|
|
||||||
|
## Структура модуля
|
||||||
|
|
||||||
|
Клиент кладётся в слой `infrastructure/` отдельным модулем по имени API (kebab-case):
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/
|
||||||
|
└── pet-project-api/
|
||||||
|
├── generated/ # сегмент сгенерированного кода
|
||||||
|
│ └── pet-project-api.generated.ts # сгенерировано — не править
|
||||||
|
├── types/ # расширения сгенерированных типов
|
||||||
|
│ ├── user.ts # declare module + Extended-тип
|
||||||
|
│ └── index.ts # реэкспорт расширений
|
||||||
|
├── hooks/ # SWR-хуки для клиентских компонентов
|
||||||
|
│ ├── use-user-list.hook.ts
|
||||||
|
│ ├── use-user-detail.hook.ts
|
||||||
|
│ └── index.ts # реэкспорт хуков
|
||||||
|
├── config/ # конфиги модуля
|
||||||
|
│ └── pet-project-api.config.ts # конфиг генерации клиента
|
||||||
|
├── client.ts # настройка HttpClient, инстанс Api
|
||||||
|
└── index.ts # публичный API модуля
|
||||||
|
```
|
||||||
|
|
||||||
|
| Файл | Роль | Кто правит |
|
||||||
|
|------|------|-----------|
|
||||||
|
| `generated/{service-name}.generated.ts` | Сгенерированный код: типы, `class Api`, `class HttpClient` | codegen, не править |
|
||||||
|
| `types/{сущность}.ts` | `declare module` + `Extended`-типы по сущности | разработчик |
|
||||||
|
| `types/index.ts` | Реэкспорт публичных расширений | разработчик |
|
||||||
|
| `hooks/use-{action}.hook.ts` | SWR-хук поверх метода клиента | разработчик |
|
||||||
|
| `hooks/index.ts` | Реэкспорт хуков | разработчик |
|
||||||
|
| `config/{service-name}.config.ts` | Параметры генерации для конкретного API | разработчик |
|
||||||
|
| `client.ts` | `baseUrl` из env, конфиг `HttpClient`, инстанс `new Api(...)` | разработчик |
|
||||||
|
| `index.ts` | Публичный API: инстанс сервиса, расширенные типы, хуки | разработчик |
|
||||||
|
|
||||||
|
`client.ts` и `index.ts` — единственные корневые файлы модуля. Все остальные файлы живут в сегментах (`generated/`, `types/`, `hooks/`, `config/`).
|
||||||
|
|
||||||
|
Имя сгенерированного файла — `{service-name}.generated.ts` (имя сервиса в kebab-case + суффикс `.generated.ts`). Суффикс сигнализирует «не править руками».
|
||||||
|
|
||||||
|
## `client.ts`
|
||||||
|
|
||||||
|
Тонкий ручной слой поверх сгенерированного кода. Делает три вещи: читает и нормализует `baseUrl`, конфигурирует `HttpClient`, создаёт **именованный инстанс** сервиса.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/client.ts
|
||||||
|
import { Api, HttpClient } from './generated/pet-project-api.generated'
|
||||||
|
|
||||||
|
const resolvedBaseUrl = process.env.NEXT_PUBLIC_API_URL
|
||||||
|
.replace(/\/+$/, '') // убираем хвостовой слэш
|
||||||
|
.replace(/\/v1$/, '') // версия уже в путях методов — режем дубль
|
||||||
|
|
||||||
|
const httpClient = new HttpClient({
|
||||||
|
baseApiParams: {
|
||||||
|
secure: false,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
// кастомные заголовки API — если требуются
|
||||||
|
// 'X-App-Key': '...',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
httpClient.baseUrl = resolvedBaseUrl
|
||||||
|
|
||||||
|
export const petProjectApi = new Api(httpClient)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Имя инстанса = имя сервиса
|
||||||
|
|
||||||
|
Инстанс называется по имени API в camelCase, не унифицированно `api`/`client`. Это даёт **процедурное обращение** и однозначность при работе с несколькими сервисами:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { petProjectApi } from 'infrastructure/pet-project-api'
|
||||||
|
|
||||||
|
const user = await petProjectApi.user.getUser(id)
|
||||||
|
```
|
||||||
|
|
||||||
|
При нескольких API — каждый со своим именем:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { petProjectApi } from 'infrastructure/pet-project-api'
|
||||||
|
import { paymentsApi } from 'infrastructure/payments-api'
|
||||||
|
|
||||||
|
const user = await petProjectApi.user.list()
|
||||||
|
const invoice = await paymentsApi.invoices.list()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Нормализация `baseUrl`
|
||||||
|
|
||||||
|
`@gromlab/api-codegen` может включать версию (`/v1`) в `baseUrl` сгенерированного кода, а пути методов уже содержат её — отсюда дубль. Стандартный приём:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
.replace(/\/+$/, '') // хвостовой слэш
|
||||||
|
.replace(/\/v1$/, '') // версия (если фигурирует в путях)
|
||||||
|
```
|
||||||
|
|
||||||
|
Подгоняется под конкретный API: если версия в путях не повторяется — второй `replace` не нужен.
|
||||||
|
|
||||||
|
## Расширения типов
|
||||||
|
|
||||||
|
Автогенерация не покрывает все реальные поля API: иногда тип `object`, иногда поле просто отсутствует. Расширения живут в `types/`, по файлу на сущность.
|
||||||
|
|
||||||
|
Две техники:
|
||||||
|
|
||||||
|
### `declare module` — добавление полей
|
||||||
|
|
||||||
|
Дополняет существующий интерфейс из `generated.ts`. Сама сгенерированная декларация не трогается.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/types/user.ts
|
||||||
|
import type { User } from '../generated/pet-project-api.generated'
|
||||||
|
|
||||||
|
declare module '../generated/pet-project-api.generated' {
|
||||||
|
interface User {
|
||||||
|
avatar?: {
|
||||||
|
file?: string
|
||||||
|
title?: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Extended` через `Omit & {...}` — переопределение полей
|
||||||
|
|
||||||
|
Когда автогенерация даёт `object` или общий тип, а реально структура известна — создаётся отдельный тип `UserExtended` (по имени сущности + суффикс `Extended`).
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/types/user.ts
|
||||||
|
export type UserExtended = Omit<User, 'roles' | 'tags' | 'fields'> & {
|
||||||
|
roles?: 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/pet-project-api/types/index.ts
|
||||||
|
export type { UserExtended } from './user'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Правила
|
||||||
|
|
||||||
|
- Расширения — **только в `types/`**, не в `client.ts` и не в сгенерированном файле.
|
||||||
|
- Один файл на сущность (имя файла — kebab-case по сущности: `user.ts`, `order.ts`, `invoice.ts`).
|
||||||
|
- При регенерации `generated/{service-name}.generated.ts` файлы в `types/` не затрагиваются.
|
||||||
|
- Если сломался `Extended`-тип после regen — синхронизировать руками.
|
||||||
|
|
||||||
|
## Хуки для клиентских компонентов
|
||||||
|
|
||||||
|
В клиентских компонентах вызовы клиента не делаются напрямую — компонент получает готовый хук, который инкапсулирует SWR + метод клиента. Хуки живут в сегменте `hooks/`, по файлу на операцию.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/hooks/use-user-list.hook.ts
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import type { SWRConfiguration } from 'swr'
|
||||||
|
import { petProjectApi } from '../client'
|
||||||
|
import type { User } from '../generated/pet-project-api.generated'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение списка пользователей.
|
||||||
|
*/
|
||||||
|
export const useUserList = (
|
||||||
|
query?: { limit?: number; offset?: number },
|
||||||
|
config?: SWRConfiguration,
|
||||||
|
) => {
|
||||||
|
return useSWR<User[]>(
|
||||||
|
['pet-project-api', 'user', 'list', query],
|
||||||
|
() => petProjectApi.user.list(query ?? {}),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/hooks/use-user-detail.hook.ts
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import type { SWRConfiguration } from 'swr'
|
||||||
|
import { petProjectApi } from '../client'
|
||||||
|
import type { UserExtended } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение пользователя по идентификатору.
|
||||||
|
*/
|
||||||
|
export const useUserDetail = (
|
||||||
|
id: string | null,
|
||||||
|
config?: SWRConfiguration,
|
||||||
|
) => {
|
||||||
|
const key = id ? ['pet-project-api', 'user', 'detail', id] : null
|
||||||
|
const fetcher = () => petProjectApi.user.getUser(id!) as Promise<UserExtended>
|
||||||
|
|
||||||
|
return useSWR<UserExtended>(key, fetcher, config)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/hooks/index.ts
|
||||||
|
export { useUserList } from './use-user-list.hook'
|
||||||
|
export { useUserDetail } from './use-user-detail.hook'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Правила хуков
|
||||||
|
|
||||||
|
- **Один файл — один хук**, имя файла `use-{action}.hook.ts` ([Именование](/docs/basics/naming)).
|
||||||
|
- **Тонкая обёртка над SWR.** Внутри — построение ключа, fetcher через метод клиента, возврат `useSWR(...)`. Никакой бизнес-логики.
|
||||||
|
- **Ключ начинается с имени сервиса** (`['pet-project-api', ...]`) — изолирует кеш между разными API.
|
||||||
|
- **Условный ключ для опциональных параметров:** `id ? [...key, id] : null`. `null` приостанавливает запрос, пока параметры не готовы.
|
||||||
|
- **Параметр `config?: SWRConfiguration`** — даёт потребителю переопределить ревалидацию, `fallbackData`, `suspense` и т.п. без обёрток.
|
||||||
|
|
||||||
|
## Публичный API модуля
|
||||||
|
|
||||||
|
Из `index.ts` экспортируются инстанс, расширенные типы и хуки. Сырые типы из `generated/` экспортируются по необходимости — точечно.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/index.ts
|
||||||
|
export { petProjectApi } from './client'
|
||||||
|
export type { UserExtended } from './types'
|
||||||
|
export * from './hooks'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Регенерация
|
||||||
|
|
||||||
|
При изменении OpenAPI-схемы:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run codegen:pet-project-api
|
||||||
|
```
|
||||||
|
|
||||||
|
Что меняется:
|
||||||
|
|
||||||
|
- `generated/{service-name}.generated.ts` — перезаписывается полностью, изменения коммитятся.
|
||||||
|
- `client.ts`, `types/`, `config/`, `index.ts` — **не трогаются** автоматически.
|
||||||
|
|
||||||
|
Поломка контракта (изменение типов в схеме) ловится TypeScript при сборке проекта. Если ломаются `Extended`-типы — синхронизировать вручную в соответствующих файлах `types/`.
|
||||||
|
|
||||||
|
## Сгенерированный файл коммитится
|
||||||
|
|
||||||
|
Файл `generated/{service-name}.generated.ts` **не добавляется в `.gitignore`** — попадает в репозиторий вместе с остальным кодом.
|
||||||
|
|
||||||
|
Причины:
|
||||||
|
|
||||||
|
- **Детерминированная сборка.** `npm run build` не зависит от доступности OpenAPI-схемы (обычно она на удалённом сервере). Сервис лёг — прод собирается.
|
||||||
|
- **Видимость изменений в PR.** Diff показывает, что именно поменялось в контракте API между версиями.
|
||||||
|
- **Простой онбординг.** После `git clone` IDE сразу видит типы, без предварительной генерации.
|
||||||
|
- **Фиксация версии контракта.** Пересборка старого коммита даёт ровно тот клиент, что был тогда.
|
||||||
|
|
||||||
|
Регенерация — **ручная команда** при обновлении схемы, не хук `predev`/`prebuild`. Запускается осознанно.
|
||||||
|
|
||||||
|
Исключение возможно, только если OpenAPI-схема лежит **в этом же репозитории** и генерация быстрая, без сети — тогда допустимо добавить сегмент `generated/` в `.gitignore` и хук `prebuild`, по аналогии со спрайтами. На практике встречается редко.
|
||||||
365
docs/docs/usage/data/rest/clients/manual.md
Normal file
365
docs/docs/usage/data/rest/clients/manual.md
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
---
|
||||||
|
title: Ручное создание
|
||||||
|
description: "Создание REST-клиента вручную, когда нет OpenAPI-спецификации."
|
||||||
|
keywords: [api, rest, клиент, ручной, fetch, infrastructure, api-клиент]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ручное создание
|
||||||
|
|
||||||
|
Создание REST-клиента вручную, когда нет OpenAPI-спецификации.
|
||||||
|
|
||||||
|
В примерах ниже используется условный API `pet-project-api` / `petProjectApi`. В реальном проекте имена выбираются по конкретному API.
|
||||||
|
|
||||||
|
## Структура модуля
|
||||||
|
|
||||||
|
Клиент живёт в слое `infrastructure/` отдельным модулем по имени API (kebab-case):
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/infrastructure/
|
||||||
|
└── pet-project-api/
|
||||||
|
├── methods/ # методы по сущностям API
|
||||||
|
│ ├── pages.ts
|
||||||
|
│ ├── posts.ts
|
||||||
|
│ └── forms.ts
|
||||||
|
├── hooks/ # SWR-хуки для клиентских компонентов
|
||||||
|
│ ├── use-post-detail.hook.ts
|
||||||
|
│ ├── use-post-filter.hook.ts
|
||||||
|
│ └── index.ts
|
||||||
|
├── types/ # типы клиента и доменные типы
|
||||||
|
│ ├── client.ts # типы клиента: RequestOptions, ParamValue
|
||||||
|
│ ├── post.ts # доменные типы сущности post
|
||||||
|
│ ├── form.ts # доменные типы сущности form
|
||||||
|
│ └── index.ts # реэкспорт публичных типов
|
||||||
|
├── errors/ # доменные ошибки API
|
||||||
|
│ └── pet-project-api.error.ts
|
||||||
|
├── client.ts # класс клиента: baseUrl, headers, get/post
|
||||||
|
└── index.ts # публичный API модуля
|
||||||
|
```
|
||||||
|
|
||||||
|
| Файл | Роль |
|
||||||
|
|------|------|
|
||||||
|
| `client.ts` | Класс `PetProjectApiClient`: `baseUrl`, общие заголовки, `buildUrl`, базовые `get`/`post` |
|
||||||
|
| `methods/{entity}.ts` | Методы по сущности, экспортируются фабрикой `{entity}Methods(client)` |
|
||||||
|
| `hooks/use-{action}.hook.ts` | SWR-хук поверх метода клиента |
|
||||||
|
| `hooks/index.ts` | Реэкспорт хуков |
|
||||||
|
| `types/client.ts` | Типы инфраструктуры клиента: `RequestOptions`, `PostOptions`, `ParamValue` |
|
||||||
|
| `types/{entity}.ts` | Доменные типы: запросы, ответы, фильтры по сущности |
|
||||||
|
| `types/index.ts` | Реэкспорт публичных типов |
|
||||||
|
| `errors/{service-name}.error.ts` | Доменный класс ошибок API |
|
||||||
|
| `index.ts` | Публичный API: инстанс клиента, хуки, доменные ошибки, типы |
|
||||||
|
|
||||||
|
`methods/`, `hooks/`, `types/`, `errors/` — сегменты модуля по канону SLM. `client.ts` и `index.ts` — единственные корневые файлы.
|
||||||
|
|
||||||
|
## Типы клиента
|
||||||
|
|
||||||
|
Типы, описывающие саму инфраструктуру запросов (опции, параметры) — выносятся в `types/client.ts`. Это держит `client.ts` коротким и не смешивает декларации типов с реализацией класса.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/types/client.ts
|
||||||
|
export type ParamValue = string | number | (string | number)[]
|
||||||
|
|
||||||
|
export type RequestOptions = {
|
||||||
|
params?: Record<string, ParamValue>
|
||||||
|
headers?: Record<string, string>
|
||||||
|
revalidate?: number | false
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PostOptions = RequestOptions & {
|
||||||
|
type?: 'json' | 'formdata'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Базовый клиент
|
||||||
|
|
||||||
|
Класс с конфигурацией (`baseUrl`, общие заголовки) и базовыми методами `get` / `post`. Конкретные методы API размещаются в сегменте `methods/`, а не на самом классе — это держит `client.ts` коротким и не плодит «бога-класс».
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/client.ts
|
||||||
|
import { PetProjectApiError } from './errors/pet-project-api.error'
|
||||||
|
import type { ParamValue, RequestOptions, PostOptions } from './types/client'
|
||||||
|
|
||||||
|
export class PetProjectApiClient {
|
||||||
|
constructor(
|
||||||
|
private readonly baseUrl: string,
|
||||||
|
private readonly defaultHeaders: Record<string, string> = {},
|
||||||
|
) {
|
||||||
|
this.defaultHeaders = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
...defaultHeaders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildUrl(path: string, params?: Record<string, ParamValue>): string {
|
||||||
|
const base = this.baseUrl.replace(/\/+$/, '')
|
||||||
|
const tail = path.replace(/^\/+/, '')
|
||||||
|
const url = `${base}/${tail}`
|
||||||
|
|
||||||
|
if (!params) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = new URLSearchParams()
|
||||||
|
for (const [key, value] of Object.entries(params)) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach((v) => search.append(key, String(v)))
|
||||||
|
} else {
|
||||||
|
search.set(key, String(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${url}?${search}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async get<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
||||||
|
const { params, headers, revalidate } = options
|
||||||
|
const response = await fetch(this.buildUrl(path, params), {
|
||||||
|
headers: { ...this.defaultHeaders, ...headers },
|
||||||
|
...(revalidate !== undefined && { next: { revalidate } }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw await PetProjectApiError.fromResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
async post<T>(path: string, body: unknown, options: PostOptions = {}): Promise<T> {
|
||||||
|
const { params, headers, revalidate, type = 'json' } = options
|
||||||
|
const isJson = type === 'json'
|
||||||
|
|
||||||
|
const response = await fetch(this.buildUrl(path, params), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
...this.defaultHeaders,
|
||||||
|
...(isJson && { 'Content-Type': 'application/json' }),
|
||||||
|
...headers,
|
||||||
|
},
|
||||||
|
body: isJson ? JSON.stringify(body) : (body as BodyInit),
|
||||||
|
...(revalidate !== undefined && { next: { revalidate } }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw await PetProjectApiError.fromResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ключевые требования к клиенту
|
||||||
|
|
||||||
|
- **Класс с приватным состоянием** (`baseUrl`, `defaultHeaders`) — конфигурация инкапсулирована.
|
||||||
|
- **Типы клиента — в `types/client.ts`**, не в `client.ts`. Реализация и контракты разделены.
|
||||||
|
- **Базовые методы дженерик `<T>` без дефолта.** Вызов без типа невозможен — потребитель обязан указать форму ответа.
|
||||||
|
- **Доменная ошибка вместо `null`.** При не-`ok` бросается `PetProjectApiError`. Возврат `null` глотает причины (404 vs 500 vs 401) — не использовать.
|
||||||
|
- **Дефолт POST — `json`.** `formdata` указывается явно, на конкретных методах (загрузка файлов, отправка форм).
|
||||||
|
- **Нормализация слэшей** в `buildUrl` — `baseUrl` без хвостового `/`, `path` без ведущего `/`.
|
||||||
|
- **`async/await`**, не `.then()` — линейное чтение, простая обработка ошибок.
|
||||||
|
- **Поддержка `next.revalidate`** — клиент знает о Next.js App Router и пробрасывает кеш-флаги.
|
||||||
|
|
||||||
|
## Доменная ошибка
|
||||||
|
|
||||||
|
Сетевая ошибка превращается в класс ошибки модуля. Наружу не выходит сырой `Response`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/errors/pet-project-api.error.ts
|
||||||
|
export class PetProjectApiError extends Error {
|
||||||
|
constructor(
|
||||||
|
public readonly status: number,
|
||||||
|
public readonly body: string,
|
||||||
|
) {
|
||||||
|
super(`PetProjectApi ${status}: ${body.slice(0, 200)}`)
|
||||||
|
this.name = 'PetProjectApiError'
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fromResponse(response: Response): Promise<PetProjectApiError> {
|
||||||
|
const body = await response.text().catch(() => '')
|
||||||
|
return new PetProjectApiError(response.status, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Дополнительные подклассы по необходимости: `PetProjectApiValidationError` (400), `PetProjectApiAuthError` (401/403), `PetProjectApiNotFoundError` (404). Вводятся когда у потребителя есть **разная реакция** на разные коды; иначе хватает базового класса.
|
||||||
|
|
||||||
|
## Доменные типы
|
||||||
|
|
||||||
|
Типы запросов, ответов и фильтров — по файлу на сущность. Типы должны лежать рядом по смыслу: всё, что относится к `posts`, — в `types/post.ts`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/types/post.ts
|
||||||
|
export type Post = {
|
||||||
|
id: string
|
||||||
|
slug: string
|
||||||
|
title: string
|
||||||
|
content: string
|
||||||
|
publishedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PostFilter = {
|
||||||
|
limit?: number
|
||||||
|
categories?: number[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/types/index.ts
|
||||||
|
export type * from './post'
|
||||||
|
export type * from './form'
|
||||||
|
// типы клиента — внутренние, наружу не реэкспортируются
|
||||||
|
```
|
||||||
|
|
||||||
|
Типы клиента (`RequestOptions`, `PostOptions`, `ParamValue`) **не реэкспортируются** через `types/index.ts` — они нужны только внутри модуля.
|
||||||
|
|
||||||
|
## Методы
|
||||||
|
|
||||||
|
Методы группируются по сущностям в сегменте `methods/`, экспортируются фабрикой, принимающей клиент. Это даёт **процедурное обращение** в стиле автогенерированного клиента (`petProjectApi.posts.get(slug)`), а не плоский список (`petProjectApi.getPost(slug)`).
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/methods/posts.ts
|
||||||
|
import type { PetProjectApiClient } from '../client'
|
||||||
|
import type { Post, PostFilter } from '../types/post'
|
||||||
|
|
||||||
|
export function postsMethods(client: PetProjectApiClient) {
|
||||||
|
return {
|
||||||
|
/** GET /posts/{slug} */
|
||||||
|
get: (slug: string, options?: { revalidate?: number | false }) =>
|
||||||
|
client.get<Post>(`posts/${slug}`, options),
|
||||||
|
|
||||||
|
/** POST /posts/filter */
|
||||||
|
filter: (body: PostFilter) =>
|
||||||
|
client.post<Post[]>('posts/filter', body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/methods/forms.ts
|
||||||
|
import type { PetProjectApiClient } from '../client'
|
||||||
|
import type { Form, FormSubmissionResult } from '../types/form'
|
||||||
|
|
||||||
|
export function formsMethods(client: PetProjectApiClient) {
|
||||||
|
return {
|
||||||
|
/** GET /forms/{id} */
|
||||||
|
get: (id: string) => client.get<Form>(`forms/${id}`),
|
||||||
|
|
||||||
|
/** POST /forms/{id} — multipart/form-data */
|
||||||
|
submit: (id: string, data: FormData) =>
|
||||||
|
client.post<FormSubmissionResult>(`forms/${id}`, data, { type: 'formdata' }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Правила методов
|
||||||
|
|
||||||
|
- **Группировка по сущности** (`pages`, `posts`, `forms`), не плоский список.
|
||||||
|
- **Имя метода — глагол действия**: `get`, `list`, `filter`, `create`, `update`, `delete`, `submit`. Не `getPost`/`getPosts` — сущность уже в имени группы.
|
||||||
|
- **Типы запросов и ответов — в `types/{entity}.ts`**, импортируются в файл методов. В `methods/` лежит только композиция вызовов клиента, без объявлений типов.
|
||||||
|
- **Фабрика принимает клиент** — это даёт тестируемость (моковый клиент в юнит-тестах) и единый источник конфигурации.
|
||||||
|
- **Никаких знаний об UI.** Клиент не знает про React, SWR, тосты — только данные и ошибки.
|
||||||
|
|
||||||
|
## Сборка инстанса
|
||||||
|
|
||||||
|
Группы методов соединяются в один объект на уровне `index.ts`. Это даёт процедурный доступ `petProjectApi.posts.get(...)`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/index.ts
|
||||||
|
import { PetProjectApiClient } from './client'
|
||||||
|
import { pagesMethods } from './methods/pages'
|
||||||
|
import { postsMethods } from './methods/posts'
|
||||||
|
import { formsMethods } from './methods/forms'
|
||||||
|
|
||||||
|
const client = new PetProjectApiClient(process.env.NEXT_PUBLIC_API_URL, {
|
||||||
|
'X-App-Key': process.env.NEXT_PUBLIC_APP_KEY,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const petProjectApi = {
|
||||||
|
pages: pagesMethods(client),
|
||||||
|
posts: postsMethods(client),
|
||||||
|
forms: formsMethods(client),
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PetProjectApiError } from './errors/pet-project-api.error'
|
||||||
|
export type { Post, PostFilter, Page, Form } from './types'
|
||||||
|
export * from './hooks'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Хуки для клиентских компонентов
|
||||||
|
|
||||||
|
В клиентских компонентах вызовы клиента не делаются напрямую — компонент получает готовый хук, который инкапсулирует SWR + метод клиента. Хуки живут в сегменте `hooks/`, по файлу на операцию.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/hooks/use-post-detail.hook.ts
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import type { SWRConfiguration } from 'swr'
|
||||||
|
import { petProjectApi } from '..'
|
||||||
|
import type { Post } from '../types/post'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение поста по slug.
|
||||||
|
*/
|
||||||
|
export const usePostDetail = (
|
||||||
|
slug: string | null,
|
||||||
|
config?: SWRConfiguration,
|
||||||
|
) => {
|
||||||
|
const key = slug ? ['pet-project-api', 'post', 'detail', slug] : null
|
||||||
|
const fetcher = () => petProjectApi.posts.get(slug!)
|
||||||
|
|
||||||
|
return useSWR<Post>(key, fetcher, config)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/hooks/use-post-filter.hook.ts
|
||||||
|
import useSWR from 'swr'
|
||||||
|
import type { SWRConfiguration } from 'swr'
|
||||||
|
import { petProjectApi } from '..'
|
||||||
|
import type { Post, PostFilter } from '../types/post'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение списка постов по фильтру.
|
||||||
|
*/
|
||||||
|
export const usePostFilter = (
|
||||||
|
filter: PostFilter,
|
||||||
|
config?: SWRConfiguration,
|
||||||
|
) => {
|
||||||
|
return useSWR<Post[]>(
|
||||||
|
['pet-project-api', 'post', 'filter', filter],
|
||||||
|
() => petProjectApi.posts.filter(filter),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/hooks/index.ts
|
||||||
|
export { usePostDetail } from './use-post-detail.hook'
|
||||||
|
export { usePostFilter } from './use-post-filter.hook'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Правила хуков
|
||||||
|
|
||||||
|
- **Один файл — один хук**, имя файла `use-{action}.hook.ts` ([Именование](/docs/basics/naming)).
|
||||||
|
- **Тонкая обёртка над SWR.** Внутри — построение ключа, fetcher через метод клиента, возврат `useSWR(...)`. Никакой бизнес-логики.
|
||||||
|
- **Ключ начинается с имени сервиса** (`['pet-project-api', ...]`) — изолирует кеш между разными API.
|
||||||
|
- **Условный ключ для опциональных параметров:** `id ? [...key, id] : null`. `null` приостанавливает запрос, пока параметры не готовы.
|
||||||
|
- **Параметр `config?: SWRConfiguration`** — даёт потребителю переопределить ревалидацию, `fallbackData`, `suspense` и т.п. без обёрток.
|
||||||
|
|
||||||
|
## Запрет прямого `fetch`
|
||||||
|
|
||||||
|
В коде приложения (слои выше `infrastructure`) прямые вызовы `fetch` к API запрещены. Все запросы идут через клиент.
|
||||||
|
|
||||||
|
Исключение допускается точечно — например, разовая отладочная проверка эндпоинта в скрипте — и требует обоснования в коде (комментарий с причиной).
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { petProjectApi } from 'infrastructure/pet-project-api'
|
||||||
|
|
||||||
|
const post = await petProjectApi.posts.get('my-post')
|
||||||
|
const list = await petProjectApi.posts.filter({ limit: 10, categories: [1, 2] })
|
||||||
|
const form = await petProjectApi.forms.get('contact')
|
||||||
|
```
|
||||||
|
|
||||||
|
Стиль вызовов совпадает с автогенерированным клиентом — потребитель не различает, ручной API или сгенерирован.
|
||||||
164
docs/docs/usage/data/rest/fetching/client.md
Normal file
164
docs/docs/usage/data/rest/fetching/client.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
---
|
||||||
|
title: Клиентские компоненты
|
||||||
|
description: Получение REST-данных в клиентских компонентах.
|
||||||
|
keywords: [swr, клиентские компоненты, useSWR, хук, мутация, useSWRMutation, кеш, ревалидация]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Клиентские компоненты
|
||||||
|
|
||||||
|
Получение REST-данных в клиентских компонентах.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- **Только готовые хуки.** В компоненте — `usePostDetail(slug)`, не `useSWR(['post', slug], () => api.posts.get(slug))`.
|
||||||
|
- **`useSWR` пишется один раз — в `hooks/`** модуля API. В клиентских компонентах никогда напрямую.
|
||||||
|
- **Прямой вызов методов клиента в `useEffect` запрещён.** Это потеря кеша, повторные запросы и гонки.
|
||||||
|
- **Мутации — через `useSWRMutation`**, тоже инкапсулированный в хуке. В компоненте вызывается готовый `trigger`.
|
||||||
|
|
||||||
|
## Чтение
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { usePostDetail } from 'infrastructure/pet-project-api'
|
||||||
|
|
||||||
|
export function PostView({ slug }: { slug: string }) {
|
||||||
|
const { data: post, error, isLoading } = usePostDetail(slug)
|
||||||
|
|
||||||
|
if (isLoading) return <Spinner />
|
||||||
|
if (error) return <ErrorView error={error} />
|
||||||
|
|
||||||
|
return <article>{post?.title}</article>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
В компоненте нет `useSWR`, нет ключей, нет fetcher — только готовый хук.
|
||||||
|
|
||||||
|
## Параметризованный запрос
|
||||||
|
|
||||||
|
Хук сам обрабатывает «нет параметра — нет запроса». В компоненте можно безопасно передавать `null`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useUserDetail } from 'infrastructure/pet-project-api'
|
||||||
|
|
||||||
|
export function UserProfile({ userId }: { userId: string | null }) {
|
||||||
|
const { data: user } = useUserDetail(userId)
|
||||||
|
|
||||||
|
if (!userId) return <EmptyState />
|
||||||
|
return <UserCard user={user} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Внутри `useUserDetail` ключ становится `null`, когда `userId` не задан, и SWR не делает запрос — это поведение зашито в хук, потребитель об этом не думает.
|
||||||
|
|
||||||
|
## Мутации
|
||||||
|
|
||||||
|
Мутации тоже оборачиваются в хук модуля API:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/infrastructure/pet-project-api/hooks/use-create-user.hook.ts
|
||||||
|
import useSWRMutation from 'swr/mutation'
|
||||||
|
import { mutate } from 'swr'
|
||||||
|
import { petProjectApi } from '..'
|
||||||
|
import type { User, UserCreateInput } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создание пользователя с инвалидацией списка.
|
||||||
|
*/
|
||||||
|
export const useCreateUser = () => {
|
||||||
|
return useSWRMutation<User, Error, [string, string, string], UserCreateInput>(
|
||||||
|
['pet-project-api', 'user', 'create'],
|
||||||
|
(_key, { arg }) => petProjectApi.user.create(arg),
|
||||||
|
{
|
||||||
|
onSuccess: () => mutate(['pet-project-api', 'user', 'list']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useCreateUser } from 'infrastructure/pet-project-api'
|
||||||
|
|
||||||
|
export function CreateUserForm() {
|
||||||
|
const { trigger, isMutating } = useCreateUser()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
onSubmit={(input) => trigger(input)}
|
||||||
|
disabled={isMutating}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
В компоненте — снова только хук. Логика инвалидации кеша зашита внутрь, потребитель её не дублирует.
|
||||||
|
|
||||||
|
## Передача config из компонента
|
||||||
|
|
||||||
|
Каждый хук принимает второй (или третий) параметр `config?: SWRConfiguration` — он пробрасывается в `useSWR`. Это даёт потребителю точечно настроить ревалидацию, `fallbackData`, `suspense` и т.п.:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { usePostDetail } from 'infrastructure/pet-project-api'
|
||||||
|
|
||||||
|
export function PostView({ slug, initialPost }: Props) {
|
||||||
|
const { data: post } = usePostDetail(slug, { fallbackData: initialPost })
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Начальное состояние с сервера
|
||||||
|
|
||||||
|
Если данные пришли из серверного компонента (см. [Серверные компоненты](/docs/usage/data/rest/fetching/server)) — передаются в `fallbackData` через `config` хука:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// page.tsx (server)
|
||||||
|
import { petProjectApi } from 'infrastructure/pet-project-api'
|
||||||
|
|
||||||
|
export default async function Page({ params }: { params: { slug: string } }) {
|
||||||
|
const initialPost = await petProjectApi.posts.get(params.slug)
|
||||||
|
return <PostView slug={params.slug} initialPost={initialPost} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// post-view.tsx ('use client')
|
||||||
|
import { usePostDetail } from 'infrastructure/pet-project-api'
|
||||||
|
|
||||||
|
export function PostView({ slug, initialPost }: Props) {
|
||||||
|
const { data: post } = usePostDetail(slug, { fallbackData: initialPost })
|
||||||
|
return <article>{post?.title}</article>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Для массового заполнения кеша на странице с несколькими хуками — используется `<SWRConfig fallback>` обёртка. Серверный компонент собирает данные и передаёт сериализованную карту ключей в провайдер; все вложенные хуки сразу видят кеш.
|
||||||
|
|
||||||
|
## Запрет прямых вызовов
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Плохо — прямой fetch в обход клиента
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/users').then(...)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Плохо — клиент без SWR: нет кеша, нет дедупликации
|
||||||
|
useEffect(() => {
|
||||||
|
petProjectApi.user.list().then(setUsers)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Плохо — useSWR в компоненте: SWR должен быть в хуке модуля
|
||||||
|
const { data } = useSWR(
|
||||||
|
['pet-project-api', 'user', 'list'],
|
||||||
|
() => petProjectApi.user.list(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Хорошо — готовый хук модуля
|
||||||
|
const { data } = useUserList()
|
||||||
|
```
|
||||||
|
|
||||||
|
Если для нужной операции хука ещё нет — он добавляется в `hooks/` модуля API, не в компонент.
|
||||||
66
docs/docs/usage/data/rest/fetching/server.md
Normal file
66
docs/docs/usage/data/rest/fetching/server.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
title: Серверные компоненты
|
||||||
|
description: Получение REST-данных в серверных компонентах.
|
||||||
|
keywords: [server components, rsc, серверные компоненты, fetch, api, app router, прямой вызов]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Серверные компоненты
|
||||||
|
|
||||||
|
Получение REST-данных в серверных компонентах.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
- **Прямой `await` метода клиента.** Никаких хуков, обёрток состояний, `useEffect` — серверный компонент не имеет жизненного цикла React-клиента.
|
||||||
|
- **Ошибки бросаются.** Не оборачивать `try/catch` без необходимости — Next.js поднимет ближайший `error.tsx`.
|
||||||
|
- **Параллельные запросы — через `Promise.all`.** Последовательный `await` за `await` блокирует рендер.
|
||||||
|
|
||||||
|
## Шаблон
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/app/(routes)/users/page.tsx
|
||||||
|
import { petProjectApi } from 'infrastructure/pet-project-api'
|
||||||
|
|
||||||
|
export default async function UsersPage() {
|
||||||
|
const users = await petProjectApi.user.list()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{users.map((user) => (
|
||||||
|
<li key={user.id}>{user.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Параллельные запросы
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export default async function DashboardPage() {
|
||||||
|
const [users, orders] = await Promise.all([
|
||||||
|
petProjectApi.user.list(),
|
||||||
|
petProjectApi.order.list(),
|
||||||
|
])
|
||||||
|
|
||||||
|
return <Dashboard users={users} orders={orders} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Передача данных в клиентский компонент
|
||||||
|
|
||||||
|
Серверный компонент получает данные и передаёт их пропсами в клиентский. На клиенте данные становятся начальным состоянием — при необходимости перезапрашиваются через SWR (см. [Клиентские компоненты](/docs/usage/data/rest/fetching/client)).
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// page.tsx (server)
|
||||||
|
import { petProjectApi } from 'infrastructure/pet-project-api'
|
||||||
|
import { UsersList } from 'widgets/users-list'
|
||||||
|
|
||||||
|
export default async function UsersPage() {
|
||||||
|
const initialUsers = await petProjectApi.user.list()
|
||||||
|
return <UsersList initialUsers={initialUsers} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Запрет прямого `fetch`
|
||||||
|
|
||||||
|
Серверный компонент тоже использует только клиент из `infrastructure/`. Прямой `fetch` в `page.tsx` или в server-action запрещён теми же правилами, что и на клиенте.
|
||||||
0
docs/docs/usage/fonts.md
Normal file
0
docs/docs/usage/fonts.md
Normal file
0
docs/docs/usage/hooks.md
Normal file
0
docs/docs/usage/hooks.md
Normal file
0
docs/docs/usage/images-sprites.md
Normal file
0
docs/docs/usage/images-sprites.md
Normal file
0
docs/docs/usage/localization.md
Normal file
0
docs/docs/usage/localization.md
Normal file
@@ -1,16 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Файлы роутинга
|
title: Файлы роутинга
|
||||||
scope: applied
|
description: "Что должно лежать в файлах роутинга, а что — в экранах."
|
||||||
keywords: [page.tsx, layout.tsx, error.tsx, not-found.tsx, loading.tsx, App Router, metadata]
|
|
||||||
when: "Работа с файлами роутинга Next.js App Router: page, layout, error, not-found"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Файлы роутинга
|
# Файлы роутинга
|
||||||
|
|
||||||
Правила для специальных файлов App Router (`page.tsx`, `layout.tsx`, `error.tsx`, `not-found.tsx` и др.) — чем наш подход отличается от дефолтного.
|
Что должно лежать в файлах роутинга, а что — в экранах.
|
||||||
|
|
||||||
## Что нужно знать
|
|
||||||
|
|
||||||
Страница в проекте — это два файла: экран в `src/screens/` (вся логика, стили, зависимости) и `page.tsx` в `src/app/` (точка входа для роутинга Next.js). Экран генерируется из шаблона, `page.tsx` создаётся вручную.
|
|
||||||
|
|
||||||
## Организация
|
## Организация
|
||||||
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Структура проекта
|
title: Структура проекта
|
||||||
scope: applied
|
description: Из чего состоит проект и где что лежит.
|
||||||
keywords: [структура проекта, папки, src/app, src/shared, FSD, Next.js структура]
|
|
||||||
when: "Организация папок и файлов в Next.js проекте"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Структура проекта
|
# Структура проекта
|
||||||
|
|
||||||
Раздел описывает расположение файлов и папок в проекте Next.js (App Router).
|
Из чего состоит проект и где что лежит.
|
||||||
|
|
||||||
## Корень репозитория
|
## Корень репозитория
|
||||||
|
|
||||||
@@ -43,19 +42,20 @@ 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-сегменты).
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/app/
|
src/app/
|
||||||
@@ -75,8 +75,7 @@ src/app/
|
|||||||
├── screen/ # Шаблон экрана
|
├── screen/ # Шаблон экрана
|
||||||
├── layout/ # Шаблон layout
|
├── layout/ # Шаблон layout
|
||||||
├── widget/ # Шаблон виджета
|
├── widget/ # Шаблон виджета
|
||||||
├── feature/ # Шаблон фичи
|
├── module/ # Шаблон бизнес-модуля
|
||||||
├── entity/ # Шаблон сущности
|
|
||||||
└── store/ # Шаблон стора
|
└── store/ # Шаблон стора
|
||||||
```
|
```
|
||||||
|
|
||||||
0
docs/docs/usage/stores.md
Normal file
0
docs/docs/usage/stores.md
Normal file
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Стили
|
title: Стили
|
||||||
scope: applied
|
description: Как пишутся стили в проекте.
|
||||||
keywords: [css, postcss, модули, css modules, токены, медиа-запросы, вложенность, класс]
|
|
||||||
when: "Стилизация: CSS Modules, PostCSS, переменные, медиа-запросы"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Стили
|
# Стили
|
||||||
|
|
||||||
Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование.
|
Как пишутся стили в проекте.
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
@@ -269,17 +268,3 @@ when: "Стилизация: CSS Modules, PostCSS, переменные, мед
|
|||||||
|
|
||||||
- Желательно не писать комментарии в CSS.
|
- Желательно не писать комментарии в CSS.
|
||||||
- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.
|
- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.
|
||||||
|
|
||||||
## Приоритет стилизации
|
|
||||||
|
|
||||||
Основной UI-фреймворк проекта — **Mantine**. При стилизации компонентов придерживаться следующего приоритета:
|
|
||||||
|
|
||||||
1. **Mantine-компоненты и их пропсы** — в первую очередь использовать встроенные возможности Mantine (пропсы, `classNames`, `styles`).
|
|
||||||
2. **Глобальные CSS-токены** (`--color-*`, `--space-*`, `--radius-*`) — для значений, которые не покрываются Mantine.
|
|
||||||
3. **PostCSS Modules** — когда Mantine не покрывает задачу и нужна кастомная стилизация.
|
|
||||||
|
|
||||||
## Что запрещено
|
|
||||||
|
|
||||||
- **Инлайн-стили** — использование атрибута `style` в компонентах строго запрещено.
|
|
||||||
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
|
|
||||||
- **Глобальные стили** вне `app/styles/` запрещены.
|
|
||||||
56
docs/docs/usage/svg-sprites.md
Normal file
56
docs/docs/usage/svg-sprites.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);
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: Шаблоны и генерация кода
|
title: Шаблоны и генерация кода
|
||||||
scope: applied
|
description: Как устроены шаблоны кодогенерации и как ими пользоваться.
|
||||||
keywords: [шаблон, генерация, template, scaffold, plop, hygen, .templates]
|
|
||||||
when: "Генерация кода из шаблонов, создание новых шаблонов"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- @formatter:off -->
|
<!-- @formatter:off -->
|
||||||
::: v-pre
|
::: v-pre
|
||||||
|
|
||||||
# Шаблоны и генерация кода
|
# Шаблоны и генерация кода
|
||||||
|
|
||||||
Как работают шаблоны, как их создавать, синтаксис переменных и как генерировать код с помощью расширения VS Code и CLI.
|
Как устроены шаблоны кодогенерации и как ими пользоваться.
|
||||||
|
|
||||||
## Структура шаблонов
|
## Структура шаблонов
|
||||||
|
|
||||||
@@ -148,28 +147,10 @@ npx @gromlab/create <шаблон> <имя> <путь>
|
|||||||
| Команда | Что создаёт |
|
| Команда | Что создаёт |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `npx @gromlab/create component button src/shared/ui` | Компонент |
|
| `npx @gromlab/create component button src/shared/ui` | Компонент |
|
||||||
| `npx @gromlab/create feature auth src/features` | Фичу |
|
| `npx @gromlab/create module auth src/business` | Бизнес-модуль |
|
||||||
| `npx @gromlab/create widget header src/widgets` | Виджет |
|
| `npx @gromlab/create widget header src/widgets` | Виджет |
|
||||||
| `npx @gromlab/create entity user src/entities` | Сущность |
|
|
||||||
| `npx @gromlab/create layout admin src/layouts` | Layout |
|
| `npx @gromlab/create layout admin src/layouts` | Layout |
|
||||||
| `npx @gromlab/create store auth src/shared/model` | Стор |
|
| `npx @gromlab/create store auth src/business/auth/stores` | Стор |
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Какие модули генерируются из шаблонов
|
|
||||||
|
|
||||||
| Модуль | Слой | Шаблон |
|
|
||||||
|---|---|---|
|
|
||||||
| Компонент | `shared/ui/` | `component` |
|
|
||||||
| Фича | `features/` | `feature` |
|
|
||||||
| Виджет | `widgets/` | `widget` |
|
|
||||||
| Сущность | `entities/` | `entity` |
|
|
||||||
| Layout | `layouts/` | `layout` |
|
|
||||||
| Экран | `screens/` | `screen` |
|
|
||||||
| Стор | `model/` | `store` |
|
|
||||||
|
|
||||||
## Когда создавать новый шаблон
|
|
||||||
|
|
||||||
- Повторяющаяся структура появляется больше одного раза.
|
|
||||||
- Существующий шаблон не покрывает нужный тип модуля.
|
|
||||||
|
|
||||||
0
docs/docs/usage/video.md
Normal file
0
docs/docs/usage/video.md
Normal file
8
docs/docs/workflow.md
Normal file
8
docs/docs/workflow.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Подсказки
|
||||||
|
description: Короткие ответы на типовые вопросы и решения для спорных ситуаций.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Подсказки
|
||||||
|
|
||||||
|
Короткие ответы на типовые вопросы и решения для спорных ситуаций.
|
||||||
371
docs/index.md
Normal file
371
docs/index.md
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
---
|
||||||
|
layout: false
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const THEME_KEY = 'vitepress-theme-appearance'
|
||||||
|
|
||||||
|
// __BUILD_VERSION__ подставляется Vite-define из ENV `BUILD_VERSION`
|
||||||
|
// (см. .vitepress/config.ts). В dev и build всегда определена.
|
||||||
|
const buildVersion = __BUILD_VERSION__
|
||||||
|
|
||||||
|
const theme = ref('auto')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const savedTheme = localStorage.getItem(THEME_KEY)
|
||||||
|
theme.value = savedTheme === 'dark' || savedTheme === 'light' ? savedTheme : 'auto'
|
||||||
|
})
|
||||||
|
|
||||||
|
function setTheme(value) {
|
||||||
|
theme.value = value
|
||||||
|
if (value === 'auto') {
|
||||||
|
localStorage.removeItem(THEME_KEY)
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(THEME_KEY, value)
|
||||||
|
}
|
||||||
|
const isDark = value === 'dark' || (value === 'auto' && matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
|
document.documentElement.classList.toggle('dark', isDark)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Клик по кнопке темы:
|
||||||
|
* - по активной → переключение в auto;
|
||||||
|
* - по неактивной → выбор этого варианта.
|
||||||
|
*/
|
||||||
|
function toggleTheme(value) {
|
||||||
|
setTheme(theme.value === value ? 'auto' : value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="landing">
|
||||||
|
<section class="landing__hero">
|
||||||
|
<h1 class="landing__title">NextJS Style Guide</h1>
|
||||||
|
<ClientOnly>
|
||||||
|
<p class="landing__tagline">Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.</p>
|
||||||
|
<div class="landing__controls">
|
||||||
|
<a
|
||||||
|
class="landing__repo"
|
||||||
|
href="https://gromlab.ru/docs/nextjs-style-guide"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 21.07c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.08-.74.09-.73.09-.73 1.2.09 1.83 1.24 1.83 1.24 1.08 1.83 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18a4.65 4.65 0 0 1 1.23 3.22c0 4.61-2.8 5.63-5.48 5.92.42.36.81 1.1.81 2.22l-.01 3.29c0 .31.2.69.82.57A12 12 0 0 0 12 .3Z"/>
|
||||||
|
</svg>
|
||||||
|
<span>Репозиторий</span>
|
||||||
|
</a>
|
||||||
|
<div class="seg seg--icons" role="group" aria-label="Тема">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="seg__btn"
|
||||||
|
:class="{ 'seg__btn--active': theme === 'light' }"
|
||||||
|
:aria-pressed="theme === 'light'"
|
||||||
|
title="Светлая"
|
||||||
|
@click="toggleTheme('light')"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="seg__btn"
|
||||||
|
:class="{ 'seg__btn--active': theme === 'dark' }"
|
||||||
|
:aria-pressed="theme === 'dark'"
|
||||||
|
title="Тёмная"
|
||||||
|
@click="toggleTheme('dark')"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ClientOnly>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="landing__cards">
|
||||||
|
<a class="landing__card" href="/docs/">
|
||||||
|
<h3>Документация</h3>
|
||||||
|
<p>Все разделы: процессы разработки, базовые правила, прикладные руководства.</p>
|
||||||
|
<span class="landing__cta">Открыть →</span>
|
||||||
|
</a>
|
||||||
|
<div class="landing__card landing__card--multi">
|
||||||
|
<h3>Ассистенту</h3>
|
||||||
|
<p>
|
||||||
|
Карта документации для AI-агентов:
|
||||||
|
<code>/llms.txt</code>, <code>/llms-full.txt</code>.
|
||||||
|
</p>
|
||||||
|
<div class="landing__buttons">
|
||||||
|
<a class="landing__button" href="/llms.txt">llms.txt</a>
|
||||||
|
<a class="landing__button" href="/llms-full.txt">llms-full.txt</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="landing__card" href="/nextjs-style-guide.zip">
|
||||||
|
<h3>Скачать правила</h3>
|
||||||
|
<p>Архив всех Markdown-файлов одним ZIP.</p>
|
||||||
|
<span class="landing__cta">Скачать →</span>
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p class="landing__version">v{{ buildVersion }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.landing {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 48px 32px;
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
font-family: var(--vp-font-family-base);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 28px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__controls > * {
|
||||||
|
height: 36px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__repo {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.15s, border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__repo:hover {
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__repo svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seg {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 4px;
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 999px;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seg__btn {
|
||||||
|
appearance: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.15s, background-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seg__btn:hover {
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seg__btn--active {
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seg--icons .seg__btn {
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seg__btn svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__hero {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__title {
|
||||||
|
font-size: 56px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
background: linear-gradient(120deg, var(--vp-c-brand-1), var(--vp-c-brand-2));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__tagline {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.55;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__cards {
|
||||||
|
max-width: 1100px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
transition: border-color 0.2s, transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__card:hover {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__card h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__card p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__cta {
|
||||||
|
margin-top: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__buttons {
|
||||||
|
margin-top: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: var(--vp-font-family-mono, monospace);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: border-color 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__button:hover {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__version {
|
||||||
|
text-align: center;
|
||||||
|
margin: 24px 0 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
font-family: var(--vp-font-family-mono, monospace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.landing {
|
||||||
|
padding: 48px 20px 56px;
|
||||||
|
gap: 40px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.landing__title {
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
.landing__tagline {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.landing__cards {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.landing__controls {
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.landing {
|
||||||
|
padding: 44px 16px 48px;
|
||||||
|
gap: 36px;
|
||||||
|
}
|
||||||
|
.landing__title {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
.landing__tagline {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.landing__controls {
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
/* Репозиторий — только иконка, без текста, чтобы все контролы влезли в строку */
|
||||||
|
.landing__repo {
|
||||||
|
width: 36px;
|
||||||
|
padding: 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.landing__repo span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.seg__btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.landing__card {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
601
generate-llms.ts
Normal file
601
generate-llms.ts
Normal file
@@ -0,0 +1,601 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import config from './.vitepress/config';
|
||||||
|
|
||||||
|
/** Версия сборки. Передаётся CI через ENV; локально — `dev`. */
|
||||||
|
const VERSION = process.env.BUILD_VERSION || 'dev';
|
||||||
|
const BUILD_DATE = new Date().toISOString();
|
||||||
|
|
||||||
|
/** Корневая папка для генерируемой статики (попадает в build dist). */
|
||||||
|
const PUBLIC_DIR = 'docs/public';
|
||||||
|
|
||||||
|
/** Префикс URL документации. Соответствует структуре `docs/docs/...`. */
|
||||||
|
const DOC_PREFIX = '/docs/';
|
||||||
|
|
||||||
|
/** Канонический хост сайта (для sitemap/robots). Можно переопределить через ENV. */
|
||||||
|
const SITE_URL = (process.env.SITE_URL || 'https://nextjs-style-guide.gromlab.ru').replace(/\/$/, '');
|
||||||
|
|
||||||
|
interface SidebarItem {
|
||||||
|
text: string;
|
||||||
|
link?: string;
|
||||||
|
items?: SidebarItem[];
|
||||||
|
collapsed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Entry {
|
||||||
|
/** Название группы верхнего уровня (sidebar[].text) */
|
||||||
|
section: string;
|
||||||
|
/** Префикс из вложенной группы (например "Архитектура") */
|
||||||
|
prefix: string | null;
|
||||||
|
/** Текст пункта в sidebar */
|
||||||
|
text: string;
|
||||||
|
/** Ссылка из sidebar */
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Разобрать YAML frontmatter (плоский, без вложенностей) */
|
||||||
|
const parseFrontmatter = (
|
||||||
|
content: string,
|
||||||
|
): { data: Record<string, string>; body: string } => {
|
||||||
|
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
||||||
|
if (!match) return { data: {}, body: content };
|
||||||
|
|
||||||
|
const data: Record<string, string> = {};
|
||||||
|
for (const line of match[1].split('\n')) {
|
||||||
|
const lineMatch = line.match(/^([^:]+):\s*(.*)$/);
|
||||||
|
if (!lineMatch) continue;
|
||||||
|
let value = lineMatch[2].trim();
|
||||||
|
if (
|
||||||
|
(value.startsWith('"') && value.endsWith('"')) ||
|
||||||
|
(value.startsWith("'") && value.endsWith("'"))
|
||||||
|
) {
|
||||||
|
value = value.slice(1, -1);
|
||||||
|
}
|
||||||
|
data[lineMatch[1].trim()] = value;
|
||||||
|
}
|
||||||
|
return { data, body: match[2] };
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Первый абзац после h1 — однострочное описание для llms.txt */
|
||||||
|
const firstParagraphAfterH1 = (body: string): string | null => {
|
||||||
|
const lines = body.split('\n');
|
||||||
|
const h1Idx = lines.findIndex((l) => /^#\s/.test(l));
|
||||||
|
if (h1Idx === -1) return null;
|
||||||
|
|
||||||
|
let i = h1Idx + 1;
|
||||||
|
while (i < lines.length && lines[i].trim() === '') i++;
|
||||||
|
|
||||||
|
const para: string[] = [];
|
||||||
|
while (
|
||||||
|
i < lines.length &&
|
||||||
|
lines[i].trim() !== '' &&
|
||||||
|
!lines[i].startsWith('#')
|
||||||
|
) {
|
||||||
|
para.push(lines[i].trim());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return para.join(' ').trim() || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Преобразовать sidebar `link` (например `/docs/foo`) в относительный
|
||||||
|
* путь файла внутри `docs/docs/`. Префикс `/docs/` отрезается.
|
||||||
|
*/
|
||||||
|
const linkToRel = (link: string): string => {
|
||||||
|
let rel = link.startsWith(DOC_PREFIX)
|
||||||
|
? link.slice(DOC_PREFIX.length)
|
||||||
|
: link.replace(/^\//, '');
|
||||||
|
if (rel === '' || rel.endsWith('/')) {
|
||||||
|
rel += 'index.md';
|
||||||
|
} else {
|
||||||
|
rel += '.md';
|
||||||
|
}
|
||||||
|
return rel;
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkToFilePath = (link: string): string =>
|
||||||
|
path.join('docs/docs', linkToRel(link));
|
||||||
|
|
||||||
|
/** Абсолютный URL `.md`-копии страницы на сайте. */
|
||||||
|
const linkToSiteUrl = (link: string): string =>
|
||||||
|
`${DOC_PREFIX}${linkToRel(link)}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Развернуть sidebar в плоский список с сохранением группы и
|
||||||
|
* накопленного префикса вложенных групп. Поддерживает произвольную
|
||||||
|
* глубину вложенности — префиксы подгрупп склеиваются через `: `.
|
||||||
|
*/
|
||||||
|
const flattenSidebar = (sidebar: SidebarItem[]): Entry[] => {
|
||||||
|
const entries: Entry[] = [];
|
||||||
|
|
||||||
|
const walk = (
|
||||||
|
items: SidebarItem[],
|
||||||
|
section: string,
|
||||||
|
prefix: string | null,
|
||||||
|
): void => {
|
||||||
|
for (const item of items) {
|
||||||
|
const hasChildren = !!item.items && item.items.length > 0;
|
||||||
|
|
||||||
|
if (item.link) {
|
||||||
|
entries.push({ section, prefix, text: item.text, link: item.link });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChildren) {
|
||||||
|
const nextPrefix = prefix ? `${prefix}: ${item.text}` : item.text;
|
||||||
|
walk(item.items!, section, nextPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const top of sidebar) {
|
||||||
|
const hasChildren = !!top.items && top.items.length > 0;
|
||||||
|
|
||||||
|
if (top.link && !hasChildren) {
|
||||||
|
entries.push({ section: top.text, prefix: null, text: top.text, link: top.link });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChildren) {
|
||||||
|
walk(top.items!, top.text, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupBySection = (entries: Entry[]): Map<string, Entry[]> => {
|
||||||
|
const map = new Map<string, Entry[]>();
|
||||||
|
for (const entry of entries) {
|
||||||
|
const list = map.get(entry.section);
|
||||||
|
if (list) list.push(entry);
|
||||||
|
else map.set(entry.section, [entry]);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SiteConfig {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
themeConfig: { sidebar: SidebarItem[] };
|
||||||
|
llmsBlockquote?: string;
|
||||||
|
llmsContext?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cfg = config as unknown as SiteConfig;
|
||||||
|
|
||||||
|
const buildLlms = (): void => {
|
||||||
|
const sidebar = cfg.themeConfig.sidebar;
|
||||||
|
const blockquote = cfg.llmsBlockquote ?? cfg.description;
|
||||||
|
const context = cfg.llmsContext;
|
||||||
|
|
||||||
|
const entries = flattenSidebar(sidebar);
|
||||||
|
const grouped = groupBySection(entries);
|
||||||
|
|
||||||
|
const lines: string[] = [];
|
||||||
|
lines.push(`# ${cfg.title}`);
|
||||||
|
lines.push('');
|
||||||
|
lines.push(`> ${blockquote}`);
|
||||||
|
lines.push('');
|
||||||
|
if (context) {
|
||||||
|
lines.push(context);
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [section, items] of grouped) {
|
||||||
|
lines.push(`## ${section}`);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
for (const entry of items) {
|
||||||
|
const filePath = linkToFilePath(entry.link);
|
||||||
|
const url = linkToSiteUrl(entry.link);
|
||||||
|
|
||||||
|
let description: string | null = null;
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const raw = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const { data, body } = parseFrontmatter(raw);
|
||||||
|
description = data.description || firstParagraphAfterH1(body);
|
||||||
|
} else {
|
||||||
|
console.warn(`файл не найден: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const display = entry.prefix
|
||||||
|
? `${entry.prefix}: ${entry.text}`
|
||||||
|
: entry.text;
|
||||||
|
const descPart = description ? `: ${description}` : '';
|
||||||
|
lines.push(`- [${display}](${url})${descPart}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||||
|
const outFile = path.join(PUBLIC_DIR, 'llms.txt');
|
||||||
|
fs.writeFileSync(outFile, lines.join('\n'), 'utf8');
|
||||||
|
console.log(`${outFile} создан`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Рекурсивно скопировать дерево, фильтруя по предикату. */
|
||||||
|
const copyDirSync = (
|
||||||
|
src: string,
|
||||||
|
dest: string,
|
||||||
|
filter: (name: string) => boolean = () => true,
|
||||||
|
): number => {
|
||||||
|
let count = 0;
|
||||||
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
||||||
|
const srcPath = path.join(src, entry.name);
|
||||||
|
const destPath = path.join(dest, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
fs.mkdirSync(destPath, { recursive: true });
|
||||||
|
count += copyDirSync(srcPath, destPath, filter);
|
||||||
|
} else if (entry.isFile() && filter(entry.name)) {
|
||||||
|
fs.mkdirSync(dest, { recursive: true });
|
||||||
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Скопировать все `.md`-файлы документации в `docs/public/docs/`,
|
||||||
|
* чтобы они попали в build `dist/` и были доступны по URL `/docs/path.md`.
|
||||||
|
*/
|
||||||
|
const copyMdFiles = (): void => {
|
||||||
|
const srcDir = 'docs/docs';
|
||||||
|
const destDir = path.join(PUBLIC_DIR, 'docs');
|
||||||
|
if (!fs.existsSync(srcDir)) return;
|
||||||
|
|
||||||
|
const copied = copyDirSync(srcDir, destDir, (name) => name.endsWith('.md'));
|
||||||
|
console.log(`скопировано ${copied} .md-файлов в ${destDir}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Преобразовать sidebar `link` в относительный путь файла внутри архива
|
||||||
|
* (от корня папки `nextjs-style-guide/`).
|
||||||
|
*/
|
||||||
|
const linkToArchiveRel = (link: string): string => {
|
||||||
|
let rel = link.startsWith(DOC_PREFIX)
|
||||||
|
? link.slice(DOC_PREFIX.length)
|
||||||
|
: link.replace(/^\//, '');
|
||||||
|
if (rel === '' || rel.endsWith('/')) {
|
||||||
|
rel += 'index.md';
|
||||||
|
} else {
|
||||||
|
rel += '.md';
|
||||||
|
}
|
||||||
|
return rel;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заменить во всех `.md` архива ссылки `[text](/docs/foo)` на относительные
|
||||||
|
* пути от расположения файла. Без этого внутренние ссылки в распакованной
|
||||||
|
* папке не работают.
|
||||||
|
*/
|
||||||
|
const transformLinksInDir = (rootDir: string): void => {
|
||||||
|
const linkRe = /\]\(\/docs\/([^)\s#]*)(#[^)]*)?\)/g;
|
||||||
|
|
||||||
|
const walk = (dir: string): void => {
|
||||||
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
const full = path.join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
walk(full);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
||||||
|
|
||||||
|
const content = fs.readFileSync(full, 'utf8');
|
||||||
|
const fileDir = path.dirname(full);
|
||||||
|
|
||||||
|
const updated = content.replace(linkRe, (_match, route, hash = '') => {
|
||||||
|
const targetRel = linkToArchiveRel(`${DOC_PREFIX}${route}`);
|
||||||
|
const targetAbs = path.join(rootDir, targetRel);
|
||||||
|
let rel = path.relative(fileDir, targetAbs);
|
||||||
|
if (!rel.startsWith('.')) rel = './' + rel;
|
||||||
|
return `](${rel}${hash})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updated !== content) {
|
||||||
|
fs.writeFileSync(full, updated, 'utf8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
walk(rootDir);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать `README.md` — точка входа архива. Карта документации
|
||||||
|
* с относительными ссылками, описаниями из frontmatter/первого абзаца
|
||||||
|
* и метаинфо сборки.
|
||||||
|
*/
|
||||||
|
const buildArchiveReadme = (rootDir: string): void => {
|
||||||
|
const sidebar = cfg.themeConfig.sidebar;
|
||||||
|
const blockquote = cfg.llmsBlockquote ?? cfg.description ?? '';
|
||||||
|
const context = cfg.llmsContext;
|
||||||
|
|
||||||
|
const entries = flattenSidebar(sidebar).filter(
|
||||||
|
// «Главная» из sidebar — это страница раздела для веба, в архиве не нужна.
|
||||||
|
(e) => e.section !== 'Главная',
|
||||||
|
);
|
||||||
|
const grouped = groupBySection(entries);
|
||||||
|
|
||||||
|
const lines: string[] = [];
|
||||||
|
lines.push(`# ${cfg.title}`);
|
||||||
|
lines.push('');
|
||||||
|
if (blockquote) {
|
||||||
|
lines.push(`> ${blockquote}`);
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
if (context) {
|
||||||
|
lines.push(context);
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('## Содержание');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
for (const [section, items] of grouped) {
|
||||||
|
lines.push(`### ${section}`);
|
||||||
|
lines.push('');
|
||||||
|
for (const entry of items) {
|
||||||
|
const targetRel = './' + linkToArchiveRel(entry.link);
|
||||||
|
const filePath = path.join(rootDir, linkToArchiveRel(entry.link));
|
||||||
|
|
||||||
|
let description: string | null = null;
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const raw = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const { data, body } = parseFrontmatter(raw);
|
||||||
|
description = data.description || firstParagraphAfterH1(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const display = entry.prefix
|
||||||
|
? `${entry.prefix}: ${entry.text}`
|
||||||
|
: entry.text;
|
||||||
|
const descPart = description ? ` — ${description}` : '';
|
||||||
|
lines.push(`- [${display}](${targetRel})${descPart}`);
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('---');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(`Версия: ${VERSION} · Сборка: ${BUILD_DATE}`);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(rootDir, 'README.md'), lines.join('\n'), 'utf8');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Собрать `nextjs-style-guide.zip`. Внутри — папка `nextjs-style-guide/`
|
||||||
|
* с `.md`-файлами, README, `llms-full.txt` и `VERSION`. Внутренние ссылки
|
||||||
|
* преобразуются в относительные.
|
||||||
|
*/
|
||||||
|
const buildZip = (): void => {
|
||||||
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-'));
|
||||||
|
const stage = path.join(tmpRoot, 'nextjs-style-guide');
|
||||||
|
fs.mkdirSync(stage, { recursive: true });
|
||||||
|
|
||||||
|
// 1. Копируем все .md в staging.
|
||||||
|
copyDirSync('docs/docs', stage, (name) => name.endsWith('.md'));
|
||||||
|
|
||||||
|
// 2. Удаляем веб-index.md — в архиве его роль выполняет README.md.
|
||||||
|
const indexPath = path.join(stage, 'index.md');
|
||||||
|
if (fs.existsSync(indexPath)) fs.unlinkSync(indexPath);
|
||||||
|
|
||||||
|
// 3. Преобразуем абсолютные ссылки `/docs/...` в относительные.
|
||||||
|
transformLinksInDir(stage);
|
||||||
|
|
||||||
|
// 4. Генерируем точку входа README.md.
|
||||||
|
buildArchiveReadme(stage);
|
||||||
|
|
||||||
|
// 5. Кладём llms-full.txt — удобно для одноразового чтения LLM.
|
||||||
|
const llmsFullSrc = path.join(PUBLIC_DIR, 'llms-full.txt');
|
||||||
|
if (fs.existsSync(llmsFullSrc)) {
|
||||||
|
fs.copyFileSync(llmsFullSrc, path.join(stage, 'llms-full.txt'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Метаинформация сборки.
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(stage, 'VERSION'),
|
||||||
|
`${VERSION}\n${BUILD_DATE}\n`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const outFile = path.resolve(PUBLIC_DIR, 'nextjs-style-guide.zip');
|
||||||
|
fs.rmSync(outFile, { force: true });
|
||||||
|
|
||||||
|
execFileSync('zip', ['-rq', outFile, 'nextjs-style-guide'], {
|
||||||
|
cwd: tmpRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||||
|
console.log(`${outFile} создан (${VERSION})`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Удалить YAML frontmatter из исходника `.md`. */
|
||||||
|
const stripFrontmatter = (content: string): string =>
|
||||||
|
content.replace(/^---\n[\s\S]*?\n---\n*/, '');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сдвинуть уровень заголовков на 1 вниз (h1→h2, h2→h3, ...).
|
||||||
|
* Игнорирует строки внутри блоков кода.
|
||||||
|
*/
|
||||||
|
const shiftHeadings = (content: string): string => {
|
||||||
|
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');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Собрать `llms-full.txt` — все страницы в одном файле.
|
||||||
|
* Порядок страниц повторяет порядок в sidebar.
|
||||||
|
*/
|
||||||
|
const buildLlmsFull = (): void => {
|
||||||
|
const sidebar = cfg.themeConfig.sidebar;
|
||||||
|
const entries = flattenSidebar(sidebar);
|
||||||
|
const blockquote = cfg.llmsBlockquote ?? cfg.description ?? '';
|
||||||
|
|
||||||
|
const parts: string[] = [];
|
||||||
|
parts.push(`# ${cfg.title}`);
|
||||||
|
parts.push('');
|
||||||
|
if (blockquote) parts.push(`> ${blockquote}`);
|
||||||
|
if (cfg.llmsContext) {
|
||||||
|
parts.push('');
|
||||||
|
parts.push(cfg.llmsContext);
|
||||||
|
}
|
||||||
|
parts.push('');
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const filePath = linkToFilePath(entry.link);
|
||||||
|
if (!fs.existsSync(filePath)) continue;
|
||||||
|
|
||||||
|
const raw = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const content = shiftHeadings(stripFrontmatter(raw)).trim();
|
||||||
|
if (!content) continue;
|
||||||
|
|
||||||
|
// Мета-якорь: путь страницы для ориентации LLM
|
||||||
|
parts.push(`<!-- ${entry.link} -->`);
|
||||||
|
parts.push('');
|
||||||
|
parts.push(content);
|
||||||
|
parts.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||||
|
const outFile = path.join(PUBLIC_DIR, 'llms-full.txt');
|
||||||
|
fs.writeFileSync(outFile, parts.join('\n'), 'utf8');
|
||||||
|
console.log(`${outFile} создан`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Манифест сборки — для лендинга и внешних потребителей. */
|
||||||
|
const writeManifest = (): void => {
|
||||||
|
const manifest = {
|
||||||
|
version: VERSION,
|
||||||
|
buildDate: BUILD_DATE,
|
||||||
|
llms: '/llms.txt',
|
||||||
|
llmsFull: '/llms-full.txt',
|
||||||
|
zip: '/nextjs-style-guide.zip',
|
||||||
|
};
|
||||||
|
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(PUBLIC_DIR, 'manifest.json'),
|
||||||
|
JSON.stringify(manifest, null, 2),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
console.log(`${PUBLIC_DIR}/manifest.json создан`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать `robots.txt` с указанием sitemap и явными ссылками
|
||||||
|
* на llms.txt/llms-full.txt — стандартные файлы, которые читают агенты.
|
||||||
|
*/
|
||||||
|
const buildRobots = (): void => {
|
||||||
|
const lines = [
|
||||||
|
'User-agent: *',
|
||||||
|
'Allow: /',
|
||||||
|
'',
|
||||||
|
`Sitemap: ${SITE_URL}/sitemap.xml`,
|
||||||
|
'',
|
||||||
|
'# Карта документации для AI-агентов:',
|
||||||
|
`# ${SITE_URL}/llms.txt`,
|
||||||
|
`# ${SITE_URL}/llms-full.txt`,
|
||||||
|
'',
|
||||||
|
];
|
||||||
|
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(PUBLIC_DIR, 'robots.txt'), lines.join('\n'), 'utf8');
|
||||||
|
console.log(`${PUBLIC_DIR}/robots.txt создан`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать `sitemap.xml` из sidebar + корневые ресурсы для LLM
|
||||||
|
* (llms.txt, llms-full.txt) — чтобы агенты, читающие sitemap, видели их.
|
||||||
|
*/
|
||||||
|
const buildSitemap = (): void => {
|
||||||
|
const sidebar = cfg.themeConfig.sidebar;
|
||||||
|
const entries = flattenSidebar(sidebar);
|
||||||
|
|
||||||
|
const urls = new Set<string>();
|
||||||
|
urls.add(`${SITE_URL}/`);
|
||||||
|
urls.add(`${SITE_URL}/llms.txt`);
|
||||||
|
urls.add(`${SITE_URL}/llms-full.txt`);
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const link = entry.link;
|
||||||
|
// cleanUrls: канон без `.html`. Index-страницы — каталог со слешем.
|
||||||
|
urls.add(`${SITE_URL}${link}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = BUILD_DATE.slice(0, 10);
|
||||||
|
const xml = [
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
|
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
||||||
|
...[...urls].map(
|
||||||
|
(loc) => ` <url><loc>${loc}</loc><lastmod>${today}</lastmod></url>`,
|
||||||
|
),
|
||||||
|
'</urlset>',
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(PUBLIC_DIR, 'sitemap.xml'), xml, 'utf8');
|
||||||
|
console.log(`${PUBLIC_DIR}/sitemap.xml создан`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Преобразовать абсолютные ссылки `index.md` в рабочие при открытии
|
||||||
|
* README в репозитории:
|
||||||
|
* - `/docs/foo` → относительный путь `docs/docs/foo.md`;
|
||||||
|
* - корневые ресурсы (`/llms.txt`, `/llms-full.txt`, `*.zip`, `/manifest.json`,
|
||||||
|
* `/sitemap.xml`, `/robots.txt`) — генерируемые, в репозитории отсутствуют,
|
||||||
|
* поэтому ссылки переписываются на абсолютный `SITE_URL`.
|
||||||
|
*/
|
||||||
|
const transformReadmeLinks = (content: string): string => {
|
||||||
|
const linkRe = /\]\((\/[^)\s]*)\)/g;
|
||||||
|
return content.replace(linkRe, (match, href: string) => {
|
||||||
|
const [pathPart, hash = ''] = href.split('#');
|
||||||
|
const hashPart = hash ? `#${hash}` : '';
|
||||||
|
|
||||||
|
if (pathPart.startsWith(DOC_PREFIX)) {
|
||||||
|
const rel = linkToArchiveRel(pathPart);
|
||||||
|
return `](docs/docs/${rel}${hashPart})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `](${SITE_URL}${pathPart}${hashPart})`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Скопировать `index.md` документации в корневой README без frontmatter. */
|
||||||
|
const buildReadme = (): void => {
|
||||||
|
const indexPath = 'docs/docs/index.md';
|
||||||
|
if (!fs.existsSync(indexPath)) {
|
||||||
|
console.warn(`Пропуск README.md: ${indexPath} не найден`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const raw = fs.readFileSync(indexPath, 'utf8');
|
||||||
|
const { body } = parseFrontmatter(raw);
|
||||||
|
const transformed = transformReadmeLinks(body.trimStart());
|
||||||
|
|
||||||
|
// Порядок: H1 → описание (первый абзац) → ссылка на сайт.
|
||||||
|
const withSiteLink = transformed.replace(
|
||||||
|
/^(#\s[^\n]*\n\n[^\n]+\n)/,
|
||||||
|
`$1\nСайт: ${SITE_URL}\n`,
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync('README.md', withSiteLink, 'utf8');
|
||||||
|
console.log(`README.md обновлён из ${indexPath}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
buildLlms();
|
||||||
|
buildLlmsFull();
|
||||||
|
copyMdFiles();
|
||||||
|
buildZip();
|
||||||
|
writeManifest();
|
||||||
|
buildRobots();
|
||||||
|
buildSitemap();
|
||||||
|
buildReadme();
|
||||||
166
notes
166
notes
@@ -1,23 +1,153 @@
|
|||||||
# TODO
|
ФЛОУ
|
||||||
|
- после создания компонента, заменить шаблонный коментарий документа на реальный.
|
||||||
|
|
||||||
## Триггеры: классификация и расширение
|
|
||||||
|
|
||||||
Текущий список триггеров слабо проработан. Нужно:
|
Проблема, неочевидность слоев (наследие FSD)
|
||||||
|
|
||||||
1. Классифицировать триггеры по группам:
|
|
||||||
- Создание — новые модули, компоненты, страницы
|
|
||||||
- Ресурсы — ассеты (иконки, шрифты, изображения, видео)
|
|
||||||
- Данные — API, сторы, серверные данные, формы
|
|
||||||
- Навигация — роутинг, middleware, редиректы
|
|
||||||
- Модификация — рефакторинг, перенос, удаление
|
|
||||||
- Инфраструктура — зависимости, переводы, настройка окружения
|
|
||||||
|
|
||||||
2. Добавить недостающие триггеры. Примеры пробелов:
|
Архитектурные слои проекта
|
||||||
- Создание: утилита/хелпер, тип/интерфейс, контекст
|
Каждый нижний слой не знает о существовании верхних. Импорты идут только сверху вниз.
|
||||||
- Данные: создать форму, добавить валидацию
|
pages → layouts → screens → widgets → features → entities → shared
|
||||||
- Навигация: динамический роут, middleware, редирект
|
---
|
||||||
- Модификация: рефакторинг компонента, перенос модуля, удаление модуля
|
1. Pages (pages/)
|
||||||
- Обработка ошибок: error boundary, fallback UI, error.tsx, not-found.tsx, loading.tsx
|
Точка входа маршрута. Только связывает layout и screen.
|
||||||
- Авторизация: защита страницы, проверка прав
|
Правила:
|
||||||
|
- Никакой логики, стилей, разметки кроме композиции
|
||||||
|
- Один page = один layout + один screen
|
||||||
|
Пример:
|
||||||
|
// pages/knv-new.js
|
||||||
|
import { KnvScreen } from 'src/screens/knv'
|
||||||
|
import { MainLayout } from 'src/layouts/main'
|
||||||
|
const KnvNewPage = () => (
|
||||||
|
<MainLayout>
|
||||||
|
<KnvScreen />
|
||||||
|
</MainLayout>
|
||||||
|
)
|
||||||
|
---
|
||||||
|
2. Layouts (src/layouts/)
|
||||||
|
Каркас страницы — общие элементы, которые одинаковы на всех страницах в рамках этого layout.
|
||||||
|
Содержит в ui/: header, footer, sidebar — дочерние компоненты, которые привязаны к layout и не переиспользуются отдельно.
|
||||||
|
Критерий: компонент одинаков на всех страницах, использующих этот layout? → layouts/{name}/ui/
|
||||||
|
Пример:
|
||||||
|
src/layouts/main/
|
||||||
|
├── main.layout.tsx # <Header /> + children + <Footer />
|
||||||
|
├── ui/
|
||||||
|
│ ├── header/ # всегда одинаковый на всех страницах
|
||||||
|
│ └── footer/ # всегда одинаковый на всех страницах
|
||||||
|
---
|
||||||
|
3. Screens (src/screens/)
|
||||||
|
Контент конкретной страницы. Собирает свои секции и переиспользуемые widgets/features/entities.
|
||||||
|
Содержит в ui/: блоки, которые существуют только на этой странице и не переиспользуются.
|
||||||
|
Критерий: компонент используется только на одной странице? → screens/{name}/ui/
|
||||||
|
Пример:
|
||||||
|
src/screens/knv/
|
||||||
|
├── knv.screen.tsx
|
||||||
|
├── ui/
|
||||||
|
│ ├── hero-section/ # hero только на главной КНВ
|
||||||
|
│ ├── products-section/ # секция препаратов только на главной
|
||||||
|
│ ├── diseases-section/ # секция заболеваний только на главной
|
||||||
|
│ └── doctor-section/ # секция врачей только на главной
|
||||||
|
Каждая секция внутри может использовать shared/ui компоненты:
|
||||||
|
// screens/knv/ui/products-section/products-section.widget.tsx
|
||||||
|
import { Carousel } from 'src/shared/ui/carousel'
|
||||||
|
import { ProductCard } from './ui/product-card' // локальный, пока не переиспользуется
|
||||||
|
Когда локальный компонент начинает использоваться на 2+ страницах — выносим в entities/ или shared/ui.
|
||||||
|
---
|
||||||
|
4. Widgets (src/widgets/)
|
||||||
|
Составные блоки с данными/логикой, которые переиспользуются на 2+ страницах.
|
||||||
|
Критерий: блок с бизнес-логикой + данными используется на нескольких страницах? → widgets/
|
||||||
|
Пример: Слайдер «Популярные препараты» с загрузкой данных из API, который показывается и на главной, и на странице заболевания, и в каталоге:
|
||||||
|
src/widgets/
|
||||||
|
├── popular-products-slider/
|
||||||
|
│ ├── popular-products-slider.widget.tsx # Carousel + ProductCard + useProducts()
|
||||||
|
│ ├── hooks/
|
||||||
|
│ │ └── use-products.hook.ts # запрос данных
|
||||||
|
Не widget: секция «Подобрать врача» которая есть только на главной → screens/knv/ui/
|
||||||
|
---
|
||||||
|
5. Features (src/features/)
|
||||||
|
Пользовательское действие или интерактивный сценарий. Содержит бизнес-логику взаимодействия.
|
||||||
|
Критерий: это действие пользователя (отправить форму, авторизоваться, добавить в корзину)? → features/
|
||||||
|
Примеры:
|
||||||
|
src/features/
|
||||||
|
├── auth/ # авторизация (форма + логика + стор)
|
||||||
|
│ ├── auth.feature.tsx
|
||||||
|
│ ├── hooks/
|
||||||
|
│ │ └── use-auth.hook.ts
|
||||||
|
│ └── stores/
|
||||||
|
│ └── auth.store.ts
|
||||||
|
│
|
||||||
|
├── order-drug/ # заказ препарата (кнопка + модалка + API)
|
||||||
|
│ ├── order-drug.feature.tsx
|
||||||
|
│ └── hooks/
|
||||||
|
│ └── use-order.hook.ts
|
||||||
|
Не feature: отображение карточки препарата без взаимодействия → entities/ или shared/ui
|
||||||
|
---
|
||||||
|
6. Entities (src/entities/)
|
||||||
|
Бизнес-сущность с её отображением и типами. Привязана к домену (препарат, заболевание, врач, пользователь).
|
||||||
|
Критерий: это представление бизнес-объекта, которое переиспользуется в разных контекстах? → entities/
|
||||||
|
Примеры:
|
||||||
|
src/entities/
|
||||||
|
├── product/ # Препарат
|
||||||
|
│ ├── ui/
|
||||||
|
│ │ └── product-card/ # карточка препарата (каталог, слайдеры, поиск)
|
||||||
|
│ ├── types/
|
||||||
|
│ │ └── product.type.ts # { id, name, mnn, indication }
|
||||||
|
│ └── index.ts
|
||||||
|
│
|
||||||
|
├── disease/ # Заболевание
|
||||||
|
│ ├── ui/
|
||||||
|
│ │ └── disease-card/
|
||||||
|
│ ├── types/
|
||||||
|
│ │ └── disease.type.ts
|
||||||
|
│ └── index.ts
|
||||||
|
Отличие от shared/ui: entity-компонент знает о бизнес-домене (принимает Product, а не абстрактные пропсы). shared/ui Button не знает ничего о бизнесе.
|
||||||
|
---
|
||||||
|
7. Shared (src/shared/)
|
||||||
|
Переиспользуемые компоненты, утилиты, стили без бизнес-логики.
|
||||||
|
Критерий: компонент не знает о бизнес-домене, работает с абстрактными данными? → shared/
|
||||||
|
src/shared/
|
||||||
|
├── ui/ # UI-компоненты
|
||||||
|
│ ├── carousel/ # принимает children, не знает о препаратах
|
||||||
|
│ ├── container/
|
||||||
|
│ ├── section/
|
||||||
|
│ └── icon-svg/
|
||||||
|
├── styles/ # CSS-переменные, media
|
||||||
|
│ ├── variables.css
|
||||||
|
│ └── media.css
|
||||||
|
├── sprites/ # SVG-спрайты
|
||||||
|
└── lib/ # утилиты, хелперы
|
||||||
|
---
|
||||||
|
Сводная таблица принятия решений
|
||||||
|
Вопрос Да → Нет ↓
|
||||||
|
Это точка входа маршрута? pages/ ↓
|
||||||
|
Одинаков на всех страницах layout? layouts/{name}/ui/ ↓
|
||||||
|
Используется только на одной странице? screens/{name}/ui/ ↓
|
||||||
|
Составной блок с данными на 2+ страницах? widgets/ ↓
|
||||||
|
Это действие пользователя с логикой? features/ ↓
|
||||||
|
Привязан к бизнес-сущности? entities/ ↓
|
||||||
|
Абстрактный UI без бизнес-логики? shared/ui/ —
|
||||||
|
|
||||||
3. Обновить секцию "Триггеры" в DEVELOP.md — перейти на новые группы.
|
|
||||||
|
И еще размышлений
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Что у нас по факту
|
||||||
|
Мы взяли FSD и добавили:
|
||||||
|
- Слой Layouts — каркас страницы
|
||||||
|
- Слой Screens — контент страницы с локальными компонентами
|
||||||
|
- Правило scope — компонент живёт максимально близко к месту использования, и поднимается на уровень выше только при переиспользовании
|
||||||
|
Это не FSD в чистом виде. Но и не что-то принципиально новое.
|
||||||
|
Существующие названия
|
||||||
|
Feature-Sliced Design (FSD) — то от чего отталкиваемся, но расходимся в деталях.
|
||||||
|
Screaming Architecture (Robert C. Martin) — структура проекта "кричит" о том, что он делает. Открываешь screens/knv/ui/ — видишь КНВ. Открываешь widgets/ — видишь глобальное. Это близко к нашему подходу.
|
||||||
|
Colocation Principle — держи код рядом с тем, где он используется. Именно этот принцип отличает нас от FSD: page-specific компоненты лежат внутри screen, а не в глобальном widgets/.
|
||||||
|
Как назвать
|
||||||
|
Я бы не стал придумывать новое название. Это:
|
||||||
|
> FSD с принципом колокации (FSD + Colocation)
|
||||||
|
Или проще — адаптированный FSD. Суть отличия укладывается в одно правило:
|
||||||
|
> Компонент живёт на самом низком уровне, где он используется. Поднимается выше только при переиспользовании на 2+ страницах.
|
||||||
|
Если хочется краткое название для внутренней документации — можно SLD (Scoped Layer Design), подчёркивая что каждый компонент привязан к scope (shared → entity → feature → widget → screen → layout → page).
|
||||||
|
|||||||
532
package-lock.json
generated
532
package-lock.json
generated
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend-style-guide",
|
"name": "nextjs-style-guide",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "frontend-style-guide",
|
"name": "nextjs-style-guide",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"tsx": "^4.19.2",
|
||||||
"vitepress": "^1.6.3"
|
"vitepress": "^1.6.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -643,6 +644,23 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||||
@@ -660,6 +678,23 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||||
@@ -677,6 +712,23 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||||
@@ -1659,6 +1711,19 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-tsconfig": {
|
||||||
|
"version": "4.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
|
||||||
|
"integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"resolve-pkg-maps": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hast-util-to-html": {
|
"node_modules/hast-util-to-html": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
|
||||||
@@ -2088,6 +2153,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/resolve-pkg-maps": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rfdc": {
|
"node_modules/rfdc": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||||
@@ -2248,6 +2323,459 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tsx": {
|
||||||
|
"version": "4.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||||
|
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild": "~0.27.0",
|
||||||
|
"get-tsconfig": "^4.7.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tsx": "dist/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/esbuild": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.27.7",
|
||||||
|
"@esbuild/android-arm": "0.27.7",
|
||||||
|
"@esbuild/android-arm64": "0.27.7",
|
||||||
|
"@esbuild/android-x64": "0.27.7",
|
||||||
|
"@esbuild/darwin-arm64": "0.27.7",
|
||||||
|
"@esbuild/darwin-x64": "0.27.7",
|
||||||
|
"@esbuild/freebsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/freebsd-x64": "0.27.7",
|
||||||
|
"@esbuild/linux-arm": "0.27.7",
|
||||||
|
"@esbuild/linux-arm64": "0.27.7",
|
||||||
|
"@esbuild/linux-ia32": "0.27.7",
|
||||||
|
"@esbuild/linux-loong64": "0.27.7",
|
||||||
|
"@esbuild/linux-mips64el": "0.27.7",
|
||||||
|
"@esbuild/linux-ppc64": "0.27.7",
|
||||||
|
"@esbuild/linux-riscv64": "0.27.7",
|
||||||
|
"@esbuild/linux-s390x": "0.27.7",
|
||||||
|
"@esbuild/linux-x64": "0.27.7",
|
||||||
|
"@esbuild/netbsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/netbsd-x64": "0.27.7",
|
||||||
|
"@esbuild/openbsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/openbsd-x64": "0.27.7",
|
||||||
|
"@esbuild/openharmony-arm64": "0.27.7",
|
||||||
|
"@esbuild/sunos-x64": "0.27.7",
|
||||||
|
"@esbuild/win32-arm64": "0.27.7",
|
||||||
|
"@esbuild/win32-ia32": "0.27.7",
|
||||||
|
"@esbuild/win32-x64": "0.27.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unist-util-position": {
|
"node_modules/unist-util-position": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend-style-guide",
|
"name": "nextjs-style-guide",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:ai": "node ./scripts/build-ai.js",
|
"llms": "tsx ./generate-llms.ts",
|
||||||
"dev": "vitepress dev .",
|
"dev": "vitepress dev .",
|
||||||
"build": "vitepress build .",
|
"build": "vitepress build .",
|
||||||
"serve": "vitepress serve ."
|
"serve": "vitepress serve ."
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"tsx": "^4.19.2",
|
||||||
"vitepress": "^1.6.3"
|
"vitepress": "^1.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { pathToFileURL } from "url";
|
|
||||||
|
|
||||||
const SRC_DIR = "./src";
|
|
||||||
const DIST_DIR = "./dist/ai";
|
|
||||||
const SCRIPTS_DIR = "./scripts";
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Сборка по манифесту
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
async function buildForFramework(framework) {
|
|
||||||
const manifestPath = path.join(SCRIPTS_DIR, `${framework}.build.js`);
|
|
||||||
|
|
||||||
if (!fs.existsSync(manifestPath)) {
|
|
||||||
console.error(`Манифест не найден: ${manifestPath}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const manifest = (await import(pathToFileURL(path.resolve(manifestPath)).href)).default;
|
|
||||||
const outDir = path.join(DIST_DIR, framework);
|
|
||||||
|
|
||||||
// Очищаем выходную директорию
|
|
||||||
if (fs.existsSync(outDir)) {
|
|
||||||
fs.rmSync(outDir, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\nСборка: ${manifest.name} (${framework})`);
|
|
||||||
console.log(`Выход: ${outDir}\n`);
|
|
||||||
|
|
||||||
const errors = [];
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
for (const [destRelative, srcRelative] of Object.entries(manifest.files)) {
|
|
||||||
const srcPath = path.join(SRC_DIR, srcRelative);
|
|
||||||
const destPath = path.join(outDir, destRelative);
|
|
||||||
|
|
||||||
if (!fs.existsSync(srcPath)) {
|
|
||||||
errors.push(` [!] Не найден: ${srcRelative}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
||||||
fs.copyFileSync(srcPath, destPath);
|
|
||||||
console.log(` ${destRelative}`);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
console.log(`\nОшибки:`);
|
|
||||||
errors.forEach((e) => console.log(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\nГотово: ${outDir} (${count} файлов)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Определяем что собирать
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
let frameworks = fs
|
|
||||||
.readdirSync(SCRIPTS_DIR)
|
|
||||||
.filter((f) => f.endsWith(".build.js"))
|
|
||||||
.map((f) => f.replace(".build.js", ""));
|
|
||||||
|
|
||||||
if (frameworks.length === 0) {
|
|
||||||
console.error("Не найдено ни одного манифеста *.build.js в scripts/");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --framework=nextjs
|
|
||||||
const fwArg = process.argv.find((a) => a.startsWith("--framework="));
|
|
||||||
if (fwArg) {
|
|
||||||
const fw = fwArg.split("=")[1];
|
|
||||||
if (frameworks.includes(fw)) {
|
|
||||||
frameworks = [fw];
|
|
||||||
} else {
|
|
||||||
console.error(`Фреймворк "${fw}" не найден. Доступные: ${frameworks.join(", ")}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const fw of frameworks) {
|
|
||||||
await buildForFramework(fw);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("\nВсе сборки завершены.");
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* Манифест сборки стайлгайда для Next.js.
|
|
||||||
*
|
|
||||||
* Ключ — путь файла в dist/ai/nextjs/.
|
|
||||||
* Значение — путь исходника относительно src/.
|
|
||||||
*
|
|
||||||
* Скрипт только копирует. Никакой генерации.
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
name: "Next.js",
|
|
||||||
|
|
||||||
files: {
|
|
||||||
// ── Точки входа ─────────────────────────────────────────────
|
|
||||||
"DEVELOP.md": "nextjs/DEVELOP.md",
|
|
||||||
|
|
||||||
// ── Базовые правила ─────────────────────────────────────────
|
|
||||||
"basics/architecture.md": "base/basics/architecture.md",
|
|
||||||
"basics/code-style.md": "base/basics/code-style.md",
|
|
||||||
"basics/documentation.md": "base/basics/documentation.md",
|
|
||||||
"basics/naming.md": "base/basics/naming.md",
|
|
||||||
"basics/tech-stack.md": "base/basics/tech-stack.md",
|
|
||||||
"basics/typing.md": "base/basics/typing.md",
|
|
||||||
|
|
||||||
// ── Прикладные разделы ──────────────────────────────────────
|
|
||||||
"applied/components.md": "base/applied/components.md",
|
|
||||||
"applied/styles.md": "base/applied/styles.md",
|
|
||||||
"applied/templates-generation.md": "base/applied/templates-generation.md",
|
|
||||||
"applied/hooks.md": "base/applied/hooks.md",
|
|
||||||
"applied/stores.md": "base/applied/stores.md",
|
|
||||||
"applied/api.md": "base/applied/api.md",
|
|
||||||
"applied/fonts.md": "base/applied/fonts.md",
|
|
||||||
"applied/localization.md": "base/applied/localization.md",
|
|
||||||
"applied/images-sprites.md": "base/applied/images-sprites.md",
|
|
||||||
"applied/svg-sprites.md": "base/applied/svg-sprites.md",
|
|
||||||
"applied/video.md": "base/applied/video.md",
|
|
||||||
"applied/vscode.md": "base/applied/vscode.md",
|
|
||||||
"applied/page-level.md": "nextjs/applied/page-level.md",
|
|
||||||
"applied/project-structure.md": "nextjs/applied/project-structure.md",
|
|
||||||
|
|
||||||
// ── Триггеры: разработка / создание ─────────────────────────
|
|
||||||
"triggers/develop/create-component.md": "base/triggers/develop/create-component.md",
|
|
||||||
"triggers/develop/create-feature.md": "base/triggers/develop/create-feature.md",
|
|
||||||
"triggers/develop/create-widget.md": "base/triggers/develop/create-widget.md",
|
|
||||||
"triggers/develop/create-entity.md": "base/triggers/develop/create-entity.md",
|
|
||||||
"triggers/develop/create-hook.md": "base/triggers/develop/create-hook.md",
|
|
||||||
"triggers/develop/create-store.md": "base/triggers/develop/create-store.md",
|
|
||||||
"triggers/develop/create-page.md": "nextjs/triggers/develop/create-page.md",
|
|
||||||
"triggers/develop/create-layout.md": "nextjs/triggers/develop/create-layout.md",
|
|
||||||
"triggers/develop/create-project.md": "nextjs/triggers/develop/create-project.md",
|
|
||||||
"triggers/develop/generate-module.md": "base/triggers/develop/generate-module.md",
|
|
||||||
|
|
||||||
// ── Триггеры: разработка / стилизация и ресурсы ─────────────
|
|
||||||
"triggers/develop/style-component.md": "base/triggers/develop/style-component.md",
|
|
||||||
"triggers/develop/add-icon.md": "base/triggers/develop/add-icon.md",
|
|
||||||
"triggers/develop/add-image.md": "base/triggers/develop/add-image.md",
|
|
||||||
"triggers/develop/add-video.md": "base/triggers/develop/add-video.md",
|
|
||||||
"triggers/develop/add-font.md": "base/triggers/develop/add-font.md",
|
|
||||||
|
|
||||||
// ── Триггеры: разработка / данные и состояние ───────────────
|
|
||||||
"triggers/develop/add-api-request.md": "base/triggers/develop/add-api-request.md",
|
|
||||||
"triggers/develop/connect-store.md": "base/triggers/develop/connect-store.md",
|
|
||||||
"triggers/develop/add-server-data.md": "nextjs/triggers/develop/add-server-data.md",
|
|
||||||
|
|
||||||
// ── Триггеры: разработка / инфраструктура ───────────────────
|
|
||||||
"triggers/develop/add-localization.md": "base/triggers/develop/add-localization.md",
|
|
||||||
"triggers/develop/add-dependency.md": "base/triggers/develop/add-dependency.md",
|
|
||||||
"triggers/develop/setup-vscode.md": "base/triggers/develop/setup-vscode.md",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
scope: applied
|
|
||||||
keywords: [api, запрос, fetch, SWR, эндпоинт, REST, клиент]
|
|
||||||
when: "Работа с API: запросы, клиенты, обработка ответов"
|
|
||||||
---
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
scope: applied
|
|
||||||
keywords: [шрифт, font, next/font, подключение шрифта, woff]
|
|
||||||
when: "Подключение и настройка шрифтов"
|
|
||||||
---
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
scope: applied
|
|
||||||
keywords: [хук, hook, use, кастомный хук, useState, useEffect]
|
|
||||||
when: "Создание или использование кастомных хуков"
|
|
||||||
---
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
scope: applied
|
|
||||||
keywords: [изображение, картинка, image, next/image, public, оптимизация]
|
|
||||||
when: "Работа с изображениями: подключение, оптимизация"
|
|
||||||
---
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
scope: applied
|
|
||||||
keywords: [i18n, локализация, перевод, язык, i18next, namespace]
|
|
||||||
when: "Локализация: добавление переводов, работа с i18next"
|
|
||||||
---
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
scope: applied
|
|
||||||
keywords: [стор, store, zustand, состояние, глобальное состояние]
|
|
||||||
when: "Работа с глобальным состоянием: создание стора, подписка"
|
|
||||||
---
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: SVG-спрайты
|
|
||||||
scope: applied
|
|
||||||
keywords: [svg, спрайт, иконка, icon, sprite]
|
|
||||||
when: "Работа с SVG-иконками и спрайтами"
|
|
||||||
---
|
|
||||||
# SVG-спрайты
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
scope: applied
|
|
||||||
keywords: [видео, video, плеер, mp4]
|
|
||||||
when: "Встраивание и работа с видео"
|
|
||||||
---
|
|
||||||
@@ -1,665 +0,0 @@
|
|||||||
---
|
|
||||||
title: Архитектура
|
|
||||||
scope: basics
|
|
||||||
keywords: [SLM Design, слой, модуль, сегмент, архитектура, FSD, scoped layered module]
|
|
||||||
when: "Организация кода: слои, модули, зависимости между модулями"
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- /index -->
|
|
||||||
# SLM Design
|
|
||||||
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
|
||||||
|
|
||||||
## Преимущества
|
|
||||||
|
|
||||||
### Вертикальная организация домена
|
|
||||||
|
|
||||||
Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы.
|
|
||||||
|
|
||||||
### Dependency Injection без фреймворков
|
|
||||||
|
|
||||||
Cross-domain зависимости в бизнес-слое реализуются через фабрики — модуль декларирует что ему нужно, а точка использования предоставляет зависимости. Домены изолированы без DI-контейнеров, провайдеров и шин событий.
|
|
||||||
|
|
||||||
### Разделение ответственности без перегрузки слоёв
|
|
||||||
|
|
||||||
Сервисы приложения (`infrastructure/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
|
|
||||||
|
|
||||||
### Горизонтальная инкапсуляция
|
|
||||||
|
|
||||||
Вложенные модули (`parts/`) и направление зависимостей позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга.
|
|
||||||
|
|
||||||
### Колокация по умолчанию
|
|
||||||
|
|
||||||
Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями.
|
|
||||||
|
|
||||||
### Явное разделение каркаса и контента
|
|
||||||
|
|
||||||
Каркас группы маршрутов (`layouts/`) и контент конкретной страницы (`screens/`) — независимые слои с собственной ответственностью.
|
|
||||||
|
|
||||||
### Масштабирование через группировку
|
|
||||||
|
|
||||||
При росте проекта слои не теряют структуру — модули группируются по естественным признакам: бизнес-домены по субдоменам, страницы по разделам, UI-компоненты по уровню абстракции (примитивы и композиции).
|
|
||||||
|
|
||||||
## Происхождение
|
|
||||||
|
|
||||||
SLM Design вырос на основе:
|
|
||||||
|
|
||||||
- **Feature-Sliced Design** — слоистая структура, публичный API модуля, направление зависимостей
|
|
||||||
- **Vertical Slice Architecture** — модуль как вертикальный срез, содержащий всё необходимое
|
|
||||||
- **Screaming Architecture** — структура проекта «кричит» о назначении: открыл `business/auth` — видишь авторизацию
|
|
||||||
- **Colocation Principle** — код живёт рядом с местом использования
|
|
||||||
|
|
||||||
## Пример структуры проекта
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/
|
|
||||||
├── app/
|
|
||||||
│
|
|
||||||
├── layouts/
|
|
||||||
│ ├── main/
|
|
||||||
│ └── dashboard/
|
|
||||||
│
|
|
||||||
├── screens/
|
|
||||||
│ ├── home/
|
|
||||||
│ ├── products/
|
|
||||||
│ ├── product-detail/
|
|
||||||
│ └── about/
|
|
||||||
│
|
|
||||||
├── widgets/
|
|
||||||
│ ├── page-heading/
|
|
||||||
│ ├── hero-section/
|
|
||||||
│ └── promo-banner/
|
|
||||||
│
|
|
||||||
├── business/
|
|
||||||
│ ├── auth/
|
|
||||||
│ ├── catalog/
|
|
||||||
│ ├── orders/
|
|
||||||
│ └── chat/
|
|
||||||
│
|
|
||||||
├── infrastructure/
|
|
||||||
│ ├── theme/
|
|
||||||
│ ├── i18n/
|
|
||||||
│ ├── backend-api/
|
|
||||||
│ └── logger/
|
|
||||||
│
|
|
||||||
├── ui/
|
|
||||||
│ ├── button/
|
|
||||||
│ ├── input/
|
|
||||||
│ ├── modal/
|
|
||||||
│ ├── toast/
|
|
||||||
│ └── dropdown/
|
|
||||||
│
|
|
||||||
└── shared/
|
|
||||||
├── lib/
|
|
||||||
├── types/
|
|
||||||
└── styles/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Принципы
|
|
||||||
|
|
||||||
- **Домен — единое целое.** Всё, что относится к домену, живёт в одном модуле.
|
|
||||||
- **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости.
|
|
||||||
- **Зависимости однонаправлены.** Импорты только сверху вниз, только через публичный API.
|
|
||||||
- **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.
|
|
||||||
|
|
||||||
<!-- /reference/layers -->
|
|
||||||
## Слои
|
|
||||||
|
|
||||||
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
|
|
||||||
|
|
||||||
### Определение
|
|
||||||
|
|
||||||
**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.**
|
|
||||||
|
|
||||||
### Группы слоёв
|
|
||||||
|
|
||||||
Слои делятся на три группы:
|
|
||||||
|
|
||||||
| Группа | Слои | Описание |
|
|
||||||
|--------|------|----------|
|
|
||||||
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
|
|
||||||
| Ядро | `business`, `infrastructure`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
|
|
||||||
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
|
|
||||||
|
|
||||||
### Направление зависимостей
|
|
||||||
|
|
||||||
Любой импорт между модулями — только через публичный API.
|
|
||||||
|
|
||||||
```
|
|
||||||
app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared
|
|
||||||
```
|
|
||||||
|
|
||||||
- `layouts` и `screens` — параллельные слои, не импортируют друг друга
|
|
||||||
- Модули одного слоя в группе «Композиция» изолированы друг от друга
|
|
||||||
- Модули одного слоя `infrastructure` и `ui` могут импортировать друг друга через публичный API
|
|
||||||
- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую
|
|
||||||
- Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях
|
|
||||||
|
|
||||||
|
|
||||||
### Слой App
|
|
||||||
|
|
||||||
Точка входа приложения. Отвечает за запуск, роутинг и композицию маршрутов из layout и screen.
|
|
||||||
|
|
||||||
В отличие от остальных слоёв, `app/` не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации.
|
|
||||||
|
|
||||||
#### Требования
|
|
||||||
|
|
||||||
- Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация
|
|
||||||
- Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов
|
|
||||||
- Провайдеры и гарды — только подключает готовые из нижних слоёв, не реализует
|
|
||||||
- Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы
|
|
||||||
- Никем не импортируется
|
|
||||||
|
|
||||||
### Слой Layouts
|
|
||||||
|
|
||||||
Каркас страницы: общие элементы, одинаковые для группы маршрутов (header, footer, sidebar).
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/layouts/
|
|
||||||
├── main/
|
|
||||||
├── dashboard/
|
|
||||||
└── auth/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Требования
|
|
||||||
|
|
||||||
- Содержит только модули
|
|
||||||
- Не содержит бизнес-логику
|
|
||||||
- Контекстно-зависимые блоки принимает через пропсы от `app`, не импортирует напрямую
|
|
||||||
|
|
||||||
### Слой Screens
|
|
||||||
|
|
||||||
Контент конкретной страницы: собирает её из модулей нижних слоёв.
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/screens/
|
|
||||||
├── home/
|
|
||||||
├── products/
|
|
||||||
├── product-detail/
|
|
||||||
├── about/
|
|
||||||
└── contacts/
|
|
||||||
```
|
|
||||||
|
|
||||||
Когда количество страниц затрудняет навигацию — вводится группировка по разделам. Группа — папка для организации, не модуль (без `index.ts`).
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/screens/
|
|
||||||
├── shop/
|
|
||||||
│ ├── home/
|
|
||||||
│ ├── products/
|
|
||||||
│ ├── product-detail/
|
|
||||||
│ └── cart/
|
|
||||||
├── account/
|
|
||||||
│ ├── profile/
|
|
||||||
│ ├── settings/
|
|
||||||
│ └── order-history/
|
|
||||||
└── info/
|
|
||||||
├── about/
|
|
||||||
├── contacts/
|
|
||||||
└── faq/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Требования
|
|
||||||
|
|
||||||
- Содержит только модули
|
|
||||||
- Не содержит бизнес-логику
|
|
||||||
- Локальные одноразовые секции живут внутри screen-модуля, не выносятся в `widgets`/`business`
|
|
||||||
|
|
||||||
### Слой Widgets
|
|
||||||
|
|
||||||
Составной блок интерфейса, который компонует модули ядра, но не принадлежит конкретному бизнес-домену. Widget появляется когда блок используется в нескольких screens или layouts.
|
|
||||||
|
|
||||||
Если блок принадлежит домену — он живёт в `business/{area}/`, даже если переиспользуется. Если блок нужен только в одном месте — это `screens/{name}/parts/` или `layouts/{name}/parts/`, а не widget.
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/widgets/
|
|
||||||
├── page-heading/
|
|
||||||
├── hero-section/
|
|
||||||
├── onboarding-checklist/
|
|
||||||
├── promo-banner/
|
|
||||||
└── error-boundary/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Требования
|
|
||||||
|
|
||||||
- Не принадлежит конкретному бизнес-домену. Если блок доменный — он живёт в `business/`
|
|
||||||
- Используется в нескольких screens или layouts
|
|
||||||
|
|
||||||
### Слой Business
|
|
||||||
|
|
||||||
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
|
|
||||||
|
|
||||||
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Cross-domain зависимости по коду реализуются через фабрику. `import type` между доменами разрешён напрямую.
|
|
||||||
|
|
||||||
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки.
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/business/
|
|
||||||
├── auth/
|
|
||||||
├── catalog/
|
|
||||||
├── orders/
|
|
||||||
├── checkout/
|
|
||||||
└── chat/
|
|
||||||
```
|
|
||||||
|
|
||||||
Когда количество доменов затрудняет навигацию — вводится группировка по субдоменам. Группа — папка для организации, не модуль (без `index.ts`).
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/business/
|
|
||||||
├── commerce/
|
|
||||||
│ ├── catalog/
|
|
||||||
│ ├── cart/
|
|
||||||
│ ├── orders/
|
|
||||||
│ └── checkout/
|
|
||||||
└── communication/
|
|
||||||
├── chat/
|
|
||||||
└── notifications/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Требования
|
|
||||||
|
|
||||||
- Один модуль = один бизнес-домен
|
|
||||||
- Циклические зависимости между доменами запрещены
|
|
||||||
- Импорт кода между доменами — через фабрику. `import type` — напрямую
|
|
||||||
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
|
|
||||||
|
|
||||||
### Слой Infrastructure
|
|
||||||
|
|
||||||
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
|
|
||||||
|
|
||||||
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`.
|
|
||||||
|
|
||||||
Отличие от `shared/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/infrastructure/
|
|
||||||
├── theme/
|
|
||||||
├── i18n/
|
|
||||||
├── backend-api/
|
|
||||||
├── maps-api/
|
|
||||||
├── logger/
|
|
||||||
├── feature-flags/
|
|
||||||
└── realtime/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Требования
|
|
||||||
|
|
||||||
- Один модуль = один техсервис
|
|
||||||
- Импортирует `infrastructure/`, `ui/`, `shared/`
|
|
||||||
|
|
||||||
### Слой UI
|
|
||||||
|
|
||||||
UI-кит без бизнес-логики: button, carousel, toast, modal.
|
|
||||||
|
|
||||||
Слой входит в группу «Ядро». Импортирует `ui/` и `shared/`.
|
|
||||||
|
|
||||||
Компоненты строятся друг на друге: `button` использует `icon`, `carousel` использует `button`.
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/ui/
|
|
||||||
├── button/
|
|
||||||
├── input/
|
|
||||||
├── icon/
|
|
||||||
├── carousel/
|
|
||||||
├── modal/
|
|
||||||
├── toast/
|
|
||||||
├── dropdown/
|
|
||||||
├── tabs/
|
|
||||||
└── tooltip/
|
|
||||||
```
|
|
||||||
|
|
||||||
Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (`button`, `icon`, `input`) не импортируют композиции. Композиции (`carousel`, `modal`, `dropdown`) строятся на примитивах.
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/ui/
|
|
||||||
├── primitives/
|
|
||||||
│ ├── button/
|
|
||||||
│ ├── input/
|
|
||||||
│ ├── icon/
|
|
||||||
│ └── badge/
|
|
||||||
└── composites/
|
|
||||||
├── carousel/
|
|
||||||
├── modal/
|
|
||||||
├── dropdown/
|
|
||||||
├── tabs/
|
|
||||||
└── tooltip/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Требования
|
|
||||||
|
|
||||||
- Не содержит бизнес-логику
|
|
||||||
- Импортирует только `ui/` и `shared/`
|
|
||||||
|
|
||||||
### Слой Shared
|
|
||||||
|
|
||||||
Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене.
|
|
||||||
|
|
||||||
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
|
|
||||||
|
|
||||||
Отличие от `infrastructure/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
|
||||||
|
|
||||||
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/shared/
|
|
||||||
├── lib/
|
|
||||||
├── types/
|
|
||||||
├── styles/
|
|
||||||
└── sprites/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Требования
|
|
||||||
|
|
||||||
- Не имеет runtime-состояния
|
|
||||||
|
|
||||||
<!-- /reference/modules -->
|
|
||||||
## Модули
|
|
||||||
|
|
||||||
Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом.
|
|
||||||
|
|
||||||
### Определение
|
|
||||||
|
|
||||||
**Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.**
|
|
||||||
|
|
||||||
### Модуль vs компонент
|
|
||||||
|
|
||||||
**Компонент** — один `.tsx` файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или `ui/` сегменте модуля.
|
|
||||||
|
|
||||||
**Модуль** — папка, которая может содержать корневой компонент, сегменты (`hooks/`, `types/`, `styles/`, `ui/`, `parts/` и т.д.) и публичный API (`index.ts`).
|
|
||||||
|
|
||||||
```text
|
|
||||||
auth/
|
|
||||||
├── ui/
|
|
||||||
│ ├── auth-guard.tsx
|
|
||||||
│ └── logout-button.tsx
|
|
||||||
├── parts/
|
|
||||||
│ ├── login-form/
|
|
||||||
│ ├── registration-form/
|
|
||||||
│ └── restore-form/
|
|
||||||
├── hooks/
|
|
||||||
├── stores/
|
|
||||||
├── types/
|
|
||||||
├── auth.tsx # корневой компонент (опционален)
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Структура
|
|
||||||
|
|
||||||
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного `index.ts` с реэкспортом типов.
|
|
||||||
|
|
||||||
```text
|
|
||||||
{module-name}/
|
|
||||||
├── {module-name}.tsx # корневой компонент (опционален)
|
|
||||||
├── ui/ # компоненты модуля (только .tsx)
|
|
||||||
├── parts/ # вложенные модули (со своими сегментами)
|
|
||||||
├── hooks/ # хуки
|
|
||||||
├── stores/ # сторы состояния
|
|
||||||
├── services/ # внешние источники данных
|
|
||||||
├── mappers/ # трансформация данных между форматами
|
|
||||||
├── types/ # типы
|
|
||||||
├── styles/ # стили
|
|
||||||
├── lib/ # утилиты модуля
|
|
||||||
├── config/ # константы
|
|
||||||
└── index.ts # публичный API
|
|
||||||
```
|
|
||||||
|
|
||||||
Подробное описание каждого сегмента — в разделе [Сегменты](/reference/segments).
|
|
||||||
|
|
||||||
### Публичный API
|
|
||||||
|
|
||||||
Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/auth/index.ts
|
|
||||||
export type { User, Session } from './types/user.types'
|
|
||||||
export { useAuth } from './hooks/use-auth.hook'
|
|
||||||
export { AuthGuard } from './ui/auth-guard'
|
|
||||||
```
|
|
||||||
|
|
||||||
Импорт в обход `index.ts` запрещён:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Плохо
|
|
||||||
import { validateToken } from '@/business/auth/lib/tokens'
|
|
||||||
|
|
||||||
// Хорошо
|
|
||||||
import { useAuth } from '@/business/auth'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Фабрика
|
|
||||||
|
|
||||||
Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове.
|
|
||||||
|
|
||||||
Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — `import type` не является runtime-зависимостью.
|
|
||||||
|
|
||||||
#### Модуль без зависимостей — прямой экспорт:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/auth/index.ts
|
|
||||||
export { useAuth } from './hooks/use-auth'
|
|
||||||
export { useCurrentUser } from './hooks/use-current-user'
|
|
||||||
export type { User, Session } from './types'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Модуль с зависимостями — фабрика:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/chat/types/deps.ts
|
|
||||||
import type { User } from '@/business/auth'
|
|
||||||
|
|
||||||
export interface ChatDeps {
|
|
||||||
useCurrentUser: () => User | null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/chat/index.ts
|
|
||||||
import type { ChatDeps } from './types/deps'
|
|
||||||
|
|
||||||
export function chatFactory(deps: ChatDeps) {
|
|
||||||
return {
|
|
||||||
useMessages: (roomId: string) => {
|
|
||||||
const user = deps.useCurrentUser()
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
useSendMessage: (roomId: string) => {
|
|
||||||
const user = deps.useCurrentUser()
|
|
||||||
return (text: string) => { /* ... */ }
|
|
||||||
},
|
|
||||||
useChatRooms: () => {
|
|
||||||
const user = deps.useCurrentUser()
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
ChatBadge: ({ count }: { count: number }) => { /* ... */ },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { Message, ChatRoom } from './types'
|
|
||||||
export type { ChatDeps } from './types/deps'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Использование на странице:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// screens/support/support.tsx
|
|
||||||
import { useCurrentUser } from '@/business/auth'
|
|
||||||
import { chatFactory } from '@/business/chat'
|
|
||||||
|
|
||||||
const chat = chatFactory({ useCurrentUser })
|
|
||||||
|
|
||||||
export function SupportScreen() {
|
|
||||||
const { useMessages, useSendMessage, ChatBadge } = chat
|
|
||||||
const messages = useMessages('support')
|
|
||||||
const sendMessage = useSendMessage('support')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ChatBadge count={messages.length} />
|
|
||||||
{messages.map(m => <MessageBubble key={m.id} {...m} />)}
|
|
||||||
<MessageInput onSend={sendMessage} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Жизненный цикл
|
|
||||||
|
|
||||||
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
|
|
||||||
|
|
||||||
- Нужен на одной странице → `screens/{name}/parts/`
|
|
||||||
- Появился в 2+ местах → поднимается по природе:
|
|
||||||
- абстрактный UI → `ui/`
|
|
||||||
- блок с данными/логикой → `widgets/`
|
|
||||||
- представление бизнес-домена → `business/{area}/parts/`
|
|
||||||
|
|
||||||
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
|
|
||||||
|
|
||||||
<!-- /reference/segments -->
|
|
||||||
## Сегменты
|
|
||||||
|
|
||||||
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
|
|
||||||
|
|
||||||
### Определение
|
|
||||||
|
|
||||||
**Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.**
|
|
||||||
|
|
||||||
### Обзор
|
|
||||||
|
|
||||||
| Сегмент | Содержимое |
|
|
||||||
|---------|------------|
|
|
||||||
| `ui/` | Компоненты модуля — только `.tsx` файлы |
|
|
||||||
| `parts/` | Вложенные модули со своими сегментами |
|
|
||||||
| `hooks/` | React-хуки |
|
|
||||||
| `stores/` | Сторы состояния |
|
|
||||||
| `services/` | Работа с внешними источниками данных |
|
|
||||||
| `mappers/` | Трансформация данных между форматами |
|
|
||||||
| `types/` | TypeScript-типы и интерфейсы |
|
|
||||||
| `styles/` | Стили |
|
|
||||||
| `lib/` | Утилиты и хелперы модуля |
|
|
||||||
| `config/` | Константы и конфигурация |
|
|
||||||
|
|
||||||
### Сегмент ui/
|
|
||||||
|
|
||||||
Компоненты, принадлежащие модулю. Содержит только `.tsx` файлы — без своих сегментов, стилей, типов, хуков. Использует сегменты родительского модуля.
|
|
||||||
|
|
||||||
```text
|
|
||||||
auth/
|
|
||||||
├── ui/
|
|
||||||
│ ├── auth-provider.tsx
|
|
||||||
│ ├── auth-guard.tsx
|
|
||||||
│ └── logout-button.tsx
|
|
||||||
├── types/
|
|
||||||
├── hooks/
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Если компоненту нужны собственные сегменты — это уже не `ui/`, а `parts/`.
|
|
||||||
|
|
||||||
### Сегмент parts/
|
|
||||||
|
|
||||||
Вложенные модули со своими сегментами. Каждый элемент `parts/` — полноценный модуль: папка с компонентом, хуками, стилями, типами и т.д.
|
|
||||||
|
|
||||||
```text
|
|
||||||
home/
|
|
||||||
├── parts/
|
|
||||||
│ ├── hero-section/
|
|
||||||
│ │ ├── hero-section.tsx
|
|
||||||
│ │ ├── styles/
|
|
||||||
│ │ └── parts/
|
|
||||||
│ │ └── top-banner/
|
|
||||||
│ │ └── top-banner.tsx
|
|
||||||
│ └── features-section/
|
|
||||||
│ ├── features-section.tsx
|
|
||||||
│ └── hooks/
|
|
||||||
├── home.screen.tsx
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Отличие от `ui/`: элемент `parts/` — модуль со своими сегментами. Элемент `ui/` — компонент, один `.tsx` файл.
|
|
||||||
|
|
||||||
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
|
|
||||||
|
|
||||||
Если вложенный модуль обрастает своими `parts/` — это сигнал, что он достаточно самостоятельный для подъёма на уровень выше.
|
|
||||||
|
|
||||||
### Сегмент hooks/
|
|
||||||
|
|
||||||
React-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты.
|
|
||||||
|
|
||||||
```text
|
|
||||||
hooks/
|
|
||||||
├── use-auth.hook.ts
|
|
||||||
├── use-session.hook.ts
|
|
||||||
└── use-permissions.hook.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сегмент stores/
|
|
||||||
|
|
||||||
Сторы состояния модуля. Конкретная реализация зависит от выбранного стейт-менеджера (Zustand, MobX, Redux и т.д.).
|
|
||||||
|
|
||||||
```text
|
|
||||||
stores/
|
|
||||||
├── auth.store.ts
|
|
||||||
└── session.store.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сегмент services/
|
|
||||||
|
|
||||||
Работа с внешними источниками данных: API-вызовы, запросы, подписки.
|
|
||||||
|
|
||||||
```text
|
|
||||||
services/
|
|
||||||
├── auth.service.ts
|
|
||||||
└── token.service.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сегмент mappers/
|
|
||||||
|
|
||||||
Функции трансформации данных из одного формата в другой: DTO в доменный тип, доменный тип в DTO, доменный тип в ViewModel.
|
|
||||||
|
|
||||||
```text
|
|
||||||
mappers/
|
|
||||||
├── map-user.ts
|
|
||||||
├── map-product.ts
|
|
||||||
└── map-order-to-dto.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сегмент types/
|
|
||||||
|
|
||||||
TypeScript-типы и интерфейсы модуля. Доменные типы, DTO, пропсы компонентов.
|
|
||||||
|
|
||||||
```text
|
|
||||||
types/
|
|
||||||
├── user.type.ts
|
|
||||||
└── session.type.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сегмент styles/
|
|
||||||
|
|
||||||
Стили модуля. Формат зависит от выбранного подхода (CSS Modules, SCSS, CSS-in-JS и т.д.).
|
|
||||||
|
|
||||||
```text
|
|
||||||
styles/
|
|
||||||
├── auth.module.css
|
|
||||||
└── login-form.module.css
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сегмент lib/
|
|
||||||
|
|
||||||
Утилиты и хелперы, специфичные для модуля. Чистые функции без побочных эффектов.
|
|
||||||
|
|
||||||
```text
|
|
||||||
lib/
|
|
||||||
├── validate-email.ts
|
|
||||||
└── format-phone.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Отличие от `shared/lib/`: здесь лежат утилиты, нужные только этому модулю. Общие утилиты — в `shared/lib/`.
|
|
||||||
|
|
||||||
### Сегмент config/
|
|
||||||
|
|
||||||
Константы и конфигурация модуля: маршруты, лимиты, дефолтные значения.
|
|
||||||
|
|
||||||
```text
|
|
||||||
config/
|
|
||||||
├── routes.ts
|
|
||||||
└── constants.ts
|
|
||||||
```
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: Начало работы
|
|
||||||
scope: workflow
|
|
||||||
keywords: [начало, onboarding, настройка, установка, первый запуск]
|
|
||||||
when: "Первый запуск проекта, знакомство со стеком"
|
|
||||||
---
|
|
||||||
# Начало работы
|
|
||||||
|
|
||||||
Что нужно знать перед началом разработки в проекте.
|
|
||||||
|
|
||||||
## Стек проекта
|
|
||||||
|
|
||||||
**Next.js** (App Router), **Mantine**, **Zustand**, **FSD**.
|
|
||||||
|
|
||||||
Подробнее — [Технологии и библиотеки](/basics/tech-stack).
|
|
||||||
|
|
||||||
## Ключевые особенности
|
|
||||||
|
|
||||||
- **Генерация вместо ручного создания** — компоненты, фичи, виджеты, сторы и другие модули не создаются вручную. Файловая структура генерируется из шаблонов `.templates/`. Ручное создание файловой структуры модулей запрещено.
|
|
||||||
- **Biome вместо ESLint + Prettier** — один инструмент для линтинга и форматирования. Автофикс и сортировка импортов происходят автоматически при сохранении файла.
|
|
||||||
|
|
||||||
## Настройка окружения
|
|
||||||
|
|
||||||
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/applied/vscode).
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
title: Добавить API-запрос
|
|
||||||
---
|
|
||||||
|
|
||||||
# Добавить API-запрос
|
|
||||||
|
|
||||||
Инструкция по добавлению запроса к серверу: создание клиента, хука, обработка ответа.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/api.md — правила API-слоя: клиенты, эндпоинты, обработка ошибок
|
|
||||||
- basics/typing.md — типизация запросов и ответов
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи подход:
|
|
||||||
- Клиентские данные → SWR / хук
|
|
||||||
- Серверные данные → серверный компонент (RSC)
|
|
||||||
|
|
||||||
2. Опиши типы запроса и ответа.
|
|
||||||
|
|
||||||
3. Создай или расширь API-клиент (→ applied/api.md).
|
|
||||||
|
|
||||||
4. Создай хук для использования в компоненте (→ triggers/develop/create-hook.md).
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-hook.md — хук для запроса
|
|
||||||
- triggers/develop/create-component.md — компонент, использующий данные
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Типы запроса и ответа описаны
|
|
||||||
- [ ] Хук для использования в компоненте создан
|
|
||||||
- [ ] Обработка ошибок реализована
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: Добавить зависимость
|
|
||||||
---
|
|
||||||
|
|
||||||
# Добавить зависимость
|
|
||||||
|
|
||||||
Инструкция по добавлению новой npm-зависимости в проект. Проверь допустимость перед установкой.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- basics/tech-stack.md — разрешённый стек, допустимые библиотеки
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Проверь, что библиотека не дублирует уже используемую (→ basics/tech-stack.md).
|
|
||||||
|
|
||||||
2. Проверь, что библиотека входит в разрешённый список или обоснуй необходимость.
|
|
||||||
|
|
||||||
3. Установи как `dependency` или `devDependency` в зависимости от назначения.
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Библиотека не дублирует уже используемую
|
|
||||||
- [ ] Библиотека входит в разрешённый список (→ basics/tech-stack.md)
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
title: Подключить шрифт
|
|
||||||
---
|
|
||||||
|
|
||||||
# Подключить шрифт
|
|
||||||
|
|
||||||
Инструкция по подключению и настройке шрифта в проекте.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/fonts.md — правила подключения шрифтов: форматы, загрузка, CSS-переменные
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Подготовь файлы шрифта (woff2).
|
|
||||||
|
|
||||||
2. Подключи шрифт по правилам (→ applied/fonts.md).
|
|
||||||
|
|
||||||
3. Зарегистрируй CSS-переменную для шрифта.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/style-component.md — использование шрифта в стилях
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Файл шрифта в формате woff2
|
|
||||||
- [ ] CSS-переменная для шрифта зарегистрирована
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
title: Добавить иконку
|
|
||||||
---
|
|
||||||
|
|
||||||
# Добавить иконку
|
|
||||||
|
|
||||||
Инструкция по добавлению SVG-иконки в проект через спрайт-систему.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/svg-sprites.md — правила SVG-спрайтов: структура, именование, использование
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Подготовь SVG-файл: убери лишние атрибуты, оптимизируй.
|
|
||||||
|
|
||||||
2. Добавь SVG в спрайт по правилам (→ applied/svg-sprites.md).
|
|
||||||
|
|
||||||
3. Используй иконку в компоненте через компонент-обёртку.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-component.md — если нужен компонент-обёртка для иконки
|
|
||||||
- triggers/develop/style-component.md — стилизация иконки (размер, цвет)
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] SVG оптимизирован — убраны лишние атрибуты
|
|
||||||
- [ ] Иконка добавлена в спрайт по правилам (→ applied/svg-sprites.md)
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
title: Добавить изображение
|
|
||||||
---
|
|
||||||
|
|
||||||
# Добавить изображение
|
|
||||||
|
|
||||||
Инструкция по добавлению и использованию растровых изображений в проекте.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/images-sprites.md — правила работы с изображениями: оптимизация, форматы, подключение
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи тип изображения:
|
|
||||||
- Статическое (логотип, декор) → `public/`
|
|
||||||
- Динамическое (контентное) → URL из API
|
|
||||||
|
|
||||||
2. Оптимизируй изображение (формат, размер, сжатие).
|
|
||||||
|
|
||||||
3. Подключи в компоненте по правилам (→ applied/images-sprites.md).
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-component.md — если нужен компонент-обёртка для изображения
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Изображение оптимизировано (формат, размер, сжатие)
|
|
||||||
- [ ] Подключено по правилам (→ applied/images-sprites.md)
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
title: Добавить перевод
|
|
||||||
---
|
|
||||||
|
|
||||||
# Добавить перевод
|
|
||||||
|
|
||||||
Инструкция по добавлению локализации: создание ключей перевода и подключение в компоненте.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/localization.md — правила локализации: namespace, ключи, форматирование
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи namespace для переводов (→ applied/localization.md).
|
|
||||||
|
|
||||||
2. Добавь ключи перевода в файлы локализации.
|
|
||||||
|
|
||||||
3. Подключи переводы в компоненте (→ applied/localization.md).
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-component.md — если компонент ещё не создан
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Ключи перевода добавлены в файлы локализации
|
|
||||||
- [ ] Namespace определён (→ applied/localization.md)
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
title: Добавить видео
|
|
||||||
---
|
|
||||||
|
|
||||||
# Добавить видео
|
|
||||||
|
|
||||||
Инструкция по встраиванию видео в проект.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/video.md — правила работы с видео: форматы, плеер, оптимизация
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи тип видео:
|
|
||||||
- Локальное → `public/`
|
|
||||||
- Внешнее (YouTube, Vimeo) → embed
|
|
||||||
|
|
||||||
2. Подключи видео по правилам (→ applied/video.md).
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-component.md — если нужен компонент-обёртка для видео
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Видео подключено по правилам (→ applied/video.md)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
title: Подключить стор к компоненту
|
|
||||||
---
|
|
||||||
|
|
||||||
# Подключить стор к компоненту
|
|
||||||
|
|
||||||
Инструкция по подключению стора к React-компоненту.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/stores.md — правила сторов: подписка, селекторы
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи нужен ли стор:
|
|
||||||
- Локальное состояние → `useState` / `useReducer`
|
|
||||||
- Глобальное состояние → стор
|
|
||||||
|
|
||||||
2. Если стор не существует — создай его (→ triggers/develop/create-store.md).
|
|
||||||
|
|
||||||
3. Подключи стор в компоненте через селектор (→ applied/stores.md).
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-store.md — создание нового стора
|
|
||||||
- triggers/develop/create-hook.md — хук-обёртка над стором
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Используется селектор, а не подписка на весь стор
|
|
||||||
- [ ] Выбор локальное/глобальное состояние обоснован
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
title: Создать компонент
|
|
||||||
---
|
|
||||||
|
|
||||||
# Создать компонент
|
|
||||||
|
|
||||||
Инструкция по созданию React-компонента в проекте. Определи слой, сгенерируй из шаблона, реализуй по правилам.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/components.md — правила компонентов: структура файлов, пропсы, документирование
|
|
||||||
- basics/naming.md — именование файла и экспортов
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи слой компонента по его назначению (→ basics/architecture.md):
|
|
||||||
- `shared/ui/` — переиспользуемый UI без бизнес-логики
|
|
||||||
- `features/` — фича с бизнес-логикой
|
|
||||||
- `widgets/` — композиция фичей и сущностей
|
|
||||||
- `entities/` — бизнес-сущность с UI
|
|
||||||
|
|
||||||
2. Сгенерируй модуль из шаблона (→ triggers/develop/generate-module.md).
|
|
||||||
|
|
||||||
3. Реализуй компонент по правилам (→ applied/components.md).
|
|
||||||
|
|
||||||
4. Если нужны стили — см. triggers/develop/style-component.md.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/style-component.md — стилизация компонента
|
|
||||||
- triggers/develop/add-icon.md — добавление иконки в компонент
|
|
||||||
- triggers/develop/generate-module.md — генерация из шаблона
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Компонент создан из шаблона, не вручную
|
|
||||||
- [ ] Файл и экспорт именованы по конвенции (→ basics/naming.md)
|
|
||||||
- [ ] Пропсы типизированы (→ basics/typing.md)
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
title: Создать сущность
|
|
||||||
---
|
|
||||||
|
|
||||||
# Создать сущность
|
|
||||||
|
|
||||||
Инструкция по созданию модуля на слое `entities/`. Сущность — бизнес-объект с UI-представлением и моделью данных.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- basics/architecture.md — слои и зависимости
|
|
||||||
- applied/components.md — правила компонентов
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Сгенерируй модуль из шаблона `entity` (→ triggers/develop/generate-module.md).
|
|
||||||
|
|
||||||
2. Определи модель данных — типы в `model/`.
|
|
||||||
|
|
||||||
3. Реализуй UI-компонент сущности.
|
|
||||||
|
|
||||||
4. Настрой публичный API — экспорт через `index.ts`.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-component.md — UI-компонент сущности
|
|
||||||
- triggers/develop/create-store.md — стор для сущности
|
|
||||||
- triggers/develop/generate-module.md — генерация из шаблона
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Сущность создана из шаблона `entity`
|
|
||||||
- [ ] Модель данных определена — типы в `model/`
|
|
||||||
- [ ] Публичный API настроен — экспорт через `index.ts`
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
title: Создать фичу
|
|
||||||
---
|
|
||||||
|
|
||||||
# Создать фичу
|
|
||||||
|
|
||||||
Инструкция по созданию модуля на слое `features/`. Фича — самодостаточный блок с бизнес-логикой и UI.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- basics/architecture.md — слои и зависимости
|
|
||||||
- applied/components.md — правила компонентов
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Сгенерируй модуль из шаблона `feature` (→ triggers/develop/generate-module.md).
|
|
||||||
|
|
||||||
2. Реализуй компонент фичи (→ applied/components.md).
|
|
||||||
|
|
||||||
3. Если нужен стор — создай в `model/` (→ triggers/develop/create-store.md).
|
|
||||||
|
|
||||||
4. Если нужны хуки — создай в `model/` (→ triggers/develop/create-hook.md).
|
|
||||||
|
|
||||||
5. Настрой публичный API — экспорт через `index.ts`.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-component.md — компонент внутри фичи
|
|
||||||
- triggers/develop/create-store.md — стор для фичи
|
|
||||||
- triggers/develop/create-hook.md — хук для фичи
|
|
||||||
- triggers/develop/generate-module.md — генерация из шаблона
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Фича создана из шаблона `feature`
|
|
||||||
- [ ] Публичный API настроен — экспорт через `index.ts`
|
|
||||||
- [ ] Нет зависимостей от других фичей (→ basics/architecture.md)
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
title: Создать хук
|
|
||||||
---
|
|
||||||
|
|
||||||
# Создать хук
|
|
||||||
|
|
||||||
Инструкция по созданию кастомного React-хука. Определи где он живёт, реализуй по правилам.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/hooks.md — правила хуков
|
|
||||||
- basics/naming.md — именование (префикс `use`)
|
|
||||||
- basics/typing.md — типизация параметров и возврата
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи область хука:
|
|
||||||
- Утилитарный (не привязан к бизнес-логике) → `shared/hooks/`
|
|
||||||
- Привязан к фиче/сущности → `model/` внутри модуля
|
|
||||||
|
|
||||||
2. Создай файл с именем `use-{name}.ts`.
|
|
||||||
|
|
||||||
3. Реализуй хук по правилам (→ applied/hooks.md).
|
|
||||||
|
|
||||||
4. Экспортируй через публичный API модуля.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-component.md — если хук используется в новом компоненте
|
|
||||||
- triggers/develop/connect-store.md — если хук подключает стор
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Имя начинается с `use` (→ basics/naming.md)
|
|
||||||
- [ ] Параметры и возвращаемое значение типизированы
|
|
||||||
- [ ] Хук экспортирован через публичный API модуля
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
title: Создать стор
|
|
||||||
---
|
|
||||||
|
|
||||||
# Создать стор
|
|
||||||
|
|
||||||
Инструкция по созданию стора для управления состоянием. Определи область, сгенерируй из шаблона.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/stores.md — правила сторов
|
|
||||||
- basics/naming.md — именование
|
|
||||||
- basics/typing.md — типизация состояния и экшенов
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи область стора:
|
|
||||||
- Глобальный → `shared/model/`
|
|
||||||
- Привязан к фиче/сущности → `model/` внутри модуля
|
|
||||||
|
|
||||||
2. Сгенерируй из шаблона `store` (→ triggers/develop/generate-module.md).
|
|
||||||
|
|
||||||
3. Реализуй стор по правилам (→ applied/stores.md).
|
|
||||||
|
|
||||||
4. Экспортируй через публичный API модуля.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/connect-store.md — подключение стора к компоненту
|
|
||||||
- triggers/develop/generate-module.md — генерация из шаблона
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Стор создан из шаблона `store`
|
|
||||||
- [ ] Состояние и экшены типизированы
|
|
||||||
- [ ] Стор экспортирован через публичный API модуля
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
title: Создать виджет
|
|
||||||
---
|
|
||||||
|
|
||||||
# Создать виджет
|
|
||||||
|
|
||||||
Инструкция по созданию модуля на слое `widgets/`. Виджет — композиция фичей и сущностей.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- basics/architecture.md — слои и зависимости
|
|
||||||
- applied/components.md — правила компонентов
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Сгенерируй модуль из шаблона `widget` (→ triggers/develop/generate-module.md).
|
|
||||||
|
|
||||||
2. Скомпонуй виджет из существующих фичей и сущностей.
|
|
||||||
|
|
||||||
3. Настрой публичный API — экспорт через `index.ts`.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-feature.md — если нужна новая фича для виджета
|
|
||||||
- triggers/develop/create-component.md — UI-компоненты внутри виджета
|
|
||||||
- triggers/develop/generate-module.md — генерация из шаблона
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Виджет создан из шаблона `widget`
|
|
||||||
- [ ] Композиция из существующих фичей/сущностей, не дублирует логику
|
|
||||||
- [ ] Публичный API настроен — экспорт через `index.ts`
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
title: Сгенерировать модуль из шаблона
|
|
||||||
---
|
|
||||||
|
|
||||||
# Сгенерировать модуль из шаблона
|
|
||||||
|
|
||||||
Инструкция по генерации модуля из шаблонов `.templates/`. Ручное создание файловой структуры запрещено.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/templates-generation.md — шаблоны, синтаксис, инструменты генерации
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи тип модуля и шаблон (→ applied/templates-generation.md):
|
|
||||||
- Компонент → `component`
|
|
||||||
- Фича → `feature`
|
|
||||||
- Виджет → `widget`
|
|
||||||
- Сущность → `entity`
|
|
||||||
- Layout → `layout`
|
|
||||||
- Экран → `screen`
|
|
||||||
- Стор → `store`
|
|
||||||
|
|
||||||
2. Запусти генерацию (→ applied/templates-generation.md).
|
|
||||||
|
|
||||||
3. Если подходящего шаблона нет — сначала создай шаблон, затем генерируй.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-component.md — после генерации компонента
|
|
||||||
- triggers/develop/create-feature.md — после генерации фичи
|
|
||||||
- triggers/develop/create-store.md — после генерации стора
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Модуль создан из шаблона, не вручную
|
|
||||||
- [ ] Выбран правильный шаблон для типа модуля (→ applied/templates-generation.md)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: Настроить VS Code
|
|
||||||
---
|
|
||||||
|
|
||||||
# Настроить VS Code
|
|
||||||
|
|
||||||
Инструкция по настройке VS Code для работы с проектом.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/vscode.md — настройки, расширения, сниппеты
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Установи рекомендованные расширения (→ applied/vscode.md).
|
|
||||||
|
|
||||||
2. Проверь настройки `.vscode/settings.json`.
|
|
||||||
|
|
||||||
3. Настрой сниппеты при необходимости.
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Рекомендованные расширения установлены
|
|
||||||
- [ ] Настройки `.vscode/settings.json` проверены
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
title: Стилизовать компонент
|
|
||||||
---
|
|
||||||
|
|
||||||
# Стилизовать компонент
|
|
||||||
|
|
||||||
Инструкция по выбору подхода к стилизации и написанию стилей для компонента.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/styles.md — правила CSS: PostCSS Modules, токены, медиа-запросы
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи подход (→ applied/styles.md):
|
|
||||||
- Mantine-компонент → используй пропсы Mantine, не пиши CSS
|
|
||||||
- CSS-токены достаточно → используй токены
|
|
||||||
- Нужна кастомная стилизация → PostCSS Modules
|
|
||||||
|
|
||||||
2. Создай файл стилей `{component-name}.module.css` рядом с компонентом.
|
|
||||||
|
|
||||||
3. Напиши стили по правилам (→ applied/styles.md).
|
|
||||||
|
|
||||||
4. Подключи стили в компоненте через `cl()`.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/create-component.md — если компонент ещё не создан
|
|
||||||
- triggers/develop/add-icon.md — если нужна иконка в компоненте
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Приоритет стилизации соблюдён: Mantine → токены → PostCSS Modules
|
|
||||||
- [ ] Нет инлайн-стилей и магических значений
|
|
||||||
- [ ] Файл стилей именован `{component-name}.module.css`
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
# Стайлгайд — Разработка
|
|
||||||
|
|
||||||
Правила и стандарты разработки на Next.js и TypeScript.
|
|
||||||
|
|
||||||
## Как работать
|
|
||||||
|
|
||||||
1. **Изучи обязательные правила** (таблица ниже) — они действуют при любой задаче.
|
|
||||||
2. Найди задачу в таблицах триггеров → открой триггер.
|
|
||||||
3. Триггер укажет какие прикладные разделы прочитать и какие шаги выполнить.
|
|
||||||
4. Перед каждой подзадачей возвращайся к триггерам — проверяй, нет ли готового.
|
|
||||||
5. Если триггера нет — ищи прикладной раздел по области задачи.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Обязательные правила
|
|
||||||
|
|
||||||
Прочитай эти разделы **до начала работы**. Соблюдай при написании любого кода.
|
|
||||||
|
|
||||||
| Раздел | Файл | Что внутри |
|
|
||||||
|--------|------|------------|
|
|
||||||
| Структура проекта | applied/project-structure.md | Организация папок и файлов |
|
|
||||||
| Архитектура | basics/architecture.md | SLM Design: слои, модули, сегменты |
|
|
||||||
| Стиль кода | basics/code-style.md | Форматирование, импорты, отступы |
|
|
||||||
| Именование | basics/naming.md | Имена файлов, переменных, событий |
|
|
||||||
| Типизация | basics/typing.md | type vs interface, generic, any/unknown |
|
|
||||||
| Документирование | basics/documentation.md | JSDoc для функций, компонентов, типов |
|
|
||||||
| Технологии | basics/tech-stack.md | Допустимые библиотеки и зависимости |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Прикладные разделы
|
|
||||||
|
|
||||||
Справочник по областям. Читай тот раздел, который относится к текущей задаче.
|
|
||||||
|
|
||||||
| Область | Файл | Когда читать |
|
|
||||||
|---------|------|--------------|
|
|
||||||
| Компоненты | applied/components.md | Создание или редактирование React-компонентов |
|
|
||||||
| Стили | applied/styles.md | CSS Modules, PostCSS, переменные, медиа-запросы |
|
|
||||||
| Файлы роутинга | applied/page-level.md | page.tsx, layout.tsx, error.tsx, not-found.tsx |
|
|
||||||
| Шаблоны и генерация | applied/templates-generation.md | Генерация кода из шаблонов |
|
|
||||||
| Настройка VS Code | applied/vscode.md | Расширения, settings.json, сниппеты |
|
|
||||||
| SVG-спрайты | applied/svg-sprites.md | Работа с SVG-иконками и спрайтами |
|
|
||||||
| Хуки | applied/hooks.md | Создание и использование кастомных хуков *(в разработке)* |
|
|
||||||
| Сторы | applied/stores.md | Глобальное состояние, Zustand *(в разработке)* |
|
|
||||||
| API | applied/api.md | Запросы, клиенты, обработка ответов *(в разработке)* |
|
|
||||||
| Локализация | applied/localization.md | i18next, переводы *(в разработке)* |
|
|
||||||
| Изображения | applied/images-sprites.md | Подключение и оптимизация изображений *(в разработке)* |
|
|
||||||
| Шрифты | applied/fonts.md | Подключение и настройка шрифтов *(в разработке)* |
|
|
||||||
| Видео | applied/video.md | Встраивание видео *(в разработке)* |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Триггеры
|
|
||||||
|
|
||||||
Пошаговые инструкции. Найди задачу → открой триггер → выполняй по шагам.
|
|
||||||
|
|
||||||
### Создание
|
|
||||||
|
|
||||||
| Задача | Триггер | Описание |
|
|
||||||
|--------|---------|----------|
|
|
||||||
| Создать компонент | triggers/develop/create-component.md | Переиспользуемый UI-элемент без бизнес-логики |
|
|
||||||
| Создать фичу | triggers/develop/create-feature.md | Самодостаточный блок с бизнес-логикой и UI |
|
|
||||||
| Создать виджет | triggers/develop/create-widget.md | Композиция нескольких фичей и сущностей |
|
|
||||||
| Создать сущность | triggers/develop/create-entity.md | Бизнес-объект с моделью данных и UI-представлением |
|
|
||||||
| Создать хук | triggers/develop/create-hook.md | Кастомный React-хук с переиспользуемой логикой |
|
|
||||||
| Создать стор | triggers/develop/create-store.md | Глобальное или модульное состояние через Zustand |
|
|
||||||
| Создать страницу | triggers/develop/create-page.md | Новый route в Next.js — экран + page.tsx |
|
|
||||||
| Создать layout | triggers/develop/create-layout.md | Общая обёртка layout.tsx для группы страниц |
|
|
||||||
| Создать проект | triggers/develop/create-project.md | Инициализация нового проекта из шаблона |
|
|
||||||
| Сгенерировать модуль | triggers/develop/generate-module.md | Создание модуля из шаблонов `.templates/` |
|
|
||||||
|
|
||||||
### Стилизация и ресурсы
|
|
||||||
|
|
||||||
| Задача | Триггер | Описание |
|
|
||||||
|--------|---------|----------|
|
|
||||||
| Стилизовать компонент | triggers/develop/style-component.md | Выбор подхода и написание CSS для компонента |
|
|
||||||
| Добавить иконку | triggers/develop/add-icon.md | SVG-иконка через спрайт-систему |
|
|
||||||
| Добавить изображение | triggers/develop/add-image.md | Растровое изображение (png, jpg, webp) |
|
|
||||||
| Добавить видео | triggers/develop/add-video.md | Встраивание видео на страницу |
|
|
||||||
| Подключить шрифт | triggers/develop/add-font.md | Подключение нового шрифта в проект |
|
|
||||||
|
|
||||||
### Данные и состояние
|
|
||||||
|
|
||||||
| Задача | Триггер | Описание |
|
|
||||||
|--------|---------|----------|
|
|
||||||
| Добавить API-запрос | triggers/develop/add-api-request.md | Клиентский запрос данных через SWR |
|
|
||||||
| Подключить стор | triggers/develop/connect-store.md | Подключение существующего стора к компоненту |
|
|
||||||
| Серверные данные (RSC) | triggers/develop/add-server-data.md | Получение данных в серверных компонентах |
|
|
||||||
|
|
||||||
### Инфраструктура
|
|
||||||
|
|
||||||
| Задача | Триггер | Описание |
|
|
||||||
|--------|---------|----------|
|
|
||||||
| Добавить перевод | triggers/develop/add-localization.md | Ключи перевода и подключение i18next |
|
|
||||||
| Добавить зависимость | triggers/develop/add-dependency.md | Подключение новой npm-библиотеки |
|
|
||||||
| Настроить VS Code | triggers/develop/setup-vscode.md | Расширения, настройки редактора, сниппеты |
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Стайлгайд — Ревью
|
|
||||||
|
|
||||||
Правила проверки кода на Next.js и TypeScript. Читай перед ревью PR.
|
|
||||||
|
|
||||||
## Как пользоваться
|
|
||||||
|
|
||||||
1. Определи какие области затронуты в PR.
|
|
||||||
2. Открой соответствующие разделы из таблиц ниже.
|
|
||||||
3. Проверь код на соответствие правилам.
|
|
||||||
|
|
||||||
## Что проверять всегда
|
|
||||||
|
|
||||||
Базовые правила — применимы к любому коду в PR.
|
|
||||||
|
|
||||||
| Что проверить | Раздел |
|
|
||||||
|---------------|--------|
|
|
||||||
| Форматирование, импорты, early return | basics/code-style.md |
|
|
||||||
| Имена файлов, переменных, компонентов | basics/naming.md |
|
|
||||||
| Нет any, правильный type/interface | basics/typing.md |
|
|
||||||
| JSDoc на публичных функциях | basics/documentation.md |
|
|
||||||
| Правильный слой, нет запрещённых зависимостей | basics/architecture.md |
|
|
||||||
| Нет неразрешённых библиотек | basics/tech-stack.md |
|
|
||||||
|
|
||||||
## Что проверять по области
|
|
||||||
|
|
||||||
Открой раздел, если PR затрагивает соответствующую область.
|
|
||||||
|
|
||||||
| Область | Раздел |
|
|
||||||
|---------|--------|
|
|
||||||
| React-компоненты | applied/components.md |
|
|
||||||
| CSS / стили | applied/styles.md |
|
|
||||||
| Шаблоны генерации | applied/templates-generation.md |
|
|
||||||
| Хуки | applied/hooks.md |
|
|
||||||
| Сторы | applied/stores.md |
|
|
||||||
| API-слой | applied/api.md |
|
|
||||||
| Шрифты | applied/fonts.md |
|
|
||||||
| Переводы | applied/localization.md |
|
|
||||||
| Изображения | applied/images-sprites.md |
|
|
||||||
| SVG-иконки | applied/svg-sprites.md |
|
|
||||||
| Видео | applied/video.md |
|
|
||||||
| Настройки VS Code | applied/vscode.md |
|
|
||||||
| page.tsx, layout.tsx | applied/page-level.md |
|
|
||||||
| Структура папок | applied/project-structure.md |
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
---
|
|
||||||
title: Добавить серверные данные
|
|
||||||
---
|
|
||||||
|
|
||||||
# Добавить серверные данные
|
|
||||||
|
|
||||||
Инструкция по получению данных в серверных компонентах (RSC) Next.js.
|
|
||||||
|
|
||||||
## Прочитай перед началом
|
|
||||||
|
|
||||||
- applied/page-level.md — серверные компоненты в App Router
|
|
||||||
- applied/api.md — API-клиенты
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Определи где получать данные:
|
|
||||||
- В `page.tsx` / `layout.tsx` → серверный fetch
|
|
||||||
- В клиентском компоненте → SWR (→ triggers/develop/add-api-request.md)
|
|
||||||
|
|
||||||
2. Создай или расширь серверный API-клиент.
|
|
||||||
|
|
||||||
3. Получи данные в серверном компоненте и передай через пропсы.
|
|
||||||
|
|
||||||
## Смежные триггеры
|
|
||||||
|
|
||||||
- triggers/develop/add-api-request.md — клиентские запросы (SWR)
|
|
||||||
- triggers/develop/create-page.md — серверный fetch в page.tsx
|
|
||||||
|
|
||||||
## Проверь себя
|
|
||||||
|
|
||||||
- [ ] Определён тип: серверный fetch или клиентский SWR
|
|
||||||
- [ ] Типы запроса и ответа описаны
|
|
||||||
- [ ] Данные передаются через пропсы, не через глобальное состояние
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user