Кирилл Мокевнин (Hexlet) набросил, как файлы класть — по типу или по домену?
Для меня меня на фронтенде это вопрос давно решенный: если сервис больше чем 2 странички, то, конечно, по фичам. Иначе, чтобы поправить какую-то мелочь, надо по 5 папкам лазить.
📗 Главный принцип: low coupling, high cohesion
Недавно был на проекте: 90к строк кода, 4 фронтендера. Код разложен по слоям: consts, utils, hooks, api, components, pages. Так там утилит было на ~20к строк (четверть кодовой базы!), в них лежала почти вся логика приложения. В константах лежало 202 файла. Тип пропсов компонента из
src/components/statistics/CohortsTable.tsx лежал в src/types/statistics/CohortsTable.d.ts. Постоянные проблемы с циклическими зависимостями. Ребята генерили новый код, похожий на старый — трудно было найти нужное.Когда в ру-язычной фронтенд-тусовке говорят про vertical slices, вспоминают FSD. Имхо, челы перегибают палку. Зачем деление на widgets/features/entities? Это тащит нас обратно — в low cohesion, high coupling. Поэтому туда не ходим, keep it simple, stupid.
И вот мы пару недель обсуждали, какие есть проблемы, почему больно это поддерживать, почему даже мелкие задачки занимают столько времени. В итоге проблема «хз че где» вышла на первое место по скору ICE (Impact × Confidence × Ease).
Мы выработали структуру и правила. Самое сложное было — выделить доменные сущности, об этом никто раньше не думал. Ещё за неделю раскидали 80% файлов, жить сразу стало легче. Подробнее писал в блоге компании (опубликовали в мой последний день там лол)
В
src осталось три папки:•
app — рутовый компонент, провайдеры•
features — самая соль по доменам•
shared — общие компонентыВ каждой фиче (опционально):
pages, ui, store, api, utils, const, types. При этом если какие-то константы, типы или хелперы нужны только для одного компонента, они идут в папку этого компонента. На уровень фичи поднимается только то, что нужно для нескольких модулей в ней.Один коллега боялся, что пути/будут/вот/такие/длинные/ужас/кошмар. Но
.../ui/Component это последний левел глубины. Внутри компонента всё плоско. Если меньше 10-15 файлов в папке, доп. уровни не нужны:src/features/apps/ui/AppsTable/
AppsTable.tsx
AppsTable.module.css
DesktopRow.tsx
MobileRow.tsx
CellWrapper.tsx
useColumns.tsx
formatter.ts
Дальше учимся давать имена сущностям из вонючего ящика
utils и выделять их из файлов по тыще строк. Типовые куски:•
adapters/mappers — из ответов API в структуру, удобную для фронтенда и обратно — обычно в src/features/<feature>/api•
validators — валидаторы/схемы для формочек — к соотв. компоненту•
formatters — как мы даты форматируем и т.п. — к компонентуВыкидываем barrel-файлы (
index.ts с реэкспортами) из папок, которые не являются модулями:•
src/features/apps/ui/AppsTable — модуль (компонент), кладём index.ts, реэкспортируем «публичный интерфейс» — скорее всего, это сам <AppsTable /> и его пропсы. А всякую внутреннюю шелуху типа форматтеров или useColumns не реэкспортим•
src/shared/routing — модуль, кладём index.ts, реэкспортируем всякие константы маршутов, функции для их построения и тп•
src/features/apps/ui/ — не модуль, а просто папочка организационная👉 Писал про barrel-файлы у себя в блоге.
Откуда я знал, что всё это сработает? Потому что уже строил такую архитектуру на проекте в Яндексе, который был крупнее, и мы жили так пару лет и всё отлично работало и скейлилось. Сейчас, посмотрев на проекты в других компаниях, понимаю, что dev experience на этом проекте был лучшим.
Зачем мы 2 недели обсуждали, если я сразу «знал, как надо»? Чтобы:
• провалидировать, что это решит проблемы именно текущего проекта, и именно самые больные
• адаптировать подход к проекту и команде, срезать углы, что-то улучшить
• ребята вовлеклись и участвовали в принятии решения, люди гораздо лучше делают то, что решили сами, чем что-то навязанное
Кстати, AI-агентам в такой структуре проще ориентироваться.
А вы в каком лагере?
🔥 features first: src/features/<feature>/ui
🤔 layers first: src/ui/<feature>/blabla
⭐️ уже иду рефакторить