Перейти к основному содержимому

Правила Frontend FSD И Atomic Design

Назначение

Документ уточняет применение Feature-Sliced Design (FSD) и Atomic Design в frontend-приложениях Pin на примере pin-admin. Цель — однозначно определить, где какой код лежит, как разделять переиспользуемые компоненты, как описывать слои в документации.

Базовые слои FSD

src/
app/ // entrypoint, providers, theme, router, global config
pages/ // route components, TanStack Router file-based
widgets/ // composite UI fragments (header, sidebar, command palette)
features/ // user-facing actions (login, schema-publish, record-create)
entities/ // client models + API adapters + stores + query keys + selectors
shared/ // ui (atoms/molecules/organisms), lib, hooks, stores, theme, config

Зависимости между слоями

app ← pages ← widgets ← features ← entities ← shared
  • app импортирует из всех нижележащих.
  • pages импортирует из widgets, features, entities, shared.
  • widgets импортируют из features, entities, shared.
  • features импортируют из entities, shared.
  • entities импортируют только из shared.
  • shared ни от кого не зависит.

Обратные импорты запрещены и должны быть пойманы ESLint-конфигом (eslint-plugin-boundaries).

Слой app

СодержимоеПримеры
Корневой компонентApp.tsx, main.tsx
Providers<HeroUIProvider>, <QueryClientProvider>, <ThemeProvider>, <RouterProvider>, <ErrorBoundary>
Глобальные guardsAuthGuard, RoleGuard, TenantContextProvider, FacilityContextProvider
Глобальная конфигурацияEnvironment variables, feature flags
Глобальные стилиindex.css, theme provider css

Слой pages

ПравилоОписание
Файловая структураTanStack Router file-based (например, pages/pin/$catalogId/records.tsx).
Толщина страницыТонкая — только композиция widgets + features + entities + загрузка данных через TanStack Query.
Запрет бизнес-логикиЛюбая логика >5 строк выносится в features/ или widgets/.
Route guardsПрименяются через TanStack Router beforeLoad.

Слой widgets

СодержимоеПримеры
HeaderAppBarTop bar с логотипом, breadcrumbs, user menu, theme toggle
SidebarNavЛевое меню с навигацией по модулям
BreadcrumbsХлебные крошки на основе TanStack Router
CommandPaletteCtrl+K палитра
NotificationsBellBell-иконка с unread count
ThemeToggleПереключатель темы
LanguageSwitcherПереключатель локали
FacilitySwitcherВыбор facility

Правила widgets

  • Виджет — это композиция: использует molecules + features + entities.
  • Не зависит от конкретной страницы; одна и та же реализация на N страниц.
  • Имеет свой документ в 04-frontends/pin-admin/04-widgets/<widget>.md.

Слой features

СодержимоеПримеры
CatalogRecordCreateФорма создания записи с валидацией Zod и mutation
SchemaPublishКнопка + диалог публикации схемы
FilterBuilderКонструктор фильтров для таблицы записей
QrScanОткрытие сканера + резолв QR + действия
FileUploadSignedUrlПолный flow загрузки файла через signed URL
AutofillTestRunЗапуск тестового прогона autofill стратегий

Правила features

  • Feature — это пользовательское действие или бизнес-цель.
  • Содержит: UI-компонент(ы), хук с мутацией/запросом, locale-строки, документацию.
  • Запрещено импортировать features из других features (низшая coupling).

Слой entities

ПодкаталогСодержимое
entities/<entity>/model.tsTypeScript-тип на основе Zod-схемы.
entities/<entity>/api-adapter.tsФункции-обёртки над Axios для CRUD endpoints.
entities/<entity>/store.tsОпционально — Zustand store для UI-state, связанного с этой сущностью.
entities/<entity>/query-keys.tsFactory query keys для TanStack Query.
entities/<entity>/selectors.tsPure functions для деривации view models.
entities/<entity>/index.tsPublic API entity (явный re-export).

Правила entities

  • Одна папка — одна сущность из бэкенда (catalog-definition, catalog-schema, catalog-entry, dictionary, и т.д.).
  • Не содержит UI-компонентов уровня страницы — только мелкие presentational компоненты, специфичные для отображения этой сущности (например, CatalogDefinitionBadge, ChecklistRunStatusPill).
  • Имеет свой документ в 04-frontends/pin-admin/06-entities/<entity>/ (5 файлов: model, api-adapter, store, query-keys, selectors).

Слой shared

shared/
ui/ // переиспользуемые компоненты (плоско); atoms/molecules/organisms — концептуальная классификация, без обязательных подпапок
api/ // сгенерированный API-клиент (ValIO) + хелперы (api-result, paging, invalidation)
lib/ // утилиты, форматтеры, хуки общего назначения

Atomic Design внутри shared/ui/ (баланс)

Atomic Design используется как принцип классификации сложности, а не как обязательная файловая иерархия. shared/ui/ остаётся плоским; компоненты группируются по смыслу (см. актуальный реестр в 07-shared/README.md).

Главный анти-паттерн — «оборачивание ui kit в наш ui kit» (запрещено). Примитивы HeroUI (Button, Input, Select, Chip, Table, Checkbox, Switch, Tooltip, Avatar, Badge, …) используются напрямую из @heroui/react. НЕ создавайте тонкие обёртки-«атомы», которые лишь прокидывают пропсы в HeroUI без добавленной ценности.

Собственный компонент в shared/ui/ оправдан только когда он добавляет ценность: фиксированную композицию, доменно-нейтральную логику, обработку состояний загрузки/ошибки/пустоты, единый layout.

  • Atoms — отдельного слоя нет: примитивы HeroUI используются напрямую.
  • Molecules (композиция + логика) — например: FormField, FormModal, ConfirmDialog, PageHeader, TableRowActions, ListPagination, DataTableCard, ActiveStatusChip, async-селекты (AsyncSingleSelect, AsyncMultiSelect), per-entity lookup-обёртки, document-editors.
  • Organisms (загрузка/состояния данных) — например: DynamicContent/PagedDynamicContent, StateSurface, редактируемая таблица записей каталога, DocumentArrayEditor.

Перед добавлением/изменением UI сверяйтесь с полной документацией HeroUI: .claude/docs/heroui/llms-full.txt (формат <page url="…">).

Atomic Design vs FSD entities

СлойЧто описывает
shared/ui/atomsУниверсальные UI-примитивы без бизнес-смысла
shared/ui/moleculesКомпозиции примитивов без бизнес-смысла
shared/ui/organismsСложные UI-блоки без бизнес-привязки
entities/<entity>Бизнес-сущность из домена (CatalogDefinition, Dictionary, ...)

Если компонент специфичен для конкретной сущности (например, CatalogDefinitionStatusBadge) — он в entities/catalog-definition/ui/. Если универсальный — в shared/ui/.

Именование

ЭлементПравилоПример
ComponentPascalCase папка + файл index.ts или <Component>.tsxButton/index.tsx или Button.tsx
HookcamelCase с префиксом useuseCurrentFacility
Storeuse<Name>StoreuseAuthStore
Selector<entity>Selectors.<derivedView>catalogDefinitionSelectors.viewModel
Query key factory<entity>KeyscatalogDefinitionsKeys.list(facilityId, filters)
Schema<purpose>SchemacreateCatalogDefinitionSchema
DTO type<Name>DtoCatalogDefinitionDto
View model<Name>ViewCatalogDefinitionView
Mutationuse<Action><Entity>MutationuseCreateCatalogDefinitionMutation

Запрещено

  • Импорт из pages/* в widgets/*, features/*, entities/*, shared/* (обратная зависимость).
  • Импорт из widgets/* в features/*, entities/*, shared/*.
  • Импорт из features/* в другие features/*.
  • Импорт shared/ui/organisms напрямую в pages без проксирования через widget/feature, если требуется бизнес-логика.
  • Создание helpers.ts / utils.ts в shared/ без явного назначения. Имя файла = его роль.
  • Использование default export в shared/ui (только named exports).
  • Тонкие обёртки над примитивами HeroUI («ui kit в нашем ui kit») без добавленной ценности — используйте HeroUI напрямую.

ESLint конфигурация

{
"plugins": ["boundaries"],
"settings": {
"boundaries/elements": [
{ "type": "app", "pattern": "src/app/*" },
{ "type": "pages", "pattern": "src/pages/*" },
{ "type": "widgets", "pattern": "src/widgets/*" },
{ "type": "features", "pattern": "src/features/*" },
{ "type": "entities", "pattern": "src/entities/*" },
{ "type": "shared", "pattern": "src/shared/*" }
]
},
"rules": {
"boundaries/element-types": ["error", {
"default": "disallow",
"rules": [
{ "from": "app", "allow": ["pages", "widgets", "features", "entities", "shared"] },
{ "from": "pages", "allow": ["widgets", "features", "entities", "shared"] },
{ "from": "widgets", "allow": ["features", "entities", "shared"] },
{ "from": "features", "allow": ["entities", "shared"] },
{ "from": "entities", "allow": ["shared"] },
{ "from": "shared", "allow": ["shared"] }
]
}]
}
}

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы