Skip to content

Монорепозитории

Раздел описывает, как применять SLM Design, когда фронтенд-проекты находятся в одном монорепозитории. В нём показано, что остаётся внутри приложений, что можно выносить в packages/ и какие ограничения действуют для общих пакетов.

Определение

Монорепозиторий — внешний уровень организации нескольких фронтенд-приложений и общих пакетов. SLM применяется внутри каждого приложения, а frontend-пакеты, относящиеся к SLM, содержат переиспользуемый код, вынесенный из слоёв ui, infra и shared.

Базовая структура

Каждое приложение внутри apps/ сохраняет собственную SLM-структуру в src/.

text
repo/
├── apps/
│   ├── web/
│   │   └── src/
│   │       ├── app/
│   │       ├── layouts/
│   │       ├── screens/
│   │       ├── widgets/
│   │       ├── business/
│   │       ├── infra/
│   │       ├── ui/
│   │       └── shared/
│   └── admin/
│       └── src/
│           └── ...
└── packages/
    ├── ui/
    │   ├── button/        # самостоятельный пакет UI-модуля
    │   ├── input/         # самостоятельный пакет UI-модуля
    │   └── modal/         # самостоятельный пакет UI-модуля
    ├── infra/
    │   ├── theme/         # самостоятельный пакет infra-модуля
    │   ├── backend-api/   # самостоятельный пакет infra-модуля
    │   └── logger/        # самостоятельный пакет infra-модуля
    └── shared/            # единый shared-пакет
        ├── package.json
        └── src/
            ├── lib/       # переиспользуемые утилиты
            ├── helpers/   # переиспользуемые helpers
            └── index.ts

apps/{app}/src — граница SLM-приложения. packages/* находятся выше SLM и не добавляют новые архитектурные слои.

Группировка frontend-пакетов

Frontend-пакеты, вынесенные из SLM-приложений, рекомендуется группировать по источнику кода: ui, infra, shared.

text
packages/ui/*      # пакеты UI-модулей
packages/infra/*   # пакеты infra-модулей
packages/shared    # единый shared-пакет

Эта группировка повторяет названия SLM-слоёв для навигации, но сама не является слоистой архитектурой внутри packages/. Монорепозиторий может содержать другие пакеты: tooling, конфиги, SDK, схемы, e2e и другие технические пакеты вне SLM.

Пакет и модуль

Пакет не равен SLM-модулю: модуль — архитектурная единица внутри слоя приложения, package — единица монорепозитория для переиспользования, владения, сборки и публикации.

В packages/ui/* размещаются пакеты самостоятельных UI-модулей. В packages/infra/* размещаются пакеты самостоятельных инфраструктурных модулей. packages/shared устроен иначе: это единый пакет для переиспользуемых утилит, helpers и другого фундаментального кода без привязки к конкретному приложению.

text
packages/ui/button/
packages/ui/modal/
packages/infra/theme/
packages/infra/backend-api/
packages/shared/

Что остаётся в приложении

Слои app, layouts, screens, widgets и business остаются внутри конкретного приложения.

text
apps/web/src/app/
apps/web/src/layouts/
apps/web/src/screens/
apps/web/src/widgets/
apps/web/src/business/

app, layouts и screens привязаны к роутингу, каркасу и страницам конкретного приложения. widgets не выносятся в пакеты, потому что это слой композиции интерфейса приложения.

business не выносится в packages/*. Домены остаются рядом со сценариями приложения, чтобы не превращать монорепозиторий в общий бизнес-слой.

Что можно выносить

В пакеты выносится только код из ui, infra и shared, который потенциально будет использоваться в двух и более фронтенд-приложениях монорепозитория.

ГруппаЧто выноситьПример
packages/ui/*Самостоятельные UI-модули без бизнес-логикиpackages/ui/button
packages/infra/*Самостоятельные технические сервисыpackages/infra/backend-api
packages/sharedОбщие утилиты, helpers и фундаментальный кодpackages/shared

Пакет можно создавать сразу, если модуль имеет общую природу и ожидается его переиспользование между приложениями. App-specific код остаётся внутри приложения.

UI-пакеты

В packages/ui/* размещаются переиспользуемые UI-модули.

text
packages/ui/button/
├── package.json
└── src/
    ├── button.tsx
    ├── styles/
    ├── types/
    └── index.ts

UI-пакет не содержит бизнес-логику, обращения к API, сценарные хуки приложения и композицию страниц.

Infra-пакеты

В packages/infra/* размещаются переиспользуемые инфраструктурные модули.

text
packages/infra/backend-api/
├── package.json
└── src/
    ├── clients/
    ├── config/
    ├── types/
    └── index.ts

Привязанные к конкретному приложению сервисы остаются в apps/{app}/src/infra. Например, локализация со словарями конкретного продукта остаётся в приложении; общим пакетом может быть только переиспользуемый i18n-движок.

Shared-пакет

packages/shared является единым пакетом.

text
packages/shared/
├── package.json
└── src/
    ├── lib/
    ├── helpers/
    └── index.ts

В packages/shared сразу выносится общий фундаментальный код: чистые функции, helpers, утилиты, независимые константы и другой код без знания о продукте.

Проектные стили, типы приложения, продуктовые конфиги и ресурсы, завязанные на одно приложение, в общий shared не выносятся.

Имена пакетов и импорты

Путь импорта задаётся name в package.json, а не расположением директории.

json
{
  "name": "@repo/theme"
}
text
packages/infra/theme/package.json
ts
import { ThemeProvider } from '@repo/theme'

Пакеты должны импортироваться только через публичный API. Deep imports внутрь пакета запрещены.

ts
// Хорошо
import { Button } from '@repo/button'

// Плохо
import { Button } from '@repo/button/src/button'

Зависимости

На уровне монорепозитория приложения зависят от пакетов, а пакеты не зависят от приложений.

text
apps → packages
packages -/→ apps

Внутри приложения продолжает действовать обычное направление зависимостей SLM.

text
app → [ layouts | screens ] → widgets → business → infra → ui → shared

Пакеты не должны нарушать природу своей группы: packages/ui/* не импортирует packages/infra/*, packages/shared не импортирует другие группы, а packages/infra/* не знает о приложениях.

Когда не выносить

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

Фактическое использование в одном приложении не запрещает пакет, если модуль имеет общую природу и потенциально нужен нескольким приложениям.

text
# Плохо
apps/web/src/screens/home/parts/promo-section/
packages/ui/promo-section/

Если блок нужен только одной странице или отражает продуктовую композицию конкретного приложения, он остаётся локальным parts/-модулем.

Конфигурационные пакеты

Конфигурационные пакеты не относятся к SLM-архитектуре.

Если в монорепозитории есть общие настройки TypeScript, ESLint, сборки или форматирования, они относятся к tooling-инфраструктуре репозитория. Такие пакеты могут находиться в packages/, но их структура зависит от выбранного инструментария и не участвует в правилах слоёв внутри src/.

Правила

  • SLM применяется внутри каждого apps/{app}/src.
  • Frontend-пакеты, вынесенные из SLM-приложений, группируются в packages/ui, packages/infra, packages/shared.
  • Группы packages/ui, packages/infra, packages/shared не являются SLM-слоями.
  • В packages/ui/* размещаются пакеты самостоятельных UI-модулей.
  • В packages/infra/* размещаются пакеты самостоятельных инфраструктурных модулей.
  • packages/shared является единым пакетом для переиспользуемых утилит и helpers.
  • Модуль можно размещать в пакете, если он потенциально будет использоваться в двух и более фронтенд-приложениях.
  • business, app, layouts, screens, widgets не выносятся в пакеты.
  • Проектные стили, типы приложения и продуктовые конфиги не выносятся в packages/shared.
  • Пакеты не импортируют приложения.
  • Межпакетные импорты идут только через публичный API.
  • Deep imports внутрь пакетов запрещены.
  • Локальная колокация важнее преждевременного выноса в packages/*.