Compare commits
14 Commits
4ae6ac893d
...
v3
| Author | SHA1 | Date | |
|---|---|---|---|
| 99c0995cb6 | |||
| d621e6b57d | |||
| 787223010f | |||
| f5732904f4 | |||
| 36304c14f0 | |||
| 436c87a986 | |||
| be3d86f198 | |||
| 073fc6507f | |||
| cba311d78e | |||
| 11f9b702e0 | |||
| f7d3506a91 | |||
| be8e89fccd | |||
| fbac3e1a55 | |||
| 6ccc4a0d06 |
@@ -20,6 +20,9 @@ jobs:
|
|||||||
echo "DOCKER_REGISTRY=$DOCKER_REGISTRY" >> $GITHUB_ENV
|
echo "DOCKER_REGISTRY=$DOCKER_REGISTRY" >> $GITHUB_ENV
|
||||||
REGISTRY_IMAGE="$DOCKER_REGISTRY/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
|
REGISTRY_IMAGE="$DOCKER_REGISTRY/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
|
||||||
echo "REGISTRY_IMAGE=$REGISTRY_IMAGE" >> $GITHUB_ENV
|
echo "REGISTRY_IMAGE=$REGISTRY_IMAGE" >> $GITHUB_ENV
|
||||||
|
# Версия сборки: тег если есть, иначе короткий SHA
|
||||||
|
BUILD_VERSION=$(git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD)
|
||||||
|
echo "BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Login to Container Registry
|
- name: Login to Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -47,6 +50,8 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
BUILD_VERSION=${{ env.BUILD_VERSION }}
|
||||||
provenance: false
|
provenance: false
|
||||||
sbom: false
|
sbom: false
|
||||||
|
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -135,3 +135,7 @@ dist
|
|||||||
.vitepress/cache
|
.vitepress/cache
|
||||||
.vitepress/dist
|
.vitepress/dist
|
||||||
docs/.vitepress
|
docs/.vitepress
|
||||||
|
|
||||||
|
# Генерируется через `npm run llms`
|
||||||
|
docs/public/
|
||||||
|
generated/
|
||||||
@@ -3,36 +3,45 @@ import { defineConfig } from 'vitepress';
|
|||||||
const ruSidebar = [
|
const ruSidebar = [
|
||||||
{
|
{
|
||||||
text: 'Workflow',
|
text: 'Workflow',
|
||||||
link: '/workflow',
|
link: '/ru/workflow',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Базовые правила',
|
text: 'Базовые правила',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Технологии и библиотеки', link: '/basics/tech-stack' },
|
{ text: 'Технологии и библиотеки', link: '/ru/basics/tech-stack' },
|
||||||
{ text: 'Именование', link: '/basics/naming' },
|
{ text: 'Именование', link: '/ru/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: '/ru/basics/architecture/' },
|
||||||
|
{ text: 'Слои', link: '/ru/basics/architecture/reference/layers' },
|
||||||
|
{ text: 'Модули', link: '/ru/basics/architecture/reference/modules' },
|
||||||
|
{ text: 'Сегменты', link: '/ru/basics/architecture/reference/segments' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ text: 'Стиль кода', link: '/ru/basics/code-style' },
|
||||||
|
{ text: 'Документирование', link: '/ru/basics/documentation' },
|
||||||
|
{ text: 'Типизация', link: '/ru/basics/typing' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Прикладные разделы',
|
text: 'Прикладные разделы',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Структура проекта', link: '/applied/project-structure' },
|
{ text: 'Структура проекта', link: '/ru/applied/project-structure' },
|
||||||
{ text: 'Компоненты', link: '/applied/components' },
|
{ text: 'Компоненты', link: '/ru/applied/components' },
|
||||||
{ text: 'Страницы (App Router)', link: '/applied/page-level' },
|
{ text: 'Страницы (App Router)', link: '/ru/applied/page-level' },
|
||||||
{ text: 'Шаблоны и генерация кода', link: '/applied/templates-generation' },
|
{ text: 'Шаблоны и генерация кода', link: '/ru/applied/templates-generation' },
|
||||||
{ text: 'Стили', link: '/applied/styles' },
|
{ text: 'Стили', link: '/ru/applied/styles' },
|
||||||
{ text: 'Изображения', link: '/applied/images-sprites' },
|
{ text: 'Изображения', link: '/ru/applied/images-sprites' },
|
||||||
{ text: 'SVG-спрайты', link: '/applied/svg-sprites' },
|
{ text: 'SVG-спрайты', link: '/ru/applied/svg-sprites' },
|
||||||
{ text: 'Видео', link: '/applied/video' },
|
{ text: 'Видео', link: '/ru/applied/video' },
|
||||||
{ text: 'API', link: '/applied/api' },
|
{ text: 'API', link: '/ru/applied/api' },
|
||||||
{ text: 'Stores', link: '/applied/stores' },
|
{ text: 'Stores', link: '/ru/applied/stores' },
|
||||||
{ text: 'Хуки', link: '/applied/hooks' },
|
{ text: 'Хуки', link: '/ru/applied/hooks' },
|
||||||
{ text: 'Шрифты', link: '/applied/fonts' },
|
{ text: 'Шрифты', link: '/ru/applied/fonts' },
|
||||||
{ text: 'Локализация', link: '/applied/localization' },
|
{ text: 'Локализация', link: '/ru/applied/localization' },
|
||||||
{ text: 'Настройка VS Code', link: '/applied/vscode' },
|
{ text: 'Настройка VS Code', link: '/ru/applied/vscode' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -83,30 +92,71 @@ const enSidebar = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vite-плагин: отдаёт `.txt` и `.md` с явной кодировкой UTF-8.
|
||||||
|
* Без этого браузер декодирует как ISO-8859-1 и кириллица ломается.
|
||||||
|
*/
|
||||||
|
const utf8TextPlugin = {
|
||||||
|
name: 'utf8-text-files',
|
||||||
|
configureServer(server: any) {
|
||||||
|
server.middlewares.use((req: any, res: any, next: any) => {
|
||||||
|
const url: string = req.url || '';
|
||||||
|
if (url.endsWith('.txt') || url.endsWith('.md')) {
|
||||||
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
srcDir: 'docs',
|
srcDir: 'docs',
|
||||||
|
// `docs/public/` содержит сгенерированные `.md`-копии и `llms.txt` для LLM
|
||||||
|
// (попадают в корень `dist/` как статика). Исключаем из сканирования
|
||||||
|
// страниц, иначе VitePress рендерит их как HTML-страницы.
|
||||||
|
srcExclude: ['public/**'],
|
||||||
title: 'NextJS Style Guide',
|
title: 'NextJS Style Guide',
|
||||||
description: 'Правила и стандарты разработки на NextJS и TypeScript',
|
description: 'Правила и стандарты разработки на NextJS и TypeScript',
|
||||||
|
|
||||||
rewrites: {
|
vite: {
|
||||||
'ru/:rest*': ':rest*',
|
plugins: [utf8TextPlugin],
|
||||||
|
define: {
|
||||||
|
__BUILD_VERSION__: JSON.stringify(process.env.BUILD_VERSION || 'dev'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
locales: {
|
locales: {
|
||||||
root: {
|
root: {
|
||||||
|
label: 'Languages',
|
||||||
|
lang: 'en',
|
||||||
|
},
|
||||||
|
ru: {
|
||||||
label: 'Русский',
|
label: 'Русский',
|
||||||
lang: 'ru-RU',
|
lang: 'ru-RU',
|
||||||
|
link: '/ru/',
|
||||||
|
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
sidebar: ruSidebar,
|
sidebar: ruSidebar,
|
||||||
},
|
},
|
||||||
|
// Расширенный блок описания для llms.txt — даёт LLM полный
|
||||||
|
// технический контекст: стек, методология, охват тем.
|
||||||
|
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.',
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
label: 'English',
|
label: 'English',
|
||||||
lang: 'en-US',
|
lang: 'en-US',
|
||||||
link: '/en/',
|
link: '/en/',
|
||||||
|
description: 'Next.js + TypeScript development standards with SLM architecture',
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
sidebar: enSidebar,
|
sidebar: enSidebar,
|
||||||
},
|
},
|
||||||
|
llmsBlockquote:
|
||||||
|
'Frontend development standards for Next.js (App Router) + TypeScript + React projects with SLM architecture (Scoped Layered Module Design — a modular architecture with responsibility layers, where each module contains everything it needs: components, hooks, stores, types, styles).',
|
||||||
|
llmsContext:
|
||||||
|
'Stack: React, TypeScript, Next.js App Router, Mantine UI, SWR, Zustand, i18next, PostCSS Modules, Vitest, clsx.\n\nThe documentation covers SLM architecture (layers, modules, segments, dependency direction, public API), code conventions (naming, formatting, imports, typing, JSDoc), component and hook implementation, App Router usage, code generation from templates, styling (Mobile First, design tokens), API and socket integration, state management via Zustand, localization, assets (fonts, images, SVG sprites), and VS Code setup.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
При работе с документацией следовать правилам из CONTRIBUTING.md.
|
При работе с документацией следовать правилам из CONTRIBUTING.md.
|
||||||
|
|
||||||
- Язык документации и коммитов — русский.
|
- Язык документации и коммитов — русский.
|
||||||
- После изменений в `.md`-файлах — запустить `npm run docs` для обновления RULES.md.
|
- После изменений в `.md`-файлах — запустить `npm run llms` для обновления `llms.txt` и README.
|
||||||
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`)
|
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`):
|
||||||
и порядок файлов (`concat-md.js`).
|
он же является источником порядка и группировки для `llms.txt`.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|---------|-----------|
|
|---------|-----------|
|
||||||
| `npm run dev` | Локальный сервер разработки |
|
| `npm run dev` | Локальный сервер разработки |
|
||||||
| `npm run build` | Сборка статического сайта |
|
| `npm run build` | Сборка статического сайта |
|
||||||
| `npm run docs` | Генерация `generated/{lang}/RULES.md` — единый файл для AI-ассистентов |
|
| `npm run llms` | Генерация `generated/{lang}/llms.txt` (карта документации для LLM) и README |
|
||||||
|
|
||||||
## Структура файлов
|
## Структура файлов
|
||||||
|
|
||||||
@@ -51,16 +51,17 @@ docs/
|
|||||||
.vitepress/
|
.vitepress/
|
||||||
├── config.ts # Конфигурация VitePress, сайдбары, локали
|
├── config.ts # Конфигурация VitePress, сайдбары, локали
|
||||||
generated/
|
generated/
|
||||||
├── ru/RULES.md # Сгенерированный единый файл (ru)
|
├── ru/llms.txt # Карта документации для LLM (ru, llmstxt.org)
|
||||||
└── en/RULES.md # Сгенерированный единый файл (en)
|
└── en/llms.txt # Карта документации для LLM (en, llmstxt.org)
|
||||||
concat-md.js # Скрипт генерации RULES.md
|
generate-llms.ts # Скрипт генерации llms.txt и README
|
||||||
```
|
```
|
||||||
|
|
||||||
### Добавление нового раздела
|
### Добавление нового раздела
|
||||||
|
|
||||||
1. Создать `.md`-файл в нужной папке (`basics/` или `applied/`).
|
1. Создать `.md`-файл в нужной папке (`basics/` или `applied/`).
|
||||||
2. Добавить пункт в сайдбар — `.vitepress/config.ts` (оба языка, если есть перевод).
|
2. Добавить пункт в сайдбар — `.vitepress/config.ts` (оба языка, если есть перевод).
|
||||||
3. Добавить файл в массив `fileOrder` — `concat-md.js` (для генерации RULES.md).
|
Сайдбар — единственный источник порядка и группировки для `llms.txt`.
|
||||||
|
3. Запустить `npm run llms` для обновления `generated/{lang}/llms.txt`.
|
||||||
|
|
||||||
## Два типа документации
|
## Два типа документации
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
:8080 {
|
:8080 {
|
||||||
root * /srv
|
root * /srv
|
||||||
|
|
||||||
|
# Кириллица в .txt и .md ломается без явного charset
|
||||||
|
@text path *.txt *.md
|
||||||
|
header @text Content-Type "text/plain; charset=utf-8"
|
||||||
|
|
||||||
file_server
|
file_server
|
||||||
try_files {path} /index.html
|
try_files {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-{lang}.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
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
||||||
| Naming | How to name files, variables, components, hooks? |
|
| Naming | How to name files, variables, components, hooks? |
|
||||||
| Documentation | How to write JSDoc: what to document and what not? |
|
| Documentation | How to write JSDoc: what to document and what not? |
|
||||||
| Typing | How to type: type vs interface, any/unknown, FC? |
|
| Typing | How to type: type vs interface, any/unknown? |
|
||||||
|
|
||||||
### Applied Sections
|
### Applied Sections
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
| Section | Answers the question |
|
| Section | Answers the question |
|
||||||
|---------|---------------------|
|
|---------|---------------------|
|
||||||
| Project Structure | How are folders and files organized by FSD? |
|
| Project Structure | How are folders and files organized by FSD? |
|
||||||
| Components | How is a component structured: files, props, clsx, FC? |
|
| Components | How is a component structured: files, props, clsx? |
|
||||||
| Page-level Components | How to define layout, page, loading, error, not-found? |
|
| Page-level Components | How to define layout, page, loading, error, not-found? |
|
||||||
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
|
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
|
||||||
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
|
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
|
||||||
@@ -54,4 +54,5 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
|
|
||||||
## For Assistants
|
## For Assistants
|
||||||
|
|
||||||
Full documentation in a single MD file: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/en/RULES.md
|
Documentation map with links to all sections ([llmstxt.org](https://llmstxt.org) format):
|
||||||
|
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/en/llms.txt
|
||||||
|
|||||||
13
README_RU.md
13
README_RU.md
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
## Для ассистентов
|
## Для ассистентов
|
||||||
|
|
||||||
Полная документация в одном MD файле: https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/RULES.md
|
Карта документации со ссылками на все разделы (формат [llmstxt.org](https://llmstxt.org)):
|
||||||
|
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/llms.txt
|
||||||
|
|
||||||
## Структура документации
|
## Структура документации
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
| Создание проекта | Как начать новый проект? |
|
| Создание проекта | Как начать новый проект? |
|
||||||
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
|
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
|
||||||
| Добавление страницы | Как добавить новую страницу в проект? |
|
| Добавление страницы | Как добавить новую страницу в проект? |
|
||||||
| Добавление UI-модуля | Как создать компонент, фичу, виджет, сущность или layout? |
|
| Добавление UI-модуля | Как создать компонент, бизнес-модуль, виджет или layout? |
|
||||||
| Стилизация | Как стилизовать компоненты в проекте? |
|
| Стилизация | Как стилизовать компоненты в проекте? |
|
||||||
| Получение данных | Как получать данные с сервера? |
|
| Получение данных | Как получать данные с сервера? |
|
||||||
| Управление состоянием | Как работать с состоянием? |
|
| Управление состоянием | Как работать с состоянием? |
|
||||||
@@ -31,11 +32,11 @@
|
|||||||
| Раздел | Отвечает на вопрос |
|
| Раздел | Отвечает на вопрос |
|
||||||
|--------|-------------------|
|
|--------|-------------------|
|
||||||
| Технологии и библиотеки | Какой стек используем? |
|
| Технологии и библиотеки | Какой стек используем? |
|
||||||
| Архитектура | Как устроены слои FSD, зависимости, публичный API? |
|
| Архитектура | Как устроены слои SLM, зависимости, публичный API? |
|
||||||
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
||||||
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
||||||
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||||
| Типизация | Как типизировать: type vs interface, any/unknown, FC? |
|
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||||
|
|
||||||
### Прикладные разделы
|
### Прикладные разделы
|
||||||
|
|
||||||
@@ -44,8 +45,8 @@
|
|||||||
| Раздел | Отвечает на вопрос |
|
| Раздел | Отвечает на вопрос |
|
||||||
|--------|-------------------|
|
|--------|-------------------|
|
||||||
| Настройка VS Code | Как настроить редактор для проекта? |
|
| Настройка VS Code | Как настроить редактор для проекта? |
|
||||||
| Структура проекта | Как организованы папки и файлы по FSD? |
|
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||||
| Компоненты | Как устроен компонент: файлы, пропсы, clsx, FC? |
|
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||||
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
|
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
|
||||||
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||||
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
||||||
|
|||||||
107
concat-md.js
107
concat-md.js
@@ -1,107 +0,0 @@
|
|||||||
import path from "path";
|
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
// Явный порядок файлов внутри каждого языка
|
|
||||||
const fileOrder = [
|
|
||||||
// index
|
|
||||||
"index.md",
|
|
||||||
// workflow
|
|
||||||
"workflow.md",
|
|
||||||
// basics
|
|
||||||
"basics/tech-stack.md",
|
|
||||||
"basics/naming.md",
|
|
||||||
"basics/architecture.md",
|
|
||||||
"basics/code-style.md",
|
|
||||||
"basics/documentation.md",
|
|
||||||
"basics/typing.md",
|
|
||||||
// applied
|
|
||||||
"applied/project-structure.md",
|
|
||||||
"applied/components.md",
|
|
||||||
"applied/page-level.md",
|
|
||||||
"applied/templates-generation.md",
|
|
||||||
"applied/styles.md",
|
|
||||||
"applied/images-sprites.md",
|
|
||||||
"applied/svg-sprites.md",
|
|
||||||
"applied/video.md",
|
|
||||||
"applied/api.md",
|
|
||||||
"applied/stores.md",
|
|
||||||
"applied/hooks.md",
|
|
||||||
"applied/fonts.md",
|
|
||||||
"applied/localization.md",
|
|
||||||
"applied/vscode.md",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Удалить frontmatter из содержимого md-файла
|
|
||||||
const stripFrontmatter = (content) =>
|
|
||||||
content.replace(/^---[\s\S]*?---\n*/m, "");
|
|
||||||
|
|
||||||
// Сдвинуть уровень заголовков на 1 вниз (h1→h2, h2→h3, ...)
|
|
||||||
// Не трогает заголовки внутри блоков кода
|
|
||||||
const shiftHeadings = (content) => {
|
|
||||||
const lines = content.split("\n");
|
|
||||||
let inCodeBlock = false;
|
|
||||||
|
|
||||||
return lines
|
|
||||||
.map((line) => {
|
|
||||||
if (line.startsWith("```")) inCodeBlock = !inCodeBlock;
|
|
||||||
if (inCodeBlock) return line;
|
|
||||||
if (/^#{1,5}\s/.test(line)) return "#" + line;
|
|
||||||
return line;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Собрать RULES.md с мета-якорями для каждого файла
|
|
||||||
const buildRules = (lang) => {
|
|
||||||
const srcDir = `./docs/${lang}`;
|
|
||||||
const outDir = `./generated/${lang}`;
|
|
||||||
const outFile = path.join(outDir, "RULES.md");
|
|
||||||
|
|
||||||
if (!fs.existsSync(srcDir)) {
|
|
||||||
console.log(`Пропуск ${lang}: папка ${srcDir} не найдена`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mkdirSync(outDir, { recursive: true });
|
|
||||||
|
|
||||||
const parts = [];
|
|
||||||
|
|
||||||
for (const file of fileOrder) {
|
|
||||||
const filePath = path.join(srcDir, file);
|
|
||||||
if (!fs.existsSync(filePath)) continue;
|
|
||||||
|
|
||||||
const raw = fs.readFileSync(filePath, "utf8");
|
|
||||||
const content = stripFrontmatter(raw).trim();
|
|
||||||
if (!content) continue;
|
|
||||||
|
|
||||||
// Мета-якорь: путь VitePress без расширения
|
|
||||||
const route = "/" + file.replace(/\.md$/, "");
|
|
||||||
// index.md остаётся без сдвига (его h1 — главный заголовок документа)
|
|
||||||
const processed = file === "index.md" ? content : shiftHeadings(content);
|
|
||||||
parts.push(`<!-- ${route} -->\n${processed}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(outFile, parts.join("\n\n"), "utf8");
|
|
||||||
console.log(`RULES.md (${lang}) создан: ${outFile}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Собираем RULES.md для обоих языков
|
|
||||||
buildRules("ru");
|
|
||||||
buildRules("en");
|
|
||||||
|
|
||||||
// Генерируем README из index.md
|
|
||||||
const buildReadme = (lang, outFile) => {
|
|
||||||
const indexPath = `./docs/${lang}/index.md`;
|
|
||||||
|
|
||||||
if (!fs.existsSync(indexPath)) {
|
|
||||||
console.log(`Пропуск README (${lang}): ${indexPath} не найден`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = stripFrontmatter(fs.readFileSync(indexPath, "utf8"));
|
|
||||||
fs.writeFileSync(outFile, content, "utf8");
|
|
||||||
console.log(`${outFile} создан из ${indexPath}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
buildReadme("en", "./README.md");
|
|
||||||
buildReadme("ru", "./README_RU.md");
|
|
||||||
@@ -30,7 +30,7 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
||||||
| Naming | How to name files, variables, components, hooks? |
|
| Naming | How to name files, variables, components, hooks? |
|
||||||
| Documentation | How to write JSDoc: what to document and what not? |
|
| Documentation | How to write JSDoc: what to document and what not? |
|
||||||
| Typing | How to type: type vs interface, any/unknown, FC? |
|
| Typing | How to type: type vs interface, any/unknown? |
|
||||||
|
|
||||||
### Applied Sections
|
### Applied Sections
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
| Section | Answers the question |
|
| Section | Answers the question |
|
||||||
|---------|---------------------|
|
|---------|---------------------|
|
||||||
| Project Structure | How are folders and files organized by FSD? |
|
| Project Structure | How are folders and files organized by FSD? |
|
||||||
| Components | How is a component structured: files, props, clsx, FC? |
|
| Components | How is a component structured: files, props, clsx? |
|
||||||
| Page-level Components | How to define layout, page, loading, error, not-found? |
|
| Page-level Components | How to define layout, page, loading, error, not-found? |
|
||||||
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
|
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
|
||||||
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
|
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
|
||||||
@@ -54,4 +54,5 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
|
|
||||||
## For Assistants
|
## For Assistants
|
||||||
|
|
||||||
Full documentation in a single MD file: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/en/RULES.md
|
Documentation map with links to all sections ([llmstxt.org](https://llmstxt.org) format):
|
||||||
|
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/en/llms.txt
|
||||||
|
|||||||
372
docs/index.md
Normal file
372
docs/index.md
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
---
|
||||||
|
layout: false
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'nsg-landing-lang'
|
||||||
|
const THEME_KEY = 'vitepress-theme-appearance'
|
||||||
|
|
||||||
|
// __BUILD_VERSION__ подставляется Vite-define из ENV `BUILD_VERSION`
|
||||||
|
// (см. .vitepress/config.ts). В dev и build всегда определена.
|
||||||
|
const buildVersion = __BUILD_VERSION__
|
||||||
|
|
||||||
|
const dict = {
|
||||||
|
ru: {
|
||||||
|
tagline: 'Готовые соглашения по архитектуре, коду, компонентам и инфраструктуре для Next.js + TypeScript-проектов — чтобы команда писала одинаково, а новые разработчики включались в проект быстрее.',
|
||||||
|
langLabel: 'Язык',
|
||||||
|
themeLabel: 'Тема',
|
||||||
|
themes: { auto: 'Авто', light: 'Светлая', dark: 'Тёмная' },
|
||||||
|
cards: {
|
||||||
|
docs: {
|
||||||
|
title: 'Документация',
|
||||||
|
desc: 'Все разделы: процессы разработки, базовые правила, прикладные руководства.',
|
||||||
|
href: './ru/',
|
||||||
|
cta: 'Открыть',
|
||||||
|
},
|
||||||
|
ai: {
|
||||||
|
title: 'Ассистенту',
|
||||||
|
desc: 'Карта документации в формате llms.txt для AI-агентов.',
|
||||||
|
href: './ru/llms.txt',
|
||||||
|
cta: 'Открыть',
|
||||||
|
},
|
||||||
|
zip: {
|
||||||
|
title: 'Скачать правила',
|
||||||
|
desc: 'Архив всех Markdown-файлов одним ZIP.',
|
||||||
|
href: './nextjs-style-guide-ru.zip',
|
||||||
|
cta: 'Скачать',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
tagline: 'Ready-made standards for architecture, code, components, and infrastructure in Next.js + TypeScript projects — so your team writes consistently and new developers ramp up faster.',
|
||||||
|
langLabel: 'Language',
|
||||||
|
themeLabel: 'Theme',
|
||||||
|
themes: { auto: 'Auto', light: 'Light', dark: 'Dark' },
|
||||||
|
cards: {
|
||||||
|
docs: {
|
||||||
|
title: 'Documentation',
|
||||||
|
desc: 'All sections: development processes, basic rules, applied guides.',
|
||||||
|
href: '#',
|
||||||
|
cta: 'Open',
|
||||||
|
badge: 'in development',
|
||||||
|
},
|
||||||
|
ai: {
|
||||||
|
title: 'For Assistant',
|
||||||
|
desc: 'Documentation map in llms.txt format for AI agents.',
|
||||||
|
href: '#',
|
||||||
|
cta: 'Open',
|
||||||
|
badge: 'in development',
|
||||||
|
},
|
||||||
|
zip: {
|
||||||
|
title: 'Download rules',
|
||||||
|
desc: 'Archive of all Markdown files and llms.txt in a single ZIP.',
|
||||||
|
href: '#',
|
||||||
|
cta: 'Download',
|
||||||
|
badge: 'soon',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const lang = ref('ru')
|
||||||
|
const theme = ref('auto')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const savedLang = localStorage.getItem(STORAGE_KEY)
|
||||||
|
if (savedLang === 'ru' || savedLang === 'en') {
|
||||||
|
lang.value = savedLang
|
||||||
|
} else {
|
||||||
|
const nav = (navigator.language || 'ru').toLowerCase()
|
||||||
|
lang.value = nav.startsWith('ru') ? 'ru' : 'en'
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedTheme = localStorage.getItem(THEME_KEY)
|
||||||
|
theme.value = savedTheme === 'dark' || savedTheme === 'light' ? savedTheme : 'auto'
|
||||||
|
})
|
||||||
|
|
||||||
|
const t = computed(() => dict[lang.value])
|
||||||
|
|
||||||
|
function setLang(value) {
|
||||||
|
lang.value = value
|
||||||
|
localStorage.setItem(STORAGE_KEY, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
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">{{ t.tagline }}</p>
|
||||||
|
<div class="landing__controls">
|
||||||
|
<div class="seg" role="group" :aria-label="t.langLabel">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="seg__btn"
|
||||||
|
:class="{ 'seg__btn--active': lang === 'ru' }"
|
||||||
|
:aria-pressed="lang === 'ru'"
|
||||||
|
@click="setLang('ru')"
|
||||||
|
>Русский</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="seg__btn"
|
||||||
|
:class="{ 'seg__btn--active': lang === 'en' }"
|
||||||
|
:aria-pressed="lang === 'en'"
|
||||||
|
@click="setLang('en')"
|
||||||
|
>English</button>
|
||||||
|
</div>
|
||||||
|
<div class="seg seg--icons" role="group" :aria-label="t.themeLabel">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="seg__btn"
|
||||||
|
:class="{ 'seg__btn--active': theme === 'light' }"
|
||||||
|
:aria-pressed="theme === 'light'"
|
||||||
|
:title="t.themes.light"
|
||||||
|
@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="t.themes.dark"
|
||||||
|
@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>
|
||||||
|
|
||||||
|
<ClientOnly>
|
||||||
|
<section class="landing__cards">
|
||||||
|
<a
|
||||||
|
v-for="key in ['docs', 'ai', 'zip']"
|
||||||
|
:key="key"
|
||||||
|
class="landing__card"
|
||||||
|
:class="{ 'landing__card--soon': t.cards[key].badge }"
|
||||||
|
:href="t.cards[key].href"
|
||||||
|
:aria-disabled="t.cards[key].badge ? 'true' : null"
|
||||||
|
>
|
||||||
|
<h3>
|
||||||
|
{{ t.cards[key].title }}
|
||||||
|
<span v-if="t.cards[key].badge" class="landing__badge">{{ t.cards[key].badge }}</span>
|
||||||
|
</h3>
|
||||||
|
<p>{{ t.cards[key].desc }}</p>
|
||||||
|
<span class="landing__cta">{{ t.cards[key].cta }} →</span>
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
</ClientOnly>
|
||||||
|
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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--soon {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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__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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__badge {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 8px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--vp-c-bg-mute);
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.landing {
|
||||||
|
padding: 16px 16px 48px;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
.landing__title {
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
.landing__cards {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.landing__controls {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,7 +6,7 @@ title: Компоненты
|
|||||||
|
|
||||||
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
|
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
|
||||||
|
|
||||||
Архитектурные слои и их назначение описаны в разделе [Архитектура](/basics/architecture).
|
Архитектурные слои и их назначение описаны в разделе [Архитектура](/ru/basics/architecture/).
|
||||||
|
|
||||||
|
|
||||||
## Правила организации
|
## Правила организации
|
||||||
@@ -25,7 +25,7 @@ container/
|
|||||||
├── styles/
|
├── styles/
|
||||||
│ └── container.module.css
|
│ └── container.module.css
|
||||||
├── types/
|
├── types/
|
||||||
│ └── container.interface.ts
|
│ └── container.type.ts
|
||||||
├── container.tsx
|
├── container.tsx
|
||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
@@ -33,13 +33,17 @@ container/
|
|||||||
## Именования
|
## Именования
|
||||||
|
|
||||||
- Имя корневого css класса всегда `.root`
|
- Имя корневого css класса всегда `.root`
|
||||||
- Интерфейс именуется `{ComponentName}Props`.
|
- Тип пропсов именуется `{ComponentName}Props`.
|
||||||
|
- Тип пользовательских параметров именуется `{ComponentName}Params`.
|
||||||
|
|
||||||
## Типизация
|
## Типизация
|
||||||
|
|
||||||
- Компонент типизируется через `FC<Props>`.
|
Структура типов компонента показана в [примере](#пример). Ниже — обоснования ключевых решений.
|
||||||
- Интерфейс пропсов наследует HTML-атрибуты своего корневого элемента.
|
|
||||||
- `children` отдельно не объявляется — приходит из `HTMLAttributes`.
|
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
|
||||||
|
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
|
||||||
|
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
|
||||||
|
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/ru/basics/typing).
|
||||||
|
|
||||||
## Реализация
|
## Реализация
|
||||||
|
|
||||||
@@ -50,7 +54,7 @@ container/
|
|||||||
|
|
||||||
## Пример
|
## Пример
|
||||||
|
|
||||||
`container/types/container.interface.ts`
|
`container/types/container.type.ts`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import type { HTMLAttributes } from 'react'
|
import type { HTMLAttributes } from 'react'
|
||||||
@@ -58,7 +62,12 @@ import type { HTMLAttributes } from 'react'
|
|||||||
/**
|
/**
|
||||||
* Параметры компонента Container.
|
* Параметры компонента Container.
|
||||||
*/
|
*/
|
||||||
export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {}
|
export type ContainerParams = {}
|
||||||
|
|
||||||
|
/** HTML-атрибуты корневого элемента. */
|
||||||
|
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||||
|
|
||||||
|
export type ContainerProps = RootAttrs & ContainerParams
|
||||||
```
|
```
|
||||||
|
|
||||||
`container/styles/container.module.css`
|
`container/styles/container.module.css`
|
||||||
@@ -74,9 +83,8 @@ export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {}
|
|||||||
`container/container.tsx`
|
`container/container.tsx`
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import type { FC } from 'react'
|
|
||||||
import cl from 'clsx'
|
import cl from 'clsx'
|
||||||
import type { ContainerProps } from './types/container.interface'
|
import type { ContainerProps } from './types/container.type'
|
||||||
import styles from './styles/container.module.css'
|
import styles from './styles/container.module.css'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,7 +94,7 @@ import styles from './styles/container.module.css'
|
|||||||
* - обёртки контента страниц с ограничением ширины
|
* - обёртки контента страниц с ограничением ширины
|
||||||
* - центрирования блоков в лейауте
|
* - центрирования блоков в лейауте
|
||||||
*/
|
*/
|
||||||
export const Container: FC<ContainerProps> = (props) => {
|
export const Container = (props: ContainerProps) => {
|
||||||
const { children, className, ...htmlAttr } = props
|
const { children, className, ...htmlAttr } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -102,4 +110,3 @@ export const Container: FC<ContainerProps> = (props) => {
|
|||||||
```ts
|
```ts
|
||||||
export { Container } from './container'
|
export { Container } from './container'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,101 +1,26 @@
|
|||||||
---
|
---
|
||||||
title: Страницы (App Router)
|
title: Файлы роутинга
|
||||||
---
|
---
|
||||||
|
|
||||||
# Страницы (App Router)
|
# Файлы роутинга
|
||||||
|
|
||||||
Специальные файлы Next.js App Router, которые фреймворк использует по соглашению: `layout.tsx`, `page.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`, `template.tsx`.
|
Правила для специальных файлов App Router (`page.tsx`, `layout.tsx`, `error.tsx`, `not-found.tsx` и др.) — чем наш подход отличается от дефолтного.
|
||||||
|
|
||||||
## Общие правила
|
## Организация
|
||||||
|
|
||||||
- Экспорт через `export default function` — конвенция Next.js.
|
- `page.tsx` — тонкий файл: только `metadata` и рендер экрана. Логика, стили и зависимости живут в экране, не в `page.tsx`.
|
||||||
- Типизация через `PropsWithChildren` или явный интерфейс.
|
- `error.tsx` и `not-found.tsx` делегируют разметку экранам по тому же принципу.
|
||||||
- Каждая страница (`page.tsx`) должна содержать `metadata` с `title` и `description`.
|
- `layout.tsx` — точка подключения провайдеров и глобальных стилей. Вёрстка layout-обёрток выносится в слой `layouts/`.
|
||||||
- Минимум логики — page-level компоненты делегируют работу экранам, виджетам и фичам.
|
- Стили в файлах роутинга не используются — стилизация только внутри вызываемых компонентов.
|
||||||
- Стили в page-level компонентах не используются — стилизация внутри вызываемых компонентов.
|
|
||||||
|
|
||||||
## layout.tsx
|
## Реализация
|
||||||
|
|
||||||
Корневой layout — точка подключения провайдеров, глобальных стилей и метаданных.
|
- Каждый `page.tsx` экспортирует `metadata` с `title` — он подставляется в шаблон корневого layout (`%s | App`).
|
||||||
|
- Корневой `layout.tsx` задаёт `metadata` с `title.template`, `description`, `metadataBase` и OpenGraph-настройками.
|
||||||
|
|
||||||
```tsx
|
## Примеры
|
||||||
import type { PropsWithChildren } from 'react'
|
|
||||||
import type { Metadata } from 'next'
|
|
||||||
import { Providers } from './providers'
|
|
||||||
import './styles/index.css'
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
`src/app/profile/[id]/page.tsx`
|
||||||
title: {
|
|
||||||
default: 'App',
|
|
||||||
template: '%s | App',
|
|
||||||
},
|
|
||||||
description: 'Описание приложения',
|
|
||||||
metadataBase: new URL('https://example.com'),
|
|
||||||
openGraph: {
|
|
||||||
type: 'website',
|
|
||||||
locale: 'ru_RU',
|
|
||||||
siteName: 'App',
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: '/og-image.png',
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
alt: 'App',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
card: 'summary_large_image',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RootLayout({ children }: PropsWithChildren) {
|
|
||||||
return (
|
|
||||||
<html lang="ru" suppressHydrationWarning>
|
|
||||||
<body>
|
|
||||||
<Providers>
|
|
||||||
{children}
|
|
||||||
</Providers>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Вложенный layout — для секции с общей обёрткой (sidebar, header):
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import type { PropsWithChildren } from 'react'
|
|
||||||
import { DashboardLayout } from '@/shared/ui/dashboard-layout'
|
|
||||||
|
|
||||||
export default function Layout({ children }: PropsWithChildren) {
|
|
||||||
return (
|
|
||||||
<DashboardLayout>
|
|
||||||
{children}
|
|
||||||
</DashboardLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## page.tsx
|
|
||||||
|
|
||||||
Тонкий файл — только импорт и рендер экрана. Логика, стили и зависимости размещаются в экране, не в `page.tsx`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import type { Metadata } from 'next'
|
|
||||||
import { HomeScreen } from '@/screens/home'
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: 'Главная',
|
|
||||||
description: 'Главная страница приложения',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HomePage() {
|
|
||||||
return <HomeScreen />
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
С параметрами маршрута:
|
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import type { Metadata } from 'next'
|
import type { Metadata } from 'next'
|
||||||
@@ -106,7 +31,7 @@ export const metadata: Metadata = {
|
|||||||
description: 'Страница профиля пользователя',
|
description: 'Страница профиля пользователя',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProfilePageProps {
|
type ProfilePageProps = {
|
||||||
params: Promise<{ id: string }>
|
params: Promise<{ id: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,66 +42,21 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Каждая страница должна содержать `metadata` с `title` — он подставится в шаблон из корневого layout: `Профиль | App`.
|
`src/app/error.tsx`
|
||||||
|
|
||||||
## loading.tsx
|
|
||||||
|
|
||||||
Состояние загрузки. Показывается пока загружается контент страницы.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
export default function Loading() {
|
|
||||||
return <div>Загрузка...</div>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## error.tsx
|
|
||||||
|
|
||||||
Обработка ошибок. Обязательно `'use client'` — error boundary работает только на клиенте. Разметку выносим в экран.
|
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import { ErrorScreen } from '@/screens/error'
|
import { ErrorScreen } from '@/screens/error'
|
||||||
|
|
||||||
interface ErrorPageProps {
|
type ErrorPageProps = {
|
||||||
error: Error & { digest?: string }
|
error: Error & { digest?: string }
|
||||||
reset: () => void
|
reset: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ErrorPage: FC<ErrorPageProps> = ({ error, reset }) => {
|
const ErrorPage = ({ error, reset }: ErrorPageProps) => {
|
||||||
return <ErrorScreen error={error} reset={reset} />
|
return <ErrorScreen error={error} reset={reset} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ErrorPage
|
export default ErrorPage
|
||||||
```
|
```
|
||||||
|
|
||||||
## not-found.tsx
|
|
||||||
|
|
||||||
Страница 404. Показывается когда маршрут не найден. Разметку выносим в экран.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import type { Metadata } from 'next'
|
|
||||||
import { NotFoundScreen } from '@/screens/not-found'
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: 'Страница не найдена',
|
|
||||||
description: 'Запрашиваемая страница не существует',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NotFound() {
|
|
||||||
return <NotFoundScreen />
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## template.tsx
|
|
||||||
|
|
||||||
Аналог layout, но пересоздаётся при каждой навигации (не сохраняет состояние). Используется редко — для анимаций переходов между страницами.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import type { PropsWithChildren } from 'react'
|
|
||||||
|
|
||||||
export default function Template({ children }: PropsWithChildren) {
|
|
||||||
return <div>{children}</div>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -41,19 +41,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/
|
||||||
@@ -73,8 +74,7 @@ src/app/
|
|||||||
├── screen/ # Шаблон экрана
|
├── screen/ # Шаблон экрана
|
||||||
├── layout/ # Шаблон layout
|
├── layout/ # Шаблон layout
|
||||||
├── widget/ # Шаблон виджета
|
├── widget/ # Шаблон виджета
|
||||||
├── feature/ # Шаблон фичи
|
├── module/ # Шаблон бизнес-модуля
|
||||||
├── entity/ # Шаблон сущности
|
|
||||||
└── store/ # Шаблон стора
|
└── store/ # Шаблон стора
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ title: Шаблоны и генерация кода
|
|||||||
│ ├── styles/
|
│ ├── styles/
|
||||||
│ │ └── {{name.kebabCase}}.module.css
|
│ │ └── {{name.kebabCase}}.module.css
|
||||||
│ ├── types/
|
│ ├── types/
|
||||||
│ │ └── {{name.kebabCase}}.interface.ts
|
│ │ └── {{name.kebabCase}}.type.ts
|
||||||
│ ├── {{name.kebabCase}}.tsx
|
│ ├── {{name.kebabCase}}.tsx
|
||||||
│ └── index.ts
|
│ └── index.ts
|
||||||
└── store/ # шаблон Zustand стора
|
└── store/ # шаблон Zustand стора
|
||||||
@@ -86,26 +86,30 @@ export { {{name.pascalCase}} } from './{{name.kebabCase}}'
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// .templates/component/types/{{name.kebabCase}}.interface.ts
|
// .templates/component/types/{{name.kebabCase}}.type.ts
|
||||||
import type { HTMLAttributes } from 'react'
|
import type { HTMLAttributes } from 'react'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Параметры {{name.pascalCase}}.
|
* Параметры {{name.pascalCase}}.
|
||||||
*/
|
*/
|
||||||
export interface {{name.pascalCase}}Props extends HTMLAttributes<HTMLDivElement> {}
|
export type {{name.pascalCase}}Params = {}
|
||||||
|
|
||||||
|
/** HTML-атрибуты корневого элемента. */
|
||||||
|
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||||
|
|
||||||
|
export type {{name.pascalCase}}Props = RootAttrs & {{name.pascalCase}}Params
|
||||||
```
|
```
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// .templates/component/{{name.kebabCase}}.tsx
|
// .templates/component/{{name.kebabCase}}.tsx
|
||||||
import type { FC } from 'react'
|
|
||||||
import cl from 'clsx'
|
import cl from 'clsx'
|
||||||
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.interface'
|
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
|
||||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {{name.pascalCase}}.
|
* {{name.pascalCase}}.
|
||||||
*/
|
*/
|
||||||
export const {{name.pascalCase}}: FC<{{name.pascalCase}}Props> = (props) => {
|
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
|
||||||
const { children, className, ...htmlAttr } = props
|
const { children, className, ...htmlAttr } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -125,7 +129,7 @@ export const {{name.pascalCase}}: FC<{{name.pascalCase}}Props> = (props) => {
|
|||||||
|
|
||||||
## Генерация через VS Code
|
## Генерация через VS Code
|
||||||
|
|
||||||
[MyTemplateGenerator](https://open-vsx.org/extension/MyTemplateGenerator/mytemplategenerator) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
|
Template File Generator | gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
|
||||||
|
|
||||||
1. ПКМ на целевой папке в проводнике VS Code.
|
1. ПКМ на целевой папке в проводнике VS Code.
|
||||||
2. **Generate from template** → выбрать шаблон.
|
2. **Generate from template** → выбрать шаблон.
|
||||||
@@ -142,11 +146,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` | Стор |
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ title: Настройка VS Code
|
|||||||
| Расширение | Назначение |
|
| Расширение | Назначение |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) | Линтинг и форматирование кода. Заменяет ESLint и Prettier |
|
| [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) | Линтинг и форматирование кода. Заменяет ESLint и Prettier |
|
||||||
| [MyTemplateGenerator](https://open-vsx.org/extension/MyTemplateGenerator/mytemplategenerator) | Генерация файлов и папок из шаблонов `.templates/` через контекстное меню |
|
| Template File Generator \| gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) | Генерация файлов и папок из шаблонов `.templates/` через контекстное меню |
|
||||||
| [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss) | Подсветка синтаксиса и автодополнение для PostCSS (`@custom-media`, `@nest` и др.) |
|
| [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss) | Подсветка синтаксиса и автодополнение для PostCSS (`@custom-media`, `@nest` и др.) |
|
||||||
|
|
||||||
### Зачем это нужно
|
### Зачем это нужно
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
---
|
|
||||||
title: Архитектура
|
|
||||||
---
|
|
||||||
|
|
||||||
# Архитектура
|
|
||||||
|
|
||||||
Этот раздел описывает архитектуру проекта: из каких слоёв состоит приложение,
|
|
||||||
как организован код внутри слоёв и какие правила управляют зависимостями.
|
|
||||||
|
|
||||||
## Что важно знать
|
|
||||||
|
|
||||||
Проект использует [FSD (Feature-Sliced Design)](https://feature-sliced.design/docs/get-started/overview)
|
|
||||||
как базовую архитектурную методологию. Если вы не знакомы с FSD — начните с официальной документации.
|
|
||||||
|
|
||||||
Данная архитектура является **надстройкой над FSD**, а не заменой. Все правила FSD действуют
|
|
||||||
по умолчанию — если правило явно не переопределено в этом документе, применяется стандарт FSD.
|
|
||||||
Единственное отклонение: вместо слайсов используются **компоненты**.
|
|
||||||
|
|
||||||
## Слои
|
|
||||||
|
|
||||||
| Слой | Назначение |
|
|
||||||
|------|-----------|
|
|
||||||
| `app` | Инициализация: провайдеры, стили, роутинг Next.js |
|
|
||||||
| `screens` | Сборка страницы из виджетов и фич |
|
|
||||||
| `layouts` | Каркасы и шаблоны страниц |
|
|
||||||
| `widgets` | Крупные блоки интерфейса |
|
|
||||||
| `features` | Пользовательские сценарии и действия |
|
|
||||||
| `entities` | Бизнес-сущности |
|
|
||||||
| `shared` | Утилиты, UI-кит, инфраструктура |
|
|
||||||
|
|
||||||
Слой `pages` не используется — конфликтует с Next.js. Вместо него: `screens` и `layouts`.
|
|
||||||
|
|
||||||
Зависимости идут строго сверху вниз: `app → screens → layouts → widgets → features → entities → shared`.
|
|
||||||
|
|
||||||
## Компоненты
|
|
||||||
|
|
||||||
Компонент — стандартная UI-единица, такая же как в любом React-проекте. Содержит корневой `.tsx`,
|
|
||||||
публичный API (`index.ts`) и сегменты.
|
|
||||||
|
|
||||||
Компоненты располагаются в:
|
|
||||||
- `shared/ui/` — переиспользуемые компоненты без бизнес-контекста
|
|
||||||
- `ui/` внутри master component'а — дочерние компоненты *(подробнее в разделе [Master component](#master-component))*
|
|
||||||
|
|
||||||
## Сегменты
|
|
||||||
|
|
||||||
Сегмент — папка внутри компонента, группирующая код по техническому назначению. Набор не фиксирован.
|
|
||||||
|
|
||||||
| Сегмент | Назначение |
|
|
||||||
|---------|-----------|
|
|
||||||
| `styles/` | Стили |
|
|
||||||
| `types/` | Интерфейсы, типы, enums, DTO |
|
|
||||||
| `ui/` | Компоненты, провайдеры и любые другие элементы интерфейса |
|
|
||||||
| `stores/` | Сторы состояния |
|
|
||||||
| `hooks/` | React-хуки |
|
|
||||||
| `services/` | Внешние источники данных |
|
|
||||||
| `lib/` | Утилиты |
|
|
||||||
| `helpers/` | Вспомогательные функции |
|
|
||||||
| `config/` | Константы, конфигурация |
|
|
||||||
|
|
||||||
## Master component
|
|
||||||
|
|
||||||
Master component — это обычный компонент, на который наложен ряд дополнительных правил.
|
|
||||||
Эти правила определяют его место в архитектуре и границы зависимостей.
|
|
||||||
|
|
||||||
- Может располагаться только в слоях: `screens`, `layouts`, `widgets`, `features`, `entities`
|
|
||||||
- Импортирует master component'ы только из слоёв ниже по иерархии
|
|
||||||
- Корневой `.tsx` именуется с суффиксом слоя: `header.widget.tsx`, `auth.feature.tsx`
|
|
||||||
- Корневой `.tsx` необязателен — `index.ts` может экспортировать несколько сущностей напрямую
|
|
||||||
- Дочерние компоненты в `ui/` доступны снаружи только через `index.ts`
|
|
||||||
- Компоненты внутри одного `ui/` могут импортировать друг друга
|
|
||||||
99
docs/ru/basics/architecture/index.md
Normal file
99
docs/ru/basics/architecture/index.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
title: Архитектура
|
||||||
|
description: "Раздел описывает архитектуру проекта: из каких слоёв состоит приложение, как организован код внутри слоёв и какие правила управляют зависимостями."
|
||||||
|
---
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
- **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.
|
||||||
252
docs/ru/basics/architecture/reference/layers.md
Normal file
252
docs/ru/basics/architecture/reference/layers.md
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
---
|
||||||
|
title: Слои
|
||||||
|
---
|
||||||
|
|
||||||
|
# Слои
|
||||||
|
|
||||||
|
Раздел описывает слои 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-состояния
|
||||||
164
docs/ru/basics/architecture/reference/modules.md
Normal file
164
docs/ru/basics/architecture/reference/modules.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
---
|
||||||
|
title: Модули
|
||||||
|
---
|
||||||
|
|
||||||
|
# Модули
|
||||||
|
|
||||||
|
Раздел описывает модули 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
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробное описание каждого сегмента — в разделе [Сегменты](/ru/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/`
|
||||||
|
|
||||||
|
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
|
||||||
153
docs/ru/basics/architecture/reference/segments.md
Normal file
153
docs/ru/basics/architecture/reference/segments.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
title: Сегменты
|
||||||
|
---
|
||||||
|
|
||||||
|
# Сегменты
|
||||||
|
|
||||||
|
Раздел описывает сегменты 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
|
||||||
|
```
|
||||||
@@ -79,7 +79,7 @@ export const buildCategoryTree = (categories: Category[]): CategoryTree[] => { .
|
|||||||
* - обёртки контента страниц с ограничением ширины
|
* - обёртки контента страниц с ограничением ширины
|
||||||
* - центрирования блоков в лейауте
|
* - центрирования блоков в лейауте
|
||||||
*/
|
*/
|
||||||
export const Container: FC<ContainerProps> = (props) => { ... }
|
export const Container = (props: ContainerProps) => { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
**Плохо**
|
**Плохо**
|
||||||
@@ -90,7 +90,7 @@ export const Container: FC<ContainerProps> = (props) => { ... }
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Плохо: нет описания вообще.
|
// Плохо: нет описания вообще.
|
||||||
export const Container: FC<ContainerProps> = (props) => { ... }
|
export const Container = (props: ContainerProps) => { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Типы, интерфейсы, enum
|
## Типы, интерфейсы, enum
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ title: Именование
|
|||||||
|
|
||||||
**Хорошо**
|
**Хорошо**
|
||||||
```text
|
```text
|
||||||
features/
|
business/
|
||||||
└── auth-by-email/
|
└── auth-by-email/
|
||||||
├── ui/
|
├── ui/
|
||||||
│ └── login-form.tsx
|
│ └── login-form.tsx
|
||||||
@@ -62,14 +62,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
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ title: Технологии и библиотеки
|
|||||||
- `Next.js` — для продуктовых сайтов.
|
- `Next.js` — для продуктовых сайтов.
|
||||||
|
|
||||||
### Архитектура
|
### Архитектура
|
||||||
- `FSD (Feature-Sliced Design)` — структура проекта и границы модулей. Используется кастомизированная версия — подробнее в разделе [Архитектура](/basics/architecture).
|
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/ru/basics/architecture/).
|
||||||
|
|
||||||
### UI компоненты
|
### UI компоненты
|
||||||
- `Mantine UI` — базовые UI-компоненты.
|
- `Mantine UI` — базовые UI-компоненты.
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
## Для ассистентов
|
## Для ассистентов
|
||||||
|
|
||||||
Полная документация в одном MD файле: https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/RULES.md
|
Карта документации со ссылками на все разделы (формат [llmstxt.org](https://llmstxt.org)):
|
||||||
|
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/llms.txt
|
||||||
|
|
||||||
## Структура документации
|
## Структура документации
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
| Создание проекта | Как начать новый проект? |
|
| Создание проекта | Как начать новый проект? |
|
||||||
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
|
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
|
||||||
| Добавление страницы | Как добавить новую страницу в проект? |
|
| Добавление страницы | Как добавить новую страницу в проект? |
|
||||||
| Добавление UI-модуля | Как создать компонент, фичу, виджет, сущность или layout? |
|
| Добавление UI-модуля | Как создать компонент, бизнес-модуль, виджет или layout? |
|
||||||
| Стилизация | Как стилизовать компоненты в проекте? |
|
| Стилизация | Как стилизовать компоненты в проекте? |
|
||||||
| Получение данных | Как получать данные с сервера? |
|
| Получение данных | Как получать данные с сервера? |
|
||||||
| Управление состоянием | Как работать с состоянием? |
|
| Управление состоянием | Как работать с состоянием? |
|
||||||
@@ -31,11 +32,11 @@
|
|||||||
| Раздел | Отвечает на вопрос |
|
| Раздел | Отвечает на вопрос |
|
||||||
|--------|-------------------|
|
|--------|-------------------|
|
||||||
| Технологии и библиотеки | Какой стек используем? |
|
| Технологии и библиотеки | Какой стек используем? |
|
||||||
| Архитектура | Как устроены слои FSD, зависимости, публичный API? |
|
| Архитектура | Как устроены слои SLM, зависимости, публичный API? |
|
||||||
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
||||||
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
||||||
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||||
| Типизация | Как типизировать: type vs interface, any/unknown, FC? |
|
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||||
|
|
||||||
### Прикладные разделы
|
### Прикладные разделы
|
||||||
|
|
||||||
@@ -44,8 +45,8 @@
|
|||||||
| Раздел | Отвечает на вопрос |
|
| Раздел | Отвечает на вопрос |
|
||||||
|--------|-------------------|
|
|--------|-------------------|
|
||||||
| Настройка VS Code | Как настроить редактор для проекта? |
|
| Настройка VS Code | Как настроить редактор для проекта? |
|
||||||
| Структура проекта | Как организованы папки и файлы по FSD? |
|
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||||
| Компоненты | Как устроен компонент: файлы, пропсы, clsx, FC? |
|
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||||
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
|
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
|
||||||
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||||
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ title: Workflow
|
|||||||
cd my-app
|
cd my-app
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
2. Проект готов к разработке — стек, структура FSD, конфигурация
|
2. Проект готов к разработке — стек, структура SLM, конфигурация
|
||||||
редактора и шаблоны генерации уже настроены.
|
редактора и шаблоны генерации уже настроены.
|
||||||
|
|
||||||
## Генерация кода
|
## Генерация кода
|
||||||
@@ -27,13 +27,12 @@ title: Workflow
|
|||||||
|
|
||||||
| Модуль | Слой | Шаблон |
|
| Модуль | Слой | Шаблон |
|
||||||
|------------|--------------|-------------|
|
|------------|--------------|-------------|
|
||||||
| Компонент | `shared/ui/` | `component` |
|
| Компонент | `ui/` | `component` |
|
||||||
| Фича | `features/` | `feature` |
|
| Бизнес-модуль | `business/` | `module` |
|
||||||
| Виджет | `widgets/` | `widget` |
|
| Виджет | `widgets/` | `widget` |
|
||||||
| Сущность | `entities/` | `entity` |
|
|
||||||
| Layout | `layouts/` | `layout` |
|
| Layout | `layouts/` | `layout` |
|
||||||
| Экран | `screens/` | `screen` |
|
| Экран | `screens/` | `screen` |
|
||||||
| Стор | `model/` | `store` |
|
| Стор | `stores/` | `store` |
|
||||||
|
|
||||||
2. Сгенерировать модуль из шаблона.
|
2. Сгенерировать модуль из шаблона.
|
||||||
3. Если подходящего шаблона нет — сначала создать шаблон, затем использовать.
|
3. Если подходящего шаблона нет — сначала создать шаблон, затем использовать.
|
||||||
@@ -53,7 +52,7 @@ title: Workflow
|
|||||||
|
|
||||||
## Добавление UI-модуля
|
## Добавление UI-модуля
|
||||||
|
|
||||||
Создание компонента, фичи, виджета, сущности или layout.
|
Создание компонента, бизнес-модуля, виджета или layout.
|
||||||
|
|
||||||
1. Сгенерировать модуль из соответствующего шаблона в целевой слой.
|
1. Сгенерировать модуль из соответствующего шаблона в целевой слой.
|
||||||
2. Заполнить модуль логикой и стилями.
|
2. Заполнить модуль логикой и стилями.
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ title: Генерация кода
|
|||||||
|
|
||||||
| Модуль | Слой | Шаблон |
|
| Модуль | Слой | Шаблон |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Компонент | `shared/ui/` | `component` |
|
| Компонент | `ui/` | `component` |
|
||||||
| Фича | `features/` | `feature` |
|
| Бизнес-модуль | `business/` | `module` |
|
||||||
| Виджет | `widgets/` | `widget` |
|
| Виджет | `widgets/` | `widget` |
|
||||||
| Сущность | `entities/` | `entity` |
|
|
||||||
| Layout | `layouts/` | `layout` |
|
| Layout | `layouts/` | `layout` |
|
||||||
| Экран | `screens/` | `screen` |
|
| Экран | `screens/` | `screen` |
|
||||||
| Стор | `model/` | `store` |
|
| Стор | `stores/` | `store` |
|
||||||
|
|
||||||
## Что нужно знать
|
## Что нужно знать
|
||||||
|
|
||||||
@@ -29,4 +28,4 @@ title: Генерация кода
|
|||||||
- Повторяющаяся структура появляется больше одного раза.
|
- Повторяющаяся структура появляется больше одного раза.
|
||||||
- Существующий шаблон не покрывает нужный тип модуля.
|
- Существующий шаблон не покрывает нужный тип модуля.
|
||||||
|
|
||||||
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/applied/templates-generation).
|
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/ru/applied/templates-generation).
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ title: Создание проекта
|
|||||||
|
|
||||||
## Что нужно знать
|
## Что нужно знать
|
||||||
|
|
||||||
Новый проект создаётся из готового шаблона. Шаблон содержит настроенный стек, структуру FSD, конфигурацию редактора и шаблоны генерации кода — проект готов к разработке сразу после установки зависимостей.
|
Новый проект создаётся из готового шаблона. Шаблон содержит настроенный стек, структуру SLM, конфигурацию редактора и шаблоны генерации кода — проект готов к разработке сразу после установки зависимостей.
|
||||||
|
|
||||||
### Создание из шаблона
|
### Создание из шаблона
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ npm install
|
|||||||
- Mantine UI + PostCSS Modules
|
- Mantine UI + PostCSS Modules
|
||||||
- Biome (линтинг и форматирование)
|
- Biome (линтинг и форматирование)
|
||||||
- Zustand, SWR
|
- Zustand, SWR
|
||||||
- Структура FSD (`screens/`, `widgets/`, `features/`, `entities/`, `shared/`)
|
- Структура SLM (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`, `shared/`)
|
||||||
- Шаблоны генерации (`.templates/`)
|
- Шаблоны генерации (`.templates/`)
|
||||||
- Конфигурация VS Code (`.vscode/`)
|
- Конфигурация VS Code (`.vscode/`)
|
||||||
- CSS-токены (цвета, отступы, радиусы, медиа)
|
- CSS-токены (цвета, отступы, радиусы, медиа)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Добавление UI-модуля
|
|||||||
|
|
||||||
# Добавление UI-модуля
|
# Добавление UI-модуля
|
||||||
|
|
||||||
Как создать компонент, фичу, виджет, сущность или layout в проекте.
|
Как создать компонент, бизнес-модуль, виджет или layout в проекте.
|
||||||
|
|
||||||
## Что нужно знать
|
## Что нужно знать
|
||||||
|
|
||||||
@@ -12,11 +12,11 @@ title: Добавление UI-модуля
|
|||||||
|
|
||||||
## Порядок действий
|
## Порядок действий
|
||||||
|
|
||||||
1. [Сгенерировать](/applied/templates-generation) модуль из соответствующего шаблона в целевой слой.
|
1. [Сгенерировать](/ru/applied/templates-generation) модуль из соответствующего шаблона в целевой слой.
|
||||||
2. Заполнить модуль логикой и стилями.
|
2. Заполнить модуль логикой и стилями.
|
||||||
|
|
||||||
## Дочерние компоненты
|
## Дочерние компоненты
|
||||||
|
|
||||||
Если модулю нужны внутренние подкомпоненты — [генерировать](/applied/templates-generation) их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
|
Если модулю нужны внутренние подкомпоненты — [генерировать](/ru/applied/templates-generation) их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
|
||||||
|
|
||||||
Правила написания компонентов — [Компоненты](/applied/components).
|
Правила написания компонентов — [Компоненты](/ru/applied/components).
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ title: Добавление страницы
|
|||||||
|
|
||||||
## Порядок действий
|
## Порядок действий
|
||||||
|
|
||||||
1. [Сгенерировать](/applied/templates-generation) экран из шаблона `screen` в папку `src/screens/`.
|
1. [Сгенерировать](/ru/applied/templates-generation) экран из шаблона `screen` в папку `src/screens/`.
|
||||||
|
|
||||||
2. Заполнить экран логикой и стилями.
|
2. Заполнить экран логикой и стилями.
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@ title: Добавление страницы
|
|||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
- Ручное создание файловой структуры экрана запрещено — только [генерация](/applied/templates-generation) из шаблона.
|
- Ручное создание файловой структуры экрана запрещено — только [генерация](/ru/applied/templates-generation) из шаблона.
|
||||||
- Логика, стили и зависимости размещаются в экране, не в `page.tsx`.
|
- Логика, стили и зависимости размещаются в экране, не в `page.tsx`.
|
||||||
- Каждая страница содержит `metadata` с `title` и `description`.
|
- Каждая страница содержит `metadata` с `title` и `description`.
|
||||||
|
|
||||||
Примеры `page.tsx` и `metadata` — [Page-level компоненты](/applied/page-level).
|
Примеры `page.tsx` и `metadata` — [Page-level компоненты](/ru/applied/page-level).
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ title: Начало работы
|
|||||||
|
|
||||||
## Стек проекта
|
## Стек проекта
|
||||||
|
|
||||||
**Next.js** (App Router), **Mantine**, **Zustand**, **FSD**.
|
**Next.js** (App Router), **Mantine**, **Zustand**, **SLM Design**.
|
||||||
|
|
||||||
Подробнее — [Технологии и библиотеки](/basics/tech-stack).
|
Подробнее — [Технологии и библиотеки](/ru/basics/tech-stack).
|
||||||
|
|
||||||
## Ключевые особенности
|
## Ключевые особенности
|
||||||
|
|
||||||
- **Генерация вместо ручного создания** — компоненты, фичи, виджеты, сторы и другие модули не создаются вручную. Файловая структура генерируется из шаблонов `.templates/`. Ручное создание файловой структуры модулей запрещено.
|
- **Генерация вместо ручного создания** — компоненты, бизнес-модули, виджеты, сторы и другие модули не создаются вручную. Файловая структура генерируется из шаблонов `.templates/`. Ручное создание файловой структуры модулей запрещено.
|
||||||
- **Biome вместо ESLint + Prettier** — один инструмент для линтинга и форматирования. Автофикс и сортировка импортов происходят автоматически при сохранении файла.
|
- **Biome вместо ESLint + Prettier** — один инструмент для линтинга и форматирования. Автофикс и сортировка импортов происходят автоматически при сохранении файла.
|
||||||
|
|
||||||
## Настройка окружения
|
## Настройка окружения
|
||||||
|
|
||||||
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/applied/vscode).
|
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/ru/applied/vscode).
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ title: Стилизация
|
|||||||
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
|
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
|
||||||
- **Глобальные стили** вне `app/styles/` запрещены.
|
- **Глобальные стили** вне `app/styles/` запрещены.
|
||||||
|
|
||||||
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили](/applied/styles).
|
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили](/ru/applied/styles).
|
||||||
|
|||||||
383
generate-llms.ts
Normal file
383
generate-llms.ts
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
type Lang = 'ru' | 'en';
|
||||||
|
|
||||||
|
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/{lang}/`. Sidebar links содержат полный префикс локали
|
||||||
|
* (`/ru/...`, `/en/...`) — отрезаем его.
|
||||||
|
*/
|
||||||
|
const linkToRel = (link: string, lang: Lang): string => {
|
||||||
|
const prefix = `/${lang}/`;
|
||||||
|
let rel = link.startsWith(prefix) ? link.slice(prefix.length) : link.replace(/^\//, '');
|
||||||
|
if (rel === '' || rel.endsWith('/')) {
|
||||||
|
rel += 'index.md';
|
||||||
|
} else {
|
||||||
|
rel += '.md';
|
||||||
|
}
|
||||||
|
return rel;
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkToFilePath = (link: string, lang: Lang): string =>
|
||||||
|
path.join('docs', lang, linkToRel(link, lang));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Абсолютный путь от корня сайта к `.md`-копии страницы.
|
||||||
|
* После build файлы лежат в `dist/{lang}/...md` (через `docs/public/`).
|
||||||
|
*/
|
||||||
|
const linkToSiteUrl = (link: string, lang: Lang): string =>
|
||||||
|
`/${lang}/${linkToRel(link, lang)}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Развернуть sidebar в плоский список с сохранением группы и
|
||||||
|
* опционального префикса вложенной группы.
|
||||||
|
*/
|
||||||
|
const flattenSidebar = (sidebar: SidebarItem[]): Entry[] => {
|
||||||
|
const entries: Entry[] = [];
|
||||||
|
|
||||||
|
for (const top of sidebar) {
|
||||||
|
const section = top.text;
|
||||||
|
|
||||||
|
if (top.link && !top.items) {
|
||||||
|
entries.push({ section, prefix: null, text: top.text, link: top.link });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!top.items) continue;
|
||||||
|
|
||||||
|
for (const item of top.items) {
|
||||||
|
if (item.items) {
|
||||||
|
for (const sub of item.items) {
|
||||||
|
if (!sub.link) continue;
|
||||||
|
entries.push({
|
||||||
|
section,
|
||||||
|
prefix: item.text,
|
||||||
|
text: sub.text,
|
||||||
|
link: sub.link,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (item.link) {
|
||||||
|
entries.push({
|
||||||
|
section,
|
||||||
|
prefix: null,
|
||||||
|
text: item.text,
|
||||||
|
link: item.link,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildLlms = (lang: Lang): void => {
|
||||||
|
const localeKey = lang;
|
||||||
|
// VitePress-конфиг типизирован как `UserConfig`, но обращаемся к
|
||||||
|
// фактически переданным значениям — сужаем тип через any.
|
||||||
|
const cfg = config as unknown as {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
locales: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
description?: string;
|
||||||
|
llmsBlockquote?: string;
|
||||||
|
llmsContext?: string;
|
||||||
|
themeConfig?: { sidebar?: SidebarItem[] };
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const locale = cfg.locales[localeKey];
|
||||||
|
const sidebar = locale?.themeConfig?.sidebar;
|
||||||
|
if (!sidebar) {
|
||||||
|
console.warn(`[${lang}] sidebar не найден в config`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Для blockquote предпочитаем расширенный llms-текст; короткий
|
||||||
|
// description — fallback и используется для HTML meta-тега VitePress.
|
||||||
|
const blockquote = locale.llmsBlockquote ?? locale.description ?? cfg.description;
|
||||||
|
const context = locale.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, lang);
|
||||||
|
const url = linkToSiteUrl(entry.link, lang);
|
||||||
|
|
||||||
|
// Текст ссылки берём из sidebar — он специально написан для навигации
|
||||||
|
// и точнее отражает иерархию (например "Обзор" внутри группы "Архитектура").
|
||||||
|
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(`[${lang}] файл не найден: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const display = entry.prefix
|
||||||
|
? `${entry.prefix}: ${entry.text}`
|
||||||
|
: entry.text;
|
||||||
|
const descPart = description ? `: ${description}` : '';
|
||||||
|
lines.push(`- [${display}](${url})${descPart}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const outDir = path.join(PUBLIC_DIR, lang);
|
||||||
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
const outFile = path.join(outDir, 'llms.txt');
|
||||||
|
fs.writeFileSync(outFile, lines.join('\n'), 'utf8');
|
||||||
|
console.log(`${outFile} создан`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Корневой `/llms.txt` — роутер. По стандарту llmstxt.org это
|
||||||
|
* единственный файл в корне сайта; для двуязычного проекта он
|
||||||
|
* указывает LLM на локализованные карты документации.
|
||||||
|
*/
|
||||||
|
const buildRootIndex = (): void => {
|
||||||
|
const cfg = config as unknown as {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
locales: Record<string, { description?: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ruDesc = cfg.locales.ru?.description ?? cfg.description;
|
||||||
|
const enDesc = cfg.locales.en?.description ?? cfg.description;
|
||||||
|
|
||||||
|
const lines: string[] = [
|
||||||
|
`# ${cfg.title}`,
|
||||||
|
'',
|
||||||
|
`> ${enDesc}.`,
|
||||||
|
'',
|
||||||
|
'## Documentation',
|
||||||
|
'',
|
||||||
|
`- [Русская версия (Russian)](/ru/llms.txt): ${ruDesc}.`,
|
||||||
|
'- English version: in development',
|
||||||
|
'',
|
||||||
|
];
|
||||||
|
|
||||||
|
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/{lang}/`,
|
||||||
|
* чтобы они попали в build `dist/` и были доступны по URL `/lang/path.md`.
|
||||||
|
*/
|
||||||
|
const copyMdFiles = (lang: Lang): void => {
|
||||||
|
const srcDir = path.join('docs', lang);
|
||||||
|
const destDir = path.join(PUBLIC_DIR, lang);
|
||||||
|
if (!fs.existsSync(srcDir)) return;
|
||||||
|
|
||||||
|
const copied = copyDirSync(srcDir, destDir, (name) => name.endsWith('.md'));
|
||||||
|
console.log(`[${lang}] скопировано ${copied} .md-файлов в ${destDir}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Собрать `nextjs-style-guide-{lang}.zip` со всеми `.md` локали и `VERSION`.
|
||||||
|
* Внутри архива — единая папка `nextjs-style-guide/`.
|
||||||
|
*
|
||||||
|
* `llms.txt` в архив не кладём: его ссылки указывают на сайт и локально
|
||||||
|
* не работают. Структура папки сама по себе является картой документации.
|
||||||
|
*/
|
||||||
|
const buildZip = (lang: Lang): void => {
|
||||||
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-'));
|
||||||
|
const stage = path.join(tmpRoot, 'nextjs-style-guide');
|
||||||
|
fs.mkdirSync(stage, { recursive: true });
|
||||||
|
|
||||||
|
copyDirSync(path.join('docs', lang), stage, (name) => name.endsWith('.md'));
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(stage, 'VERSION'),
|
||||||
|
`${VERSION}\n${BUILD_DATE}\n`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const outFile = path.resolve(
|
||||||
|
PUBLIC_DIR,
|
||||||
|
`nextjs-style-guide-${lang}.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})`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Манифест сборки — для лендинга и внешних потребителей. */
|
||||||
|
const writeManifest = (): void => {
|
||||||
|
const manifest = {
|
||||||
|
version: VERSION,
|
||||||
|
buildDate: BUILD_DATE,
|
||||||
|
languages: {
|
||||||
|
ru: {
|
||||||
|
llms: '/ru/llms.txt',
|
||||||
|
zip: '/nextjs-style-guide-ru.zip',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
llms: '/en/llms.txt',
|
||||||
|
zip: '/nextjs-style-guide-en.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 создан`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Скопировать `index.md` локали в корневой README без frontmatter */
|
||||||
|
const buildReadme = (lang: Lang, outFile: string): void => {
|
||||||
|
const indexPath = path.join('docs', lang, 'index.md');
|
||||||
|
if (!fs.existsSync(indexPath)) {
|
||||||
|
console.warn(`Пропуск ${outFile}: ${indexPath} не найден`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const raw = fs.readFileSync(indexPath, 'utf8');
|
||||||
|
const { body } = parseFrontmatter(raw);
|
||||||
|
fs.writeFileSync(outFile, body.trimStart(), 'utf8');
|
||||||
|
console.log(`${outFile} обновлён из ${indexPath}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
buildLlms('ru');
|
||||||
|
buildLlms('en');
|
||||||
|
buildRootIndex();
|
||||||
|
copyMdFiles('ru');
|
||||||
|
copyMdFiles('en');
|
||||||
|
buildZip('ru');
|
||||||
|
buildZip('en');
|
||||||
|
writeManifest();
|
||||||
|
buildReadme('en', 'README.md');
|
||||||
|
buildReadme('ru', 'README_RU.md');
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
<!-- /index -->
|
|
||||||
# NextJS Style Guide
|
|
||||||
|
|
||||||
Rules and standards for NextJS and TypeScript development: architecture, typing, styles, components, API, and infrastructure.
|
|
||||||
|
|
||||||
## Documentation Structure
|
|
||||||
|
|
||||||
### Processes
|
|
||||||
|
|
||||||
**What to do** in a specific situation — step-by-step instructions.
|
|
||||||
|
|
||||||
| Section | Answers the question |
|
|
||||||
|---------|---------------------|
|
|
||||||
| Getting Started | What tools to install before starting development? |
|
|
||||||
| Creating an App | How to create a new project, where to get a template? |
|
|
||||||
| Creating Pages | How to add a page: routing and screen? |
|
|
||||||
| Creating Components | How to generate components using templates? |
|
|
||||||
| Styling | What to use: Mantine, tokens, or PostCSS? |
|
|
||||||
| Data Fetching | How to fetch data: SWR, codegen, sockets? |
|
|
||||||
| State Management | When and how to create a store (Zustand)? |
|
|
||||||
| Localization | How to add translations and work with i18next? |
|
|
||||||
|
|
||||||
### Basic Rules
|
|
||||||
|
|
||||||
**What the code should look like** — standards not tied to a specific technology.
|
|
||||||
|
|
||||||
| Section | Answers the question |
|
|
||||||
|---------|---------------------|
|
|
||||||
| Tech Stack | What stack do we use? |
|
|
||||||
| Architecture | How are FSD layers, dependencies, and public API structured? |
|
|
||||||
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
|
||||||
| Naming | How to name files, variables, components, hooks? |
|
|
||||||
| Documentation | How to write JSDoc: what to document and what not? |
|
|
||||||
| Typing | How to type: type vs interface, any/unknown, FC? |
|
|
||||||
|
|
||||||
### Applied Sections
|
|
||||||
|
|
||||||
**How a specific area works** — rules, structure, and code examples for specific technologies and tools.
|
|
||||||
|
|
||||||
| Section | Answers the question |
|
|
||||||
|---------|---------------------|
|
|
||||||
| Project Structure | How are folders and files organized by FSD? |
|
|
||||||
| Components | How is a component structured: files, props, clsx, FC? |
|
|
||||||
| Page-level Components | How to define layout, page, loading, error, not-found? |
|
|
||||||
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
|
|
||||||
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
|
|
||||||
| Images | _(not filled)_ |
|
|
||||||
| SVG Sprites | _(not filled)_ |
|
|
||||||
| Video | _(not filled)_ |
|
|
||||||
| API | _(not filled)_ |
|
|
||||||
| Stores | _(not filled)_ |
|
|
||||||
| Hooks | _(not filled)_ |
|
|
||||||
| Fonts | _(not filled)_ |
|
|
||||||
| Localization | _(not filled)_ |
|
|
||||||
|
|
||||||
## For Assistants
|
|
||||||
|
|
||||||
Full documentation in a single MD file: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/en/RULES.md
|
|
||||||
|
|
||||||
<!-- /basics/tech-stack -->
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
Base technology stack and libraries used in projects.
|
|
||||||
|
|
||||||
<!-- /basics/naming -->
|
|
||||||
## Naming
|
|
||||||
|
|
||||||
Naming should be predictable, concise, and reflect the meaning of the entity.
|
|
||||||
|
|
||||||
<!-- /basics/architecture -->
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
Architecture based on FSD (Feature-Sliced Design) and strict module boundaries.
|
|
||||||
|
|
||||||
<!-- /basics/code-style -->
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
Unified code formatting rules: indentation, line breaks, quotes, import order, and readability.
|
|
||||||
|
|
||||||
<!-- /basics/documentation -->
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Documentation should help understand the purpose of an entity, not duplicate its types or obvious details.
|
|
||||||
|
|
||||||
<!-- /basics/typing -->
|
|
||||||
## Typing
|
|
||||||
|
|
||||||
Typing is required for all public interfaces, functions, and components.
|
|
||||||
|
|
||||||
<!-- /applied/project-structure -->
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
Base project structure and principles of module organization at folder and file level.
|
|
||||||
|
|
||||||
<!-- /applied/components -->
|
|
||||||
## Components
|
|
||||||
|
|
||||||
Rules for creating UI components across all FSD layers.
|
|
||||||
|
|
||||||
<!-- /applied/page-level -->
|
|
||||||
## Page-level Components
|
|
||||||
|
|
||||||
Next.js App Router special files used by the framework by convention: `layout.tsx`, `page.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`, `template.tsx`.
|
|
||||||
|
|
||||||
<!-- /applied/templates-generation -->
|
|
||||||
## Templates & Code Generation
|
|
||||||
|
|
||||||
Template tools, syntax, and examples for code generation.
|
|
||||||
|
|
||||||
<!-- /applied/styles -->
|
|
||||||
## Styles
|
|
||||||
|
|
||||||
CSS writing rules: PostCSS Modules, nesting, media queries, variables, formatting.
|
|
||||||
|
|
||||||
<!-- /applied/images-sprites -->
|
|
||||||
## Images
|
|
||||||
|
|
||||||
<!-- /applied/svg-sprites -->
|
|
||||||
## SVG Sprites
|
|
||||||
|
|
||||||
<!-- /applied/video -->
|
|
||||||
## Video
|
|
||||||
|
|
||||||
<!-- /applied/api -->
|
|
||||||
## API
|
|
||||||
|
|
||||||
<!-- /applied/stores -->
|
|
||||||
## Stores
|
|
||||||
|
|
||||||
<!-- /applied/hooks -->
|
|
||||||
## Hooks
|
|
||||||
|
|
||||||
<!-- /applied/fonts -->
|
|
||||||
## Fonts
|
|
||||||
|
|
||||||
<!-- /applied/localization -->
|
|
||||||
## Localization
|
|
||||||
File diff suppressed because it is too large
Load Diff
153
notes
Normal file
153
notes
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
ФЛОУ
|
||||||
|
- после создания компонента, заменить шаблонный коментарий документа на реальный.
|
||||||
|
|
||||||
|
|
||||||
|
Проблема, неочевидность слоев (наследие FSD)
|
||||||
|
|
||||||
|
|
||||||
|
Архитектурные слои проекта
|
||||||
|
Каждый нижний слой не знает о существовании верхних. Импорты идут только сверху вниз.
|
||||||
|
pages → layouts → screens → widgets → features → entities → shared
|
||||||
|
---
|
||||||
|
1. Pages (pages/)
|
||||||
|
Точка входа маршрута. Только связывает 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/ —
|
||||||
|
|
||||||
|
|
||||||
|
И еще размышлений
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Что у нас по факту
|
||||||
|
Мы взяли 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).
|
||||||
528
package-lock.json
generated
528
package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "nextjs-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",
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docs": "node ./concat-md.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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user