Модули
Раздел описывает модуль как границу ответственности в SLM: что считается модулем, что такое компонент внутри модуля и как модуль взаимодействует с остальным кодом.
Определение
Модуль — минимальная архитектурная единица SLM. Он живёт на одном из слоёв, владеет конкретной областью ответственности и предоставляет наружу только публичный API.
Модуль может содержать всё, что нужно этой области: компоненты, вложенные модули, хуки, сторы, сервисы, типы, стили, конфиги и утилиты. Набор сегментов не фиксирован — модуль включает только то, что реально нужно.
Модуль не обязан быть UI-блоком. Это может быть страница, виджет, бизнес-домен, инфраструктурный сервис или UI-kit сущность.
Главная граница модуля — не папка, а ответственность.
Компонент
Компонент — презентационная единица модуля, которая находится только в ui/ своего родительского модуля и отвечает за отображение части интерфейса.
Компонент не является архитектурной единицей: он не владеет сценарием, зависимостями, данными или внутренней структурой. Он работает только внутри границы родительского модуля.
Компонент отображает. Модуль организует.
Компонент не может:
- Импортировать код проекта за пределами родительского модуля.
- Владеть архитектурными зависимостями.
- Содержать любые компоненты.
- Содержать любые модули.
- Делать внешние запросы.
- Самостоятельно получать данные.
- Выбирать источник данных.
- Композировать данные.
- Вызывать сценарные хуки.
- Оркестрировать сценарий.
- Композировать модули.
- Решать, как устроен процесс.
- Содержать бизнес-логику.
- Содержать сценарную логику.
Если компоненту требуется что-то из этого списка, он перестаёт быть компонентом и должен быть оформлен как модуль.
auth/
├── ui/
│ └── logout-button/
│ ├── logout-button.tsx
│ ├── styles/
│ │ └── logout-button.module.css
│ ├── types/
│ │ └── logout-button-props.type.ts
│ └── index.ts
└── index.tsЧто считается модулем
Модулем считается папка, которая представляет самостоятельную область ответственности и имеет публичную границу.
Примеры модулей:
screens/home/— модуль страницы.widgets/page-heading/— модуль виджета.business/auth/— модуль бизнес-домена.infra/theme/— модуль инфраструктурного сервиса.ui/button/— модуль UI-kit сущности.screens/home/parts/hero-section/— вложенный модуль страницы.
Не считаются модулями:
ui/,parts/,hooks/,types/,styles/,config/— это сегменты.screens/shop/,business/commerce/— это группы, если в них нетindex.ts.screens/home/ui/user-card/— это компонент, если он находится вui/и соблюдает ограничения компонента.
Типы модулей
Тип модуля определяет обязательный корневой файл и стартовую структуру.
UI-модуль
Модуль строится вокруг основного UI-компонента и обязан иметь основной .tsx файл в корне:
header/
├── header.tsx
└── index.tsui/ внутри такого модуля используется только для компонентов, которые помогают корневому .tsx файлу.
Бизнес-модуль
Бизнес-модуль — модуль, который строится вокруг публичного runtime API.
Бизнес-модуль обязан иметь фабрику в корне:
auth/
├── auth.factory.ts
├── index.ts
└── types/Фабрика возвращает публичный runtime API модуля.
Инфраструктурный модуль
Инфраструктурный модуль — модуль, который строится вокруг технического сервиса или интеграции.
Инфраструктурный модуль не обязан иметь фиксированный корневой файл. Его структура определяется природой сервиса.
theme/
├── index.ts
├── config/
├── hooks/
├── styles/
└── ui/backend-api/
├── backend-api.client.ts
├── config/
├── types/
└── index.tsСтруктура
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль включает только те части, которые нужны его ответственности.
{module-name}/
├── {module-name}.factory.ts # фабрика (для business-модулей)
├── {module-name}.tsx # корневой файл модуля (опционален)
├── ui/ # компоненты модуля
├── parts/ # вложенные модули
├── hooks/ # хуки
├── stores/ # сторы состояния
├── services/ # внешние источники данных
├── mappers/ # трансформация данных между форматами
├── types/ # типы
├── styles/ # стили
├── lib/ # утилиты модуля
├── config/ # константы и конфигурация
└── index.ts # публичный APIПодробное описание сегментов — в разделе Сегменты.
Публичный API
Внешний код импортирует модуль только через публичный API.
// Хорошо
import { customerFactory } from '@/business/customer'
import type { Customer } from '@/business/customer'// Плохо
import { validateToken } from '@/business/auth/lib/tokens'index.ts модуля не обязан экспортировать всё содержимое. Он экспортирует только то, что действительно нужно снаружи.
Внутренние сегменты модуля остаются деталями реализации.
Business-модуль экспортирует из index.ts только фабрику и type-only экспорты. Хуки, компоненты, сервисы, мапперы и утилиты напрямую из index.ts не экспортируются — они доступны через API, который возвращает фабрика.
// business/customer/index.ts
export { customerFactory } from './customer.factory'
export type { Customer } from './types/customer.type'
export type { CustomerApi } from './types/customer-api.type'
export type { CustomerDeps } from './types/customer-deps.type'
export type { CustomerFactory } from './types/customer-factory.type'Фабрика
Business-модуль всегда экспортирует фабрику. Фабрика лежит в корне модуля ({name}.factory.ts), типизируется через {Name}Factory и возвращает публичный runtime API модуля.
Всё, что нужно внешнему коду в runtime, должно быть частью API, который возвращает фабрика.
Модуль без cross-domain зависимостей экспортирует фабрику без аргументов. Модуль с зависимостями — фабрику, принимающую зависимости доменными именами. Типы всегда экспортируются напрямую через export type — import type не является runtime-зависимостью.
Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция».
Структура business-модуля
business/customer/
├── customer.factory.ts
├── index.ts
└── types/
├── customer.type.ts
├── customer-api.type.ts
├── customer-deps.type.ts
└── customer-factory.type.tsТипы
// business/customer/types/customer-api.type.ts
export type CustomerApi = {
useCustomer: () => Customer
CustomerCard: (props: CustomerCardProps) => ReactNode
}// business/order/types/order-deps.type.ts
export type OrderDeps = {
customer: Pick<CustomerApi, 'useCustomer'>
}// business/order/types/order-factory.type.ts
export type OrderFactory = (deps: OrderDeps) => OrderApiФабрика без зависимостей
// business/customer/customer.factory.ts
import type { CustomerFactory } from './types/customer-factory.type'
export const customerFactory: CustomerFactory = () => {
return {
useCustomer,
CustomerCard,
}
}Фабрика с зависимостями
// business/order/order.factory.ts
import type { OrderFactory } from './types/order-factory.type'
export const orderFactory: OrderFactory = (deps) => {
return {
useOrder,
OrderCard,
}
}Композиция на уровне screen
// screens/home/home.screen.tsx
import { customerFactory } from '@/business/customer'
import { orderFactory } from '@/business/order'
const customer = customerFactory()
const order = orderFactory({ customer })
const { useOrder, OrderCard } = order
export const HomeScreen = () => {
const currentOrder = useOrder()
return <OrderCard order={currentOrder} />
}Жизненный цикл
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
- Нужен на одной странице →
screens/{name}/parts/ - Появился в 2+ местах → поднимается по природе:
- абстрактный UI →
ui/ - блок с данными/логикой →
widgets/ - представление бизнес-домена →
business/{area}/parts/
- абстрактный UI →
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.