Files
svg-sprites/preview/ai/basics/architecture.md
S.Gromov e77e7dfcf1 refactor: заменить shiki на самописный highlighter и обновить архитектуру
- Удалён shiki (9.5→0 МБ), создан regex-токенизатор для html/css/xml
- CLI переведён с аргументов на конфиг-файл svg-sprites.config.ts
- Превью переработано: React-приложение вместо инлайн HTML
- Добавлен футер с названием пакета и ссылкой на репозиторий
- Исправлена загрузка dev-data.js для Vite 8
- Футер прижат к низу, содержимое центрировано
2026-04-22 16:54:35 +03:00

28 KiB
Raw Blame History

title, scope, keywords, when
title scope keywords when
Архитектура basics
SLM Design
слой
модуль
сегмент
архитектура
FSD
scoped layered module
Организация кода: слои, модули, зависимости между модулями

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 — код живёт рядом с местом использования

Пример структуры проекта

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.
  • Архитектура — каркас, не клетка. Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.

Слои

Раздел описывает слои 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).

src/layouts/
├── main/
├── dashboard/
└── auth/

Требования

  • Содержит только модули
  • Не содержит бизнес-логику
  • Контекстно-зависимые блоки принимает через пропсы от app, не импортирует напрямую

Слой Screens

Контент конкретной страницы: собирает её из модулей нижних слоёв.

src/screens/
├── home/
├── products/
├── product-detail/
├── about/
└── contacts/

Когда количество страниц затрудняет навигацию — вводится группировка по разделам. Группа — папка для организации, не модуль (без index.ts).

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.

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/ — составные блоки.

src/business/
├── auth/
├── catalog/
├── orders/
├── checkout/
└── chat/

Когда количество доменов затрудняет навигацию — вводится группировка по субдоменам. Группа — папка для организации, не модуль (без index.ts).

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/ — общие ресурсы (утилиты, хелперы, стили, конфиги).

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.

src/ui/
├── button/
├── input/
├── icon/
├── carousel/
├── modal/
├── toast/
├── dropdown/
├── tabs/
└── tooltip/

Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (button, icon, input) не импортируют композиции. Композиции (carousel, modal, dropdown) строятся на примитивах.

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/, а не здесь.

src/shared/
├── lib/
├── types/
├── styles/
└── sprites/

Требования

  • Не имеет runtime-состояния

Модули

Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом.

Определение

Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.

Модуль vs компонент

Компонент — один .tsx файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или ui/ сегменте модуля.

Модуль — папка, которая может содержать корневой компонент, сегменты (hooks/, types/, styles/, ui/, parts/ и т.д.) и публичный API (index.ts).

auth/                              
├── ui/
│   ├── auth-guard.tsx
│   └── logout-button.tsx
├── parts/
│   ├── login-form/
│   ├── registration-form/
│   └── restore-form/
├── hooks/
├── stores/
├── types/
├── auth.tsx             # корневой компонент (опционален)
└── index.ts

Структура

Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного index.ts с реэкспортом типов.

{module-name}/
├── {module-name}.tsx            # корневой компонент (опционален)
├── ui/                          # компоненты модуля (только .tsx)
├── parts/                       # вложенные модули (со своими сегментами)
├── hooks/                       # хуки
├── stores/                      # сторы состояния
├── services/                    # внешние источники данных
├── mappers/                     # трансформация данных между форматами
├── types/                       # типы
├── styles/                      # стили
├── lib/                         # утилиты модуля
├── config/                      # константы
└── index.ts                     # публичный API

Подробное описание каждого сегмента — в разделе Сегменты.

Публичный API

Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее.

// 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 запрещён:

// Плохо
import { validateToken } from '@/business/auth/lib/tokens'

// Хорошо
import { useAuth } from '@/business/auth'

Фабрика

Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове.

Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — import type не является runtime-зависимостью.

Модуль без зависимостей — прямой экспорт:

// business/auth/index.ts
export { useAuth } from './hooks/use-auth'
export { useCurrentUser } from './hooks/use-current-user'
export type { User, Session } from './types'

Модуль с зависимостями — фабрика:

// business/chat/types/deps.ts
import type { User } from '@/business/auth'

export interface ChatDeps {
  useCurrentUser: () => User | null
}
// 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'

Использование на странице:

// 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/

Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.

Сегменты

Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.

Определение

Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.

Обзор

Сегмент Содержимое
ui/ Компоненты модуля — только .tsx файлы
parts/ Вложенные модули со своими сегментами
hooks/ React-хуки
stores/ Сторы состояния
services/ Работа с внешними источниками данных
mappers/ Трансформация данных между форматами
types/ TypeScript-типы и интерфейсы
styles/ Стили
lib/ Утилиты и хелперы модуля
config/ Константы и конфигурация

Сегмент ui/

Компоненты, принадлежащие модулю. Содержит только .tsx файлы — без своих сегментов, стилей, типов, хуков. Использует сегменты родительского модуля.

auth/
├── ui/
│   ├── auth-provider.tsx
│   ├── auth-guard.tsx
│   └── logout-button.tsx
├── types/
├── hooks/
└── index.ts

Если компоненту нужны собственные сегменты — это уже не ui/, а parts/.

Сегмент parts/

Вложенные модули со своими сегментами. Каждый элемент parts/ — полноценный модуль: папка с компонентом, хуками, стилями, типами и т.д.

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-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты.

hooks/
├── use-auth.hook.ts
├── use-session.hook.ts
└── use-permissions.hook.ts

Сегмент stores/

Сторы состояния модуля. Конкретная реализация зависит от выбранного стейт-менеджера (Zustand, MobX, Redux и т.д.).

stores/
├── auth.store.ts
└── session.store.ts

Сегмент services/

Работа с внешними источниками данных: API-вызовы, запросы, подписки.

services/
├── auth.service.ts
└── token.service.ts

Сегмент mappers/

Функции трансформации данных из одного формата в другой: DTO в доменный тип, доменный тип в DTO, доменный тип в ViewModel.

mappers/
├── map-user.ts
├── map-product.ts
└── map-order-to-dto.ts

Сегмент types/

TypeScript-типы и интерфейсы модуля. Доменные типы, DTO, пропсы компонентов.

types/
├── user.type.ts
└── session.type.ts

Сегмент styles/

Стили модуля. Формат зависит от выбранного подхода (CSS Modules, SCSS, CSS-in-JS и т.д.).

styles/
├── auth.module.css
└── login-form.module.css

Сегмент lib/

Утилиты и хелперы, специфичные для модуля. Чистые функции без побочных эффектов.

lib/
├── validate-email.ts
└── format-phone.ts

Отличие от shared/lib/: здесь лежат утилиты, нужные только этому модулю. Общие утилиты — в shared/lib/.

Сегмент config/

Константы и конфигурация модуля: маршруты, лимиты, дефолтные значения.

config/
├── routes.ts
└── constants.ts