diff --git a/projects/slm-design/canons/architecture/index.md b/projects/slm-design/canons/architecture/index.md index 70c0bca..88d8f85 100644 --- a/projects/slm-design/canons/architecture/index.md +++ b/projects/slm-design/canons/architecture/index.md @@ -4,6 +4,7 @@ description: Назначение архитектуры, ключевые пр --- # SLM Design + Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили. ## Разделы спецификации @@ -13,18 +14,16 @@ Scoped Layered Module Design — модульная архитектура фр - [Слои](/architecture/layers) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя. - [Модули](/architecture/modules) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента. - [Сегменты](/architecture/segments) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов. -- [Монорепозитории](/architecture/monorepo) — применение SLM в `apps/` и `packages/`, правила выноса общих слоёв и ограничения для business. +- [Монорепозитории](/architecture/monorepo) — применение SLM в `apps/` и `packages/`, правила выноса общих слоёв и ограничения для business/compositions. Рекомендуемый порядок чтения: обзор → слои → модули → сегменты → монорепозитории. -## AI Skill - -Готовый self-contained skill для Claude Code и opencode можно скачать как архив: [slm-design.skill.zip](/slm-design/skill/slm-design.skill.zip). - -Архив можно распаковать в корень другого проекта. Он добавит рабочие файлы `.claude/skills/slm-design/SKILL.md` и `.opencode/skills/slm-design/SKILL.md`. - ## Преимущества +### Единый слой композиции + +Страницы, маршруты и крупные продуктовые части интерфейса собираются в `compositions`. Слой не навязывает жёсткую структуру: команда может использовать `pages/layouts/screens/widgets` или другую организацию под свой фреймворк и продукт. + ### Вертикальная организация домена Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы. @@ -35,27 +34,27 @@ Cross-domain зависимости в бизнес-слое реализуют ### Разделение ответственности без перегрузки слоёв -Сервисы приложения (`infra/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода. +Композиция приложения (`compositions/`), сервисы приложения (`infra/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — разные слои с разной природой. Ни один слой не превращается в свалку разнородного кода. + +### Графовая композиция там, где она нужна + +Внутри `compositions` допускается граф импортов через публичный API. Это позволяет page-level store, provider или business composition использовать одновременно в layout, screen и widget, не перенося продуктовый runtime-state в `infra` или `shared`. ### Горизонтальная инкапсуляция -Вложенные модули (`parts/`) и направление зависимостей позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга. +Вложенные модули (`parts/`) и публичные API позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга. ### Колокация по умолчанию Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями. -### Явное разделение каркаса и контента - -Каркас группы маршрутов (`layouts/`) и контент конкретной страницы (`screens/`) — независимые слои с собственной ответственностью. - ### Масштабирование через группировку -При росте проекта слои не теряют структуру — модули группируются по естественным признакам: бизнес-домены по субдоменам, страницы по разделам, UI-компоненты по уровню абстракции (примитивы и композиции). +При росте проекта слои не теряют структуру — модули группируются по естественным признакам: композиции по страницам и маршрутам, бизнес-домены по субдоменам, UI-компоненты по уровню абстракции. ### Адаптация к монорепозиториям -SLM применяется внутри каждого приложения, а `packages/*` используются только для общего кода из слоёв `ui`, `infra` и `shared`. Бизнес-домены остаются внутри приложений, чтобы не размывать продуктовые границы. +SLM применяется внутри каждого приложения, а `packages/*` используются только для общего кода из слоёв `ui`, `infra` и `shared`. `compositions` и бизнес-домены остаются внутри приложений, чтобы не размывать продуктовые границы. ## Происхождение @@ -72,20 +71,20 @@ SLM Design вырос на основе: src/ ├── app/ │ -├── layouts/ -│ ├── main/ -│ └── dashboard/ -│ -├── screens/ -│ ├── home/ -│ ├── products/ -│ ├── product-detail/ -│ └── about/ -│ -├── widgets/ -│ ├── page-heading/ -│ ├── hero-section/ -│ └── promo-banner/ +├── compositions/ +│ ├── pages/ +│ │ ├── home/ +│ │ ├── profile/ +│ │ └── product-detail/ +│ ├── layouts/ +│ │ ├── main/ +│ │ └── dashboard/ +│ ├── screens/ +│ │ ├── home/ +│ │ └── profile/ +│ └── widgets/ +│ ├── page-heading/ +│ └── promo-banner/ │ ├── business/ │ ├── auth/ @@ -114,7 +113,10 @@ src/ ## Принципы -- **Домен — единое целое.** Всё, что относится к домену, живёт в одном модуле. +- **Композиция — отдельный слой.** Страницы, маршруты и крупные продуктовые части интерфейса собираются в `compositions`. +- **Структура композиции свободна.** Команда сама выбирает организацию внутри `compositions`; базовая рекомендация — `pages/layouts/screens/widgets`. +- **Домен — единое целое.** Всё, что относится к домену, живёт в одном business-модуле. - **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости. -- **Зависимости однонаправлены.** Импорты только сверху вниз, только через публичный API. -- **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда. +- **Зависимости однонаправлены за пределами compositions.** Глобальная стрелка: `app → compositions → business → infra → ui → shared`. +- **Внутри compositions допустим граф.** Composition modules могут импортировать друг друга через public API. +- **Архитектура — каркас, не клетка.** Правила фиксируют границы ответственности и public API, а внутреннюю форму композиции определяет команда. diff --git a/projects/slm-design/canons/architecture/layers.md b/projects/slm-design/canons/architecture/layers.md index 92e7438..d6286e0 100644 --- a/projects/slm-design/canons/architecture/layers.md +++ b/projects/slm-design/canons/architecture/layers.md @@ -9,7 +9,7 @@ description: Иерархия слоёв от app до shared, правила з ## Определение -**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.** +**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.** ## Группы слоёв @@ -17,7 +17,7 @@ description: Иерархия слоёв от app до shared, правила з | Группа | Слои | Описание | |--------|------|----------| -| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы | +| Композиция | `app`, `compositions` | Подключают приложение к фреймворку и собирают страницы, маршруты и крупные продуктовые части интерфейса | | Ядро | `business`, `infra`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит | | Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги | @@ -25,20 +25,24 @@ description: Иерархия слоёв от app до shared, правила з Любой импорт между модулями — только через публичный API. -``` -app → [ layouts | screens ] → widgets → business → infra → ui → shared +```text +app → compositions → business → infra → ui → shared ``` -- `layouts` и `screens` — параллельные слои, не импортируют друг друга -- Модули одного слоя в группе «Композиция» изолированы друг от друга -- Модули одного слоя `infra` и `ui` могут импортировать друг друга через публичный API -- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую +- `app` подключает приложение к фреймворку и импортирует готовые модули из нижних слоёв +- `compositions` импортирует `business`, `infra`, `ui`, `shared` +- `business` импортирует `infra`, `ui`, `shared` +- `infra` импортирует `infra`, `ui`, `shared` +- `ui` импортирует `ui` и `shared` +- `shared` не импортирует другие SLM-слои +- `business`, `infra`, `ui`, `shared` не импортируют `compositions` +- Внутри `compositions` направление импортов между composition modules не фиксируется, но импорты разрешены только через публичный API +- Модули `business` используют runtime-зависимости на другие домены только через фабрику, `import type` — напрямую - Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях - ## Слой App -Точка входа приложения. Отвечает за запуск, роутинг и композицию маршрутов из layout и screen. +Точка входа приложения. Отвечает за запуск, роутинг и подключение composition modules к фреймворку. В отличие от остальных слоёв, `app/` не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации. @@ -46,90 +50,77 @@ app → [ layouts | screens ] → widgets → business → infra → ui → shar - Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация - Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов -- Провайдеры и гарды — только подключает готовые из нижних слоёв, не реализует +- Провайдеры, guards, layouts, screens и страницы — только подключает готовые из `compositions` или нижних слоёв, не реализует - Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы - Никем не импортируется -## Слой Layouts +## Слой Compositions -Каркас страницы: общие элементы, одинаковые для группы маршрутов (header, footer, sidebar). +`compositions/` — слой сборки страниц, маршрутов и крупных продуктовых частей интерфейса. + +На этом слое собираются page, layout, screen, widget и другие composition modules. Они связываются между собой и с нижними слоями: `business`, `infra`, `ui`, `shared`. + +SLM не фиксирует жёсткую структуру внутри `compositions`. Команда выбирает организацию под фреймворк, роутинг, CMS и продуктовую задачу. + +Базовая рекомендация: ```text -src/layouts/ -├── main/ -├── dashboard/ -└── auth/ +src/compositions/ +├── pages/ +├── layouts/ +├── screens/ +└── widgets/ +``` + +`pages`, `layouts`, `screens` и `widgets` внутри `compositions` не являются отдельными SLM-слоями. Это типы композиционных модулей. + +Composition module может содержать обычные сегменты SLM: `ui/`, `parts/`, `hooks/`, `stores/`, `services/`, `mappers/`, `types/`, `styles/`, `lib/`, `config/`, `providers/`. + +Page-level store, provider, guard или business composition размещаются внутри page composition module, если они нужны всей странице. + +```text +compositions/pages/profile/ +├── profile.page.tsx +├── profile-business-composition.ts +├── providers/ +├── hooks/ +├── stores/ +├── types/ +└── index.ts +``` + +Layout, screen и widget могут получать данные page composition через публичный API соответствующего composition module. + +```ts +import { useProfilePageStore } from '@/compositions/pages/profile' +``` + +Внутри `compositions` направление импортов между composition modules не фиксируется. Допустим граф, но все импорты идут только через public API. + +```ts +// Хорошо +import { useProfilePageStore } from '@/compositions/pages/profile' + +// Плохо +import { useProfilePageStore } from '@/compositions/pages/profile/hooks/use-profile-page-store.hook' ``` ### Требования -- Содержит только модули -- Не содержит бизнес-логику -- Контекстно-зависимые блоки принимает через пропсы от `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 +- `compositions` содержит composition modules страниц, маршрутов и крупных продуктовых частей интерфейса +- Структура внутри `compositions` выбирается командой +- Базовая рекомендация: `pages/`, `layouts/`, `screens/`, `widgets/` +- `pages`, `layouts`, `screens`, `widgets` внутри `compositions` не являются отдельными SLM-слоями +- Providers, stores, guards и business composition размещаются внутри того composition module, которому они принадлежат +- Внутри `compositions` импорты между composition modules разрешены в любую сторону, но только через public API +- Deep imports внутрь composition modules запрещены +- `business`, `infra`, `ui` и `shared` не импортируют `compositions` ## Слой Business Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами. -Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`. Каждый бизнес-модуль создаёт публичный runtime API через фабрику в корне. Cross-domain зависимости: runtime — через аргументы фабрики, типы — напрямую через `import type`. +Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`. Каждый бизнес-модуль создаёт публичный API фабрики в корне. Cross-domain зависимости: runtime — через аргументы фабрики, типы — напрямую через `import type`. Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки. @@ -160,7 +151,7 @@ src/business/ - Один модуль = один бизнес-домен - Циклические зависимости между доменами запрещены -- Публичный runtime API — через фабрику в корне модуля (`{name}.factory.ts`). `index.ts` экспортирует только фабрику и type-only экспорты +- Публичный API фабрики — через фабрику в корне модуля (`{name}.factory.ts`). `index.ts` экспортирует только фабрику и type-only экспорты - Импорт runtime-кода между доменами — через фабрику. `import type` — напрямую - Доменные типы (`User`, `Product`) живут здесь, не в `shared/` @@ -187,6 +178,7 @@ src/infra/ - Один модуль = один техсервис - Импортирует `infra/`, `ui/`, `shared/` +- Не содержит продуктовые composition modules конкретных страниц или маршрутов ## Слой UI @@ -252,3 +244,4 @@ src/shared/ ### Требования - Не имеет runtime-состояния +- Не знает о продуктовых composition modules diff --git a/projects/slm-design/canons/architecture/modules.md b/projects/slm-design/canons/architecture/modules.md index 0447c95..e92deaf 100644 --- a/projects/slm-design/canons/architecture/modules.md +++ b/projects/slm-design/canons/architecture/modules.md @@ -1,6 +1,6 @@ --- title: Модули -description: Структура модуля, типы (UI, бизнес, инфра), публичный API, отличие модуля от компонента +description: Структура модуля, типы (композиционный, UI, бизнес, инфра), публичный API, отличие модуля от компонента --- # Модули @@ -13,7 +13,7 @@ description: Структура модуля, типы (UI, бизнес, инф Модуль может содержать всё, что нужно этой области: компоненты, вложенные модули, хуки, сторы, сервисы, типы, стили, конфиги и утилиты. Набор сегментов не фиксирован — модуль включает только то, что реально нужно. -Модуль не обязан быть UI-блоком. Это может быть страница, виджет, бизнес-домен, инфраструктурный сервис или UI-kit сущность. +Модуль не обязан быть UI-блоком. Это может быть page composition, layout composition, screen composition, widget composition, бизнес-домен, инфраструктурный сервис или UI-kit сущность. Главная граница модуля — не папка, а ответственность. @@ -63,30 +63,62 @@ auth/ Примеры модулей: -- `screens/home/` — модуль страницы. -- `widgets/page-heading/` — модуль виджета. +- `compositions/pages/home/` — модуль page composition. +- `compositions/layouts/main/` — модуль layout composition. +- `compositions/screens/profile/` — модуль screen composition. +- `compositions/widgets/page-heading/` — модуль widget composition. - `business/auth/` — модуль бизнес-домена. - `infra/theme/` — модуль инфраструктурного сервиса. - `ui/button/` — модуль UI-kit сущности. -- `screens/home/parts/hero-section/` — вложенный модуль страницы. +- `compositions/pages/home/parts/hero-section/` — вложенный модуль page composition. Не считаются модулями: -- `ui/`, `parts/`, `hooks/`, `types/`, `styles/`, `config/` — это сегменты. -- `screens/shop/`, `business/commerce/` — это группы, если в них нет `index.ts`. -- `screens/home/ui/user-card/` — это компонент, если он находится в `ui/` и соблюдает ограничения компонента. +- `ui/`, `parts/`, `hooks/`, `types/`, `styles/`, `config/`, `providers/` — это сегменты. +- `compositions/pages/`, `business/commerce/` — это группы, если в них нет `index.ts`. +- `compositions/pages/home/ui/user-card/` — это компонент, если он находится в `ui/` и соблюдает ограничения компонента. ## Типы модулей Тип модуля определяет обязательный корневой файл и стартовую структуру. +### Композиционный модуль + +Композиционный модуль — модуль внутри `compositions`, который участвует в сборке страниц, маршрутов и крупных продуктовых частей интерфейса. + +Он может быть page, layout, screen, widget, block, entry-point, CMS-entry, route segment или другим типом композиции, выбранным командой. + +```text +compositions/pages/profile/ +├── profile.page.tsx +├── profile-business-composition.ts +├── providers/ +├── hooks/ +├── stores/ +├── parts/ +├── types/ +└── index.ts +``` + +Композиционный модуль может импортировать другие composition modules через public API. Это отличие слоя `compositions`: внутри него допускается графовая композиция. + +При этом deep imports запрещены. + +```ts +// Хорошо +import { useProfilePageStore } from '@/compositions/pages/profile' + +// Плохо +import { useProfilePageStore } from '@/compositions/pages/profile/hooks/use-profile-page-store.hook' +``` + ### UI-модуль Модуль строится вокруг основного UI-компонента и обязан иметь основной `.tsx` файл в корне: ```text -header/ -├── header.tsx +button/ +├── button.tsx └── index.ts ``` @@ -94,7 +126,7 @@ header/ ### Бизнес-модуль -Бизнес-модуль — модуль, который строится вокруг публичного runtime API. +Бизнес-модуль — модуль, который строится вокруг публичного API фабрики. Бизнес-модуль обязан иметь фабрику в корне: @@ -105,7 +137,7 @@ auth/ └── types/ ``` -Фабрика возвращает публичный runtime API модуля. +Фабрика возвращает публичный API модуля для использования в runtime. ### Инфраструктурный модуль @@ -140,6 +172,7 @@ backend-api/ ├── {module-name}.tsx # корневой файл модуля (опционален) ├── ui/ # компоненты модуля ├── parts/ # вложенные модули +├── providers/ # провайдеры модуля ├── hooks/ # хуки ├── stores/ # сторы состояния ├── services/ # внешние источники данных @@ -184,32 +217,49 @@ export type { CustomerDeps } from './types/customer-deps.type' export type { CustomerFactory } from './types/customer-factory.type' ``` +Composition module экспортирует через `index.ts` только безопасный контракт, который нужен другим composition modules или `app`: page/layout/screen/widget, provider, hooks доступа, типы. Внутренние stores, context objects и функции создания состояния не экспортируются без необходимости. + +Если layout, screen или widget импортируют hooks из page composition, не смешивайте в одном public API готовую page composition и hooks для дочерних модулей: это может создать runtime-цикл. + +```ts +// compositions/pages/profile/index.ts +export { ProfilePageProvider } from './providers/profile-page.provider' +export { useProfilePageStore } from './hooks/use-profile-page-store.hook' +export { useProfileBusinessComposition } from './hooks/use-profile-business-composition.hook' + +export type { ProfilePageState } from './types/profile-page-state.type' +``` + ## Фабрика -Business-модуль всегда экспортирует фабрику. Фабрика лежит в корне модуля (`{name}.factory.ts`), типизируется через `{Name}Factory` и возвращает публичный runtime API модуля. +Business-модуль всегда экспортирует фабрику. Фабрика лежит в корне модуля (`{name}.factory.ts`), типизируется через `{Name}Factory` и возвращает публичный API фабрики. Всё, что нужно внешнему коду в runtime, должно быть частью API, который возвращает фабрика. Модуль без cross-domain зависимостей экспортирует фабрику без аргументов. Модуль с зависимостями — фабрику, принимающую зависимости доменными именами. Типы всегда экспортируются напрямую через `export type` — `import type` не является runtime-зависимостью. -Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция». +Компоновка фабрик происходит в модуле-потребителе на слое `compositions`. ### Примеры Пример реализации фабрики в React см. в [Создание фабрики](/examples/react/factory). -Пример композиции фабрик в React screen-модуле см. в [Композиция фабрик](/examples/react/factory-composition). +Пример композиции фабрик в React composition module см. в [Композиция фабрик](/examples/react/factory-composition). -Пример композиции фабрик через React Provider см. в [Композиция через Provider](/examples/react/composition-provider). +Пример page-level Provider в React см. в [Композиция через Provider](/examples/react/composition-provider). + +Примеры разных структур слоя `compositions` см. в [Структуры compositions](/examples/react/composition-structures). ## Жизненный цикл Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности. -- Нужен на одной странице → `screens/{name}/parts/` -- Появился в 2+ местах → поднимается по природе: - - абстрактный UI → `ui/` - - блок с данными/логикой → `widgets/` - - представление бизнес-домена → `business/{area}/parts/` +- Нужен одной странице, route branch или крупной продуктовой части интерфейса → внутри соответствующего composition module. +- Нужен нескольким частям одной страницы → внутри page composition или другого общего composition scope. +- Нужен нескольким страницам или маршрутам → отдельный composition module внутри `compositions`. +- Абстрактный UI без бизнес-логики → `ui/`. +- Представление или сценарий бизнес-домена → `business/{domain}/`. +- Технический сервис → `infra/`. +- Общая чистая утилита → `shared/`. Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность. diff --git a/projects/slm-design/canons/architecture/monorepo.md b/projects/slm-design/canons/architecture/monorepo.md index 0078180..6e99f2e 100644 --- a/projects/slm-design/canons/architecture/monorepo.md +++ b/projects/slm-design/canons/architecture/monorepo.md @@ -21,9 +21,7 @@ repo/ │ ├── web/ │ │ └── src/ │ │ ├── app/ -│ │ ├── layouts/ -│ │ ├── screens/ -│ │ ├── widgets/ +│ │ ├── compositions/ │ │ ├── business/ │ │ ├── infra/ │ │ ├── ui/ @@ -78,17 +76,17 @@ packages/shared/ ## Что остаётся в приложении -Слои `app`, `layouts`, `screens`, `widgets` и `business` остаются внутри конкретного приложения. +Слои `app`, `compositions` и `business` остаются внутри конкретного приложения. ```text apps/web/src/app/ -apps/web/src/layouts/ -apps/web/src/screens/ -apps/web/src/widgets/ +apps/web/src/compositions/ apps/web/src/business/ ``` -`app`, `layouts` и `screens` привязаны к роутингу, каркасу и страницам конкретного приложения. `widgets` не выносятся в пакеты, потому что это слой композиции интерфейса приложения. +`app` привязан к фреймворку и entry points приложения. + +`compositions` привязан к страницам, маршрутам и крупным продуктовым частям интерфейса конкретного приложения. Этот слой не выносится в `packages/*`, потому что отражает продуктовую сборку приложения. `business` не выносится в `packages/*`. Домены остаются рядом со сценариями приложения, чтобы не превращать монорепозиторий в общий бизнес-слой. @@ -193,7 +191,7 @@ packages -/→ apps Внутри приложения продолжает действовать обычное направление зависимостей SLM. ```text -app → [ layouts | screens ] → widgets → business → infra → ui → shared +app → compositions → business → infra → ui → shared ``` Пакеты не должны нарушать природу своей группы: `packages/ui/*` не импортирует `packages/infra/*`, `packages/shared` не импортирует другие группы, а `packages/infra/*` не знает о приложениях. @@ -206,11 +204,11 @@ app → [ layouts | screens ] → widgets → business → infra → ui → shar ```text # Плохо -apps/web/src/screens/home/parts/promo-section/ +apps/web/src/compositions/pages/home/parts/promo-section/ packages/ui/promo-section/ ``` -Если блок нужен только одной странице или отражает продуктовую композицию конкретного приложения, он остаётся локальным `parts/`-модулем. +Если блок нужен только одной странице или отражает продуктовую композицию конкретного приложения, он остаётся локальным composition module. ## Конфигурационные пакеты @@ -227,7 +225,7 @@ packages/ui/promo-section/ - В `packages/infra/*` размещаются пакеты самостоятельных инфраструктурных модулей. - `packages/shared` является единым пакетом для переиспользуемых утилит и helpers. - Модуль можно размещать в пакете, если он потенциально будет использоваться в двух и более фронтенд-приложениях. -- `business`, `app`, `layouts`, `screens`, `widgets` не выносятся в пакеты. +- `app`, `compositions` и `business` не выносятся в пакеты. - Проектные стили, типы приложения и продуктовые конфиги не выносятся в `packages/shared`. - Пакеты не импортируют приложения. - Межпакетные импорты идут только через публичный API. diff --git a/projects/slm-design/canons/architecture/segments.md b/projects/slm-design/canons/architecture/segments.md index 86cdea2..a315a13 100644 --- a/projects/slm-design/canons/architecture/segments.md +++ b/projects/slm-design/canons/architecture/segments.md @@ -1,6 +1,6 @@ --- title: Сегменты -description: Сегменты внутри модуля (ui/, model/, lib/ и др.), назначение и правила размещения файлов +description: Сегменты внутри модуля (ui/, parts/, hooks/ и др.), назначение и правила размещения файлов --- # Сегменты @@ -17,6 +17,7 @@ description: Сегменты внутри модуля (ui/, model/, lib/ и д |---------|------------| | `ui/` | Презентационные компоненты родительского модуля | | `parts/` | Вложенные модули со своими сегментами | +| `providers/` | Провайдеры модуля | | `hooks/` | React-хуки | | `stores/` | Сторы состояния | | `services/` | Работа с внешними источниками данных | @@ -26,6 +27,8 @@ description: Сегменты внутри модуля (ui/, model/, lib/ и д | `lib/` | Утилиты и хелперы модуля | | `config/` | Константы и конфигурация | +Сегменты не являются обязательными. Например, `providers/` нужен только модулю, который владеет провайдерами. Если provider, store или guard относится к конкретной странице или маршруту, он размещается внутри соответствующего composition module, а не в `infra` или `shared`. + ## Сегмент ui/ Презентационные компоненты родительского модуля. `ui/` содержит только компоненты, которые отвечают за отображение части интерфейса и не выходят за границы своего модуля. @@ -72,7 +75,7 @@ user/ Вложенные модули со своими сегментами. `parts/` содержит только модули: каждый элемент `parts/` — папка полноценного модуля с собственным публичным API. Отдельные `.tsx`, стили, хуки или произвольные файлы в `parts/` не размещаются. ```text -home/ +compositions/pages/home/ ├── parts/ │ ├── hero-section/ │ │ ├── hero-section.tsx @@ -86,7 +89,7 @@ home/ │ ├── features-section.tsx │ ├── hooks/ │ └── index.ts -├── home.screen.tsx +├── home.page.tsx └── index.ts ``` @@ -96,6 +99,18 @@ home/ Если вложенный модуль обрастает своими `parts/` — это сигнал, что он достаточно самостоятельный для подъёма на уровень выше. +## Сегмент providers/ + +Провайдеры модуля: React Context providers, провайдеры scope-состояния, провайдеры композиции фабрик или другие обёртки, которые принадлежат модулю. + +```text +providers/ +├── profile-page.provider.tsx +└── profile-business-composition.provider.tsx +``` + +Provider размещается в том модуле, который владеет соответствующим состоянием или композицией. Page-level provider живёт в page composition module; application-level provider, завязанный на фреймворк, подключается в `app`, но реализуется в нижнем подходящем слое. + ## Сегмент hooks/ React-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты. @@ -117,6 +132,8 @@ stores/ └── session.store.ts ``` +Стор размещается в модуле-владельце. Если состояние нужно всей странице, оно живёт в page composition module. Если состояние относится к бизнес-домену, оно живёт в business-модуле. + ## Сегмент services/ Работа с внешними источниками данных: API-вызовы, запросы, подписки. diff --git a/projects/slm-design/canons/examples/react/composition-provider.md b/projects/slm-design/canons/examples/react/composition-provider.md index d66ee4f..d7345a5 100644 --- a/projects/slm-design/canons/examples/react/composition-provider.md +++ b/projects/slm-design/canons/examples/react/composition-provider.md @@ -1,249 +1,286 @@ --- title: Композиция через Provider -description: Пример композиции бизнес-фабрик screen-модуля через React Provider +description: Пример page-level Provider для composition modules в React-проекте --- # Композиция через Provider -Раздел показывает, как screen-модуль может получить готовую композицию бизнес-доменов через React Context, не вызывая фабрики внутри себя. +Раздел показывает, как page composition может владеть provider, store и business composition, которые нужны layout, screen и другим composition modules. ## Идея -Screen получает готовый API бизнес-доменов через React Context. Граф фабрик собирается снаружи, например в роутере, а внутренние `parts/` достают нужные домены через хук без пропс-дриллинга. +Page composition хранит состояние и композицию бизнес-доменов на уровне страницы. Layout и screen не импортируют друг друга: они получают доступ к page-level данным через публичный API page composition. + +В примере page composition владеет scope-контрактом страницы, но не экспортирует готовый `ProfilePage`, потому что layout и screen импортируют hooks из `pages/profile`. Дерево страницы собирается в `app` или в отдельном entry-point composition module. ## Принципы -1. **Принадлежность.** Provider, Context и хук принадлежат конкретному screen-модулю и лежат в его сегментах. -2. **Внутренний тип.** Тип композиции не экспортируется наружу — это закрывает доступ из бизнес-модулей. -3. **Внутренний хук.** Хук доступа не экспортируется — доступен только внутри screen и его `parts/`. -4. **Публичный Provider.** Только Provider экспортируется через `index.ts`, чтобы роутер мог обернуть screen. -5. **Сборка снаружи.** Граф фабрик собирается в роутере или другом композиторе, screen фабрики не вызывает. -6. **Запрет для бизнеса.** Бизнес-модули не используют провайдеры композиции. Cross-domain зависимости передаются только через аргументы фабрики. +1. **Владение.** Page-level store, provider и business composition принадлежат page composition module. +2. **Обычные сегменты.** Provider, hooks, stores и types лежат в обычных сегментах модуля: `providers/`, `hooks/`, `stores/`, `types/`. +3. **Публичный контракт.** Page composition экспортирует только безопасные hooks, provider и типы, которые нужны другим composition modules или `app`. +4. **Сборка снаружи business.** Business-модули не используют page-level providers. Cross-domain зависимости передаются только через аргументы фабрики. +5. **Без deep imports.** Layout и screen импортируют hooks только из public API page composition. -## Структура модуля +## Структура модулей ```text -screens/main/ -├── main.screen.tsx +compositions/pages/profile/ +├── profile-business-composition.ts ├── providers/ -│ └── main-composition.provider.tsx +│ └── profile-page.provider.tsx ├── hooks/ -│ └── use-main-composition.hook.ts +│ ├── use-profile-page-store.hook.ts +│ └── use-profile-business-composition.hook.ts +├── stores/ +│ └── profile-page.store.ts ├── types/ -│ └── main-composition.type.ts -├── parts/ -│ └── featured-products/ -│ ├── featured-products.tsx -│ └── index.ts +│ └── profile-page-state.type.ts +└── index.ts + +compositions/layouts/profile-main/ +├── profile-main.layout.tsx +└── index.ts + +compositions/screens/profile/ +├── profile.screen.tsx └── index.ts ``` -Сегмент `providers/` — расширение стандартного набора SLM. Спецификация это разрешает: команда сама определяет, какие сегменты используются. +## Тип состояния страницы -## Распределение по сегментам - -| Файл | Сегмент | Назначение | -|------|---------|------------| -| `main-composition.type.ts` | `types/` | TypeScript-тип композиции | -| `main-composition.provider.tsx` | `providers/` | Context и Provider-компонент | -| `use-main-composition.hook.ts` | `hooks/` | React-хук доступа | -| `main.screen.tsx` | корень | Корневой компонент screen-модуля | -| `featured-products/` | `parts/` | Вложенный модуль со своим публичным API | - -## Тип композиции - -Файл: `screens/main/types/main-composition.type.ts`. - -Тип описывает, какие бизнес-домены доступны на этой странице. Он не экспортируется через `index.ts`, чтобы другие модули не зависели от внутренней формы композиции screen. +Файл: `compositions/pages/profile/types/profile-page-state.type.ts`. ```ts -import type { CatalogApi } from '@/business/catalog' -import type { CartApi } from '@/business/cart' - -export type MainComposition = { - catalog: CatalogApi - cart: CartApi +export type ProfilePageState = { + title: string + isSidebarOpen: boolean + setSidebarOpen: (value: boolean) => void } ``` -## Context и Provider +## Store страницы -Файл: `screens/main/providers/main-composition.provider.tsx`. +Файл: `compositions/pages/profile/stores/profile-page.store.ts`. -Context — внутренняя деталь Provider, наружу он не экспортируется. Значение `null` по умолчанию нужно, чтобы хук мог проверить отсутствие Provider в дереве. +```ts +import { createStore } from 'zustand/vanilla' +import type { ProfilePageState } from '../types/profile-page-state.type' -Provider-компонент экспортируется через `index.ts`. Роутер передаёт в `value` уже собранный граф фабрик со стабильной ссылкой. +export const createProfilePageStore = () => + createStore((set) => ({ + title: 'Profile', + isSidebarOpen: false, + setSidebarOpen: (value) => set({ isSidebarOpen: value }), + })) +``` + +`createProfilePageStore` не экспортируется через public API модуля. Это внутренняя деталь создания состояния. + +## Business composition страницы + +Файл: `compositions/pages/profile/profile-business-composition.ts`. + +```ts +import { authFactory } from '@/business/auth' +import { profileFactory } from '@/business/profile' + +export const createProfileBusinessComposition = () => { + const auth = authFactory() + const profile = profileFactory({ auth }) + + return { auth, profile } +} +``` + +Business composition собирается на слое `compositions`, а не внутри business-модулей. + +## Provider страницы + +Файл: `compositions/pages/profile/providers/profile-page.provider.tsx`. ```tsx -import { createContext, type ReactNode } from 'react' -import type { MainComposition } from '../types/main-composition.type' +import { createContext, useRef, type ReactNode } from 'react' +import type { StoreApi } from 'zustand/vanilla' +import { createProfileBusinessComposition } from '../profile-business-composition' +import { createProfilePageStore } from '../stores/profile-page.store' +import type { ProfilePageState } from '../types/profile-page-state.type' -export const MainCompositionContext = createContext(null) +type ProfileBusinessComposition = ReturnType + +type ProfilePageProviderValue = { + store: StoreApi + business: ProfileBusinessComposition +} + +export const ProfilePageContext = createContext(null) type Props = { - value: MainComposition children: ReactNode } -export const MainCompositionProvider = ({ value, children }: Props) => ( - - {children} - -) +export const ProfilePageProvider = ({ children }: Props) => { + const valueRef = useRef(null) + + if (!valueRef.current) { + valueRef.current = { + store: createProfilePageStore(), + business: createProfileBusinessComposition(), + } + } + + return ( + + {children} + + ) +} ``` -## Хук доступа +Context object остаётся технической деталью provider и не должен использоваться внешними модулями напрямую. Наружу экспортируются hooks доступа. -Файл: `screens/main/hooks/use-main-composition.hook.ts`. +## Hooks доступа -Хук остаётся внутренним и не экспортируется через `index.ts` модуля. Он доступен только внутри screen и его `parts/`. - -Если хук используется вне Provider, он бросает ошибку. Это даёт раннюю диагностику неправильной композиции дерева. +Файл: `compositions/pages/profile/hooks/use-profile-page-store.hook.ts`. ```ts import { useContext } from 'react' -import { MainCompositionContext } from '../providers/main-composition.provider' +import { useStore } from 'zustand' +import { ProfilePageContext } from '../providers/profile-page.provider' +import type { ProfilePageState } from '../types/profile-page-state.type' + +export const useProfilePageStore = (selector: (state: ProfilePageState) => T) => { + const ctx = useContext(ProfilePageContext) -export const useMainComposition = () => { - const ctx = useContext(MainCompositionContext) if (!ctx) { - throw new Error('useMainComposition must be used within MainCompositionProvider') + throw new Error('useProfilePageStore must be used within ProfilePageProvider') } - return ctx + + return useStore(ctx.store, selector) } ``` -## Сборка графа в роутере +Файл: `compositions/pages/profile/hooks/use-profile-business-composition.hook.ts`. -Файл: `app/router.tsx`. +```ts +import { useContext } from 'react' +import { ProfilePageContext } from '../providers/profile-page.provider' -Роутер или другой композитор собирает граф фабрик в точке использования screen. Каждый домен получает свои зависимости через аргументы фабрики. +export const useProfileBusinessComposition = () => { + const ctx = useContext(ProfilePageContext) -Фабрики вызываются вне React-компонента, если не зависят от runtime-параметров. Так API доменов не пересоздаётся на каждый рендер route-компонента. + if (!ctx) { + throw new Error('useProfileBusinessComposition must be used within ProfilePageProvider') + } -```tsx -import { MainScreen, MainCompositionProvider } from '@/screens/main' -import { catalogFactory } from '@/business/catalog' -import { cartFactory } from '@/business/cart' -import { authFactory } from '@/business/auth' - -const auth = authFactory() -const catalog = catalogFactory() -const cart = cartFactory({ auth }) - -const MainRoute = () => ( - - - -) + return ctx.business +} ``` -## Корневой компонент screen +## Layout использует page-level store -Файл: `screens/main/main.screen.tsx`. - -Screen получает нужные домены из композиции и достаёт из API готовые хуки, компоненты или функции. В JSX используются уже локальные `useCategories` и `CategoryList`, а не обращение к фабричному API через точку. +Файл: `compositions/layouts/profile-main/profile-main.layout.tsx`. ```tsx -import { useMainComposition } from './hooks/use-main-composition.hook' -import { FeaturedProducts } from './parts/featured-products' +import type { ReactNode } from 'react' +import { useProfilePageStore } from '@/compositions/pages/profile' -export const MainScreen = () => { - const { catalog } = useMainComposition() - const { useCategories, CategoryList } = catalog - const categories = useCategories() +type Props = { + children: ReactNode +} + +export const ProfileMainLayout = ({ children }: Props) => { + const title = useProfilePageStore((state) => state.title) + const isSidebarOpen = useProfilePageStore((state) => state.isSidebarOpen) return ( -
- - +
+
{title}
+
{children}
) } ``` -## Вложенный part +Layout импортирует hook из public API page composition. Он не импортирует screen и не лезет во внутренние файлы `pages/profile`. -Файл: `screens/main/parts/featured-products/featured-products.tsx`. +## Screen использует business composition -Вложенный модуль получает доступ к той же композиции родительского screen. Промежуточные компоненты не прокидывают домены через props. - -Из API доменов достаются готовые сущности: `useFeatured`, `ProductCard` и `addItem`. Компонент работает с ними напрямую. +Файл: `compositions/screens/profile/profile.screen.tsx`. ```tsx -import { useMainComposition } from '../../hooks/use-main-composition.hook' +import { useProfileBusinessComposition } from '@/compositions/pages/profile' -export const FeaturedProducts = () => { - const { catalog, cart } = useMainComposition() - const { useFeatured, ProductCard } = catalog - const { addItem } = cart - const products = useFeatured() +export const ProfileScreen = () => { + const { profile } = useProfileBusinessComposition() + const { useCurrentProfile, ProfileCard } = profile + const currentProfile = useCurrentProfile() + return +} +``` + +Screen получает готовые доменные API из page composition и не собирает граф фабрик самостоятельно. + +## Публичный API page composition + +Файл: `compositions/pages/profile/index.ts`. + +```ts +export { ProfilePageProvider } from './providers/profile-page.provider' +export { useProfilePageStore } from './hooks/use-profile-page-store.hook' +export { useProfileBusinessComposition } from './hooks/use-profile-business-composition.hook' + +export type { ProfilePageState } from './types/profile-page-state.type' +``` + +Внутренние `createProfilePageStore`, `createProfileBusinessComposition` и `ProfilePageContext` не экспортируются через public API. + +Если нужен готовый `ProfilePage`, его лучше собрать в отдельном entry-point composition module или прямо в роутере. Не смешивайте в одном public API и готовую page composition, и hooks, которые импортируют её дочерние layout/screen modules: это может создать runtime-цикл. + +## Подключение в app + +В React Router можно собрать дерево прямо в route config: + +```tsx +import { ProfilePageProvider } from '@/compositions/pages/profile' +import { ProfileMainLayout } from '@/compositions/layouts/profile-main' +import { ProfileScreen } from '@/compositions/screens/profile' + +export const profileRoute = { + path: '/profile', + element: ( + + + + + + ), +} +``` + +В Next App Router композиция может быть физически разложена по файлам `app`, но реализация остаётся в `compositions`. + +```tsx +// app/(profile)/layout.tsx +import { ProfilePageProvider } from '@/compositions/pages/profile' +import { ProfileMainLayout } from '@/compositions/layouts/profile-main' + +export default function Layout({ children }: { children: React.ReactNode }) { return ( -
- {products.map((product) => ( - addItem(product.id)} - /> - ))} -
+ + {children} + ) } ``` -Файл: `screens/main/parts/featured-products/index.ts`. +```tsx +// app/(profile)/page.tsx +import { ProfileScreen } from '@/compositions/screens/profile' -```ts -export { FeaturedProducts } from './featured-products' +export default function Page() { + return +} ``` -## Публичный API screen-модуля - -Файл: `screens/main/index.ts`. - -Наружу экспортируются только screen и его Provider. `MainComposition`, `MainCompositionContext` и `useMainComposition` остаются деталями реализации. - -```ts -export { MainScreen } from './main.screen' -export { MainCompositionProvider } from './providers/main-composition.provider' -``` - -## Почему тип композиции не экспортируется - -Внутренний тип закрывает доступ к форме композиции из внешних модулей. Бизнес-модуль не должен знать, какие домены собраны для конкретного screen. - -Такой импорт из бизнес-модуля не должен быть возможен через публичный API screen. - -```ts -import type { MainComposition } from '@/screens/main' -``` - -Когда тип остаётся внутренним, такая связь невозможна через публичный API screen-модуля. - -## Почему хук не экспортируется - -Если хук доступа сделать публичным, любой модуль сможет вызвать его напрямую. Внутренний хук доступен только через относительные импорты внутри screen-модуля и его `parts/`. - -## Почему Provider экспортируется - -Provider безопасно экспортировать: сам по себе он не даёт доступ к данным, а только принимает готовую композицию и передаёт её детям внутри React-дерева. - -## Стабильность value - -Фабрики создаются на уровне модуля, поэтому `catalog` и `cart` сохраняют ссылки между рендерами `MainRoute`. - -Если домены зависят от runtime-параметров, граф нужно собирать в отдельном композиторе для этих параметров и передавать в Provider уже готовую композицию. - -## Расширение на другие screen-модули - -Паттерн повторяется для каждого screen, которому нужна композиция бизнес-доменов. - -```text -screens/checkout/providers/checkout-composition.provider.tsx -screens/checkout/hooks/use-checkout-composition.hook.ts -screens/checkout/types/checkout-composition.type.ts -``` - -Имена включают имя screen-модуля. Не используйте универсальные названия вроде `useComposition` или `useScope`: по имени файла должно быть понятно, к какой странице привязан Context. +`app` размещает готовые composition modules по правилам фреймворка, но не реализует их внутри себя. diff --git a/projects/slm-design/canons/examples/react/composition-structures.md b/projects/slm-design/canons/examples/react/composition-structures.md new file mode 100644 index 0000000..6bb0db2 --- /dev/null +++ b/projects/slm-design/canons/examples/react/composition-structures.md @@ -0,0 +1,73 @@ +--- +title: Структуры compositions +description: Примеры организации слоя compositions под разные способы сборки React-приложения +--- + +# Структуры compositions + +Раздел показывает, что SLM не фиксирует жёсткую структуру внутри `compositions`. Команда выбирает организацию под фреймворк, роутинг, CMS и продуктовую задачу. + +## Базовая рекомендация + +Подходит для большинства приложений, где есть явные страницы, layouts, screens и переиспользуемые композиционные блоки. + +```text +src/compositions/ +├── pages/ +│ ├── home/ +│ └── profile/ +├── layouts/ +│ ├── main/ +│ └── dashboard/ +├── screens/ +│ ├── home/ +│ └── profile/ +└── widgets/ + ├── page-heading/ + └── promo-banner/ +``` + +`pages`, `layouts`, `screens` и `widgets` здесь не являются отдельными SLM-слоями. Это типы composition modules внутри одного слоя `compositions`. + +## Entry-points и blocks + +Подходит для проектов, где точка сборки не всегда является страницей: CMS registry, embedded UI, route entries, feature entries. + +```text +src/compositions/ +├── entry-points/ +│ ├── cms-profile/ +│ └── embedded-checkout/ +├── pages/ +│ └── profile/ +├── layouts/ +│ └── profile-main/ +├── screens/ +│ └── profile/ +└── blocks/ + ├── profile-summary/ + └── recommended-products/ +``` + +## Группировка вокруг продукта + +Подходит, когда удобнее держать все части одной крупной области рядом. + +```text +src/compositions/ +└── profile/ + ├── page/ + ├── layout/ + ├── screen/ + └── blocks/ +``` + +## Главное правило + +Любая структура допустима, если соблюдаются границы слоя: + +- `app` подключает готовые composition modules к фреймворку. +- `compositions` может импортировать `business`, `infra`, `ui`, `shared`. +- `business`, `infra`, `ui`, `shared` не импортируют `compositions`. +- Импорты между composition modules идут только через public API. +- Deep imports внутрь composition modules запрещены. diff --git a/projects/slm-design/canons/examples/react/factory-composition.md b/projects/slm-design/canons/examples/react/factory-composition.md index d1a5997..b1da24c 100644 --- a/projects/slm-design/canons/examples/react/factory-composition.md +++ b/projects/slm-design/canons/examples/react/factory-composition.md @@ -1,27 +1,29 @@ --- title: Композиция фабрик -description: Пример композиции business-фабрик на уровне screen-модуля в React-проекте +description: Пример композиции business-фабрик на уровне composition module в React-проекте --- # Композиция фабрик -Раздел показывает, как собрать API нескольких business-модулей в React screen-модуле. Пример подходит для простой композиции, когда screen сам является точкой использования доменов. +Раздел показывает, как собрать API нескольких business-модулей в React composition module. Пример подходит для простой композиции, когда page composition сама является точкой использования доменов. ## Идея -Композиция фабрик выполняется в модуле-потребителе: screen, layout или другом модуле группы «Композиция». Business-модули не импортируют runtime-код друг друга напрямую, а cross-domain зависимости получают только через аргументы фабрик. +Композиция фабрик выполняется в модуле-потребителе на слое `compositions`: page, layout, screen, widget или другом composition module. -## Структура screen-модуля +Business-модули не импортируют runtime-код друг друга напрямую, а cross-domain зависимости получают только через аргументы фабрик. + +## Структура page composition ```text -screens/home/ -├── home.screen.tsx +compositions/pages/home/ +├── home.page.tsx └── index.ts ``` ## Сборка фабрик -Файл: `screens/home/home.screen.tsx`. +Файл: `compositions/pages/home/home.page.tsx`. ```tsx import { customerFactory } from '@/business/customer' @@ -32,7 +34,7 @@ const order = orderFactory({ customer }) const { useOrder, OrderCard } = order -export const HomeScreen = () => { +export const HomePage = () => { const currentOrder = useOrder() return @@ -41,12 +43,12 @@ export const HomeScreen = () => { `customerFactory` создаётся первой, потому что `orderFactory` зависит от части API домена `customer`. Модуль `order` не импортирует `customer` в runtime — зависимость передаётся снаружи. -## Публичный API screen-модуля +## Публичный API page composition -Файл: `screens/home/index.ts`. +Файл: `compositions/pages/home/index.ts`. ```ts -export { HomeScreen } from './home.screen' +export { HomePage } from './home.page' ``` -Screen экспортирует только собственный публичный API. Собранные экземпляры business API остаются деталями реализации screen-модуля. +Page composition экспортирует только собственный публичный API. Собранные экземпляры business API остаются деталями реализации модуля. diff --git a/projects/slm-design/canons/examples/react/factory.md b/projects/slm-design/canons/examples/react/factory.md index 94622a6..3487dd7 100644 --- a/projects/slm-design/canons/examples/react/factory.md +++ b/projects/slm-design/canons/examples/react/factory.md @@ -5,7 +5,7 @@ description: Пример создания фабрики business-модуля # Создание фабрики -Раздел показывает, как оформить фабрику business-модуля в React-проекте: описать публичный API, зависимости и функцию, возвращающую runtime API. +Раздел показывает, как оформить фабрику business-модуля в React-проекте: описать публичный API, зависимости и функцию, возвращающую API фабрики. ## Структура business-модуля @@ -25,7 +25,7 @@ business/customer/ ## Тип публичного API -Публичный API описывает runtime-возможности, которые модуль отдаёт потребителям: хуки, компоненты и сценарные методы. +Публичный API описывает возможности, которые модуль отдаёт потребителям через фабрику: хуки, компоненты и сценарные методы. ```ts // business/customer/types/customer-api.type.ts diff --git a/projects/slm-design/docs/docs.config.ts b/projects/slm-design/docs/docs.config.ts index 19ead4d..d0970ac 100644 --- a/projects/slm-design/docs/docs.config.ts +++ b/projects/slm-design/docs/docs.config.ts @@ -22,6 +22,7 @@ export const mounts = [ { target: 'examples/react/factory.md', source: 'canons/examples/react/factory.md' }, { target: 'examples/react/factory-composition.md', source: 'canons/examples/react/factory-composition.md' }, { target: 'examples/react/composition-provider.md', source: 'canons/examples/react/composition-provider.md' }, + { target: 'examples/react/composition-structures.md', source: 'canons/examples/react/composition-structures.md' }, ]; export const sidebar = [ @@ -41,6 +42,7 @@ export const sidebar = [ { text: 'Создание фабрики', link: '/examples/react/factory' }, { text: 'Композиция фабрик', link: '/examples/react/factory-composition' }, { text: 'Композиция через Provider', link: '/examples/react/composition-provider' }, + { text: 'Структуры compositions', link: '/examples/react/composition-structures' }, ], }, ];