Как стать автором
Обновить

Комментарии 35

По поводу FSD согласен, вещь довольно сырая и судя по сайту вообще заброшенная. Не понимаю зачем куча людей её себе тащут. Видимо, из-за отсутствия каких-то конкретных альтернатив.

Но в целом статья скорее не понравилась. Мне ни о чём не говорит пример со SpecificComponent. В таких случаях нужны конкретные практические примеры и лучше на каком-нибудь gitlab/github. Покажите пример c регистрацией, онбордингом и каким-нибудь чатиком на тех же WebSockets, просто чтобы тому же джуну было понятно.

Мне ни о чём не говорит пример со SpecificComponent.

Задайте конкретный вопрос, я отвечу

В целом, я согласен с тем, что в больших командах FSD ведет к боли. Это замедляет онбординг сотрудников, потому что это не стандарт, и в итоге получается мешанина из разных разночтений. Складывается ощущение, что все должно быть проще, чем в FSD, но сложнее, чем у вас.

Касательно предложенной реализации, почему shared это отдельный модуль? Хочется видеть модуль независимым элементом, в который не протекают другие модули, чтобы не было циклической зависимости. Слой shared должен быть утилитарным, то есть элементы дизайн системы, вспомогательные функции, композоблы, директивы и все такое, что лишено груза решения бизнес задач. Плюс очко FSD, где слой shared стоит особняком.

Далее стоит очертить границы понимания модуля. Как мне видится, это не страница и не их группа. Модуль - это определенный блок, реализующий бизнес логику в рамках фронтенда, например виджет калькулятора ипотеки на сайте банка или виджет супер-юзера, появляющийся вообще на всех страницах и подключающийся на корневом уровне. Если вы согласны с таким утверждением, то стоит разделить роутинг и модули, чтобы получить более универсальный конструктор. Как вариант, вынести все страницы в отдельный слой entrypoints (называйте как хотите, но такой термин более понятен, нежели views или pages). По своей структуре entrypoint похож на модуль, но с большими полномочиями, в нем также могут быть свои сам-модули, привязанные к конкретной странице/группе страниц. В случае необходимости такие саб-модули легко поднимаются в modules и шарятся на другие страницы.

Также я бы предложил вынести api в слой shared, так как это банально дешевле в плане разработки. Бизнес требования к одному модулю могут меняться, появляется необходимость дергать разные апи-роуты, которые уже реализованы на бэкенде, но в вашем случае они привязаны к одному модулю. Также такой способ позволяет перейти на автогенерацию апи-клиентов и их типизаций.

И наконец про любимые сторы. В целом, страничный стор это скользкая дорожка, ведущая к перепотреблению памяти и, иногда, плохо читаемому коду. Такие сторы имеют привычку сильно разрастаться, в них теряется драгоценный контекст и нарушается принцип единичной ответственности. Что-то может выйти за рамки страничного стора, тогда нужно выносить логику в какой-то другой глобальный стор, чтобы не стереть данные случайно. Компоненты начинают смотреть в несколько сторов, в общем код превращается в спагетти. Например, вы разрабатывали приложение для управления задачами и использовали страничный стор. Со временем стор начал разрастаться из-за добавления новых функций, таких как фильтрация, сортировка и уведомления. Компоненты стали зависеть от нескольких сторов, в том числе глобальных, что усложнило код и сделало его трудным для поддержки. В таком случае решением может стать TanStack/Query, который управляет состоянием и кэшированием серверных данных, упрощая код, позволяя сосредоточиться на бизнес логике, а не на хранении данных, кэшированием и инвалидацией состояния стора.

Хотелось бы еще посмотреть на структуру тестов, как разделяются разные виды тестов, как шарятся в них моки/стабы и тому подобное.

Подведу итог, вам плюсик в карму за попытку систематизировать подход к построению стабильных и больших фронтенд приложений! Как мне кажется, мы, js-сообщество, еще только на середине этого тернистого пути к построению идеального фронтенд-конструктора, слишком много разного бэкграунда. Пока оптимальным решением я вижу такую структуру: Tests->App->Entrypoints->Modules->Shared, вышестоящий слой может пользоваться всеми нижестоящими.
Своим комментарии я хочу лишь подчеркнуть возможные проблемы и найти их решения, вполне возможно, что и эти подходы имеют существенные минусы, которые я не заметил со своей колокольни.

Спасибо за ответ!

Со сторами у нас организована выгрузка. Этим мы решаем проблему по памяти. Ну и чисто организационная составляющая -- в стор выносим только то, что реально нужно шарить. Где-то получается лучше, где-то хуже. Вариантов пока все равно не нашли.

Модуль shared именно отдельный модуль, поскольку его структура практически идентична всем остальным. Это переиспользуемая другими компонентами логика/ui-компоненты. По идее, никаких циклических зависимостей не должно быть, т.к. shared не может использовать никакие другие модули, а они его могут. В целом, предложение понятное. Надо покрутить его в голове.

Что касается границ. В общечеловеческом смысле проведение границ -- это отдельное искусство. Вторая логика, так сказать. Формулирование границ и ее реализация зависит от целей и некой системы ценностей человека. Возвращаясь к коду, мы границы провели так, поскольку нам это показалось удобным и отвечало нашим потребностям, представлениям о том, как было бы удобно работать с кодом. И в итоге мы убедились, что для нас это сработало. Когда модуль стал описывать раздел сайта, папка с роутами стала фактически его интерфейсом, или точкой импорта модуля, который позволял собирать все модули в одном месте -- создание роутера. Роут модуля уже импортирует компоненты-части страницы. Сердце приложения -- main.ts -- собирает модули в роуты и использует при создании vue-приложения. Переназови мы modulues в entrypoints, это, возможно, убрало бы некое противоречие, но сути бы не изменило.

Вынесение роутов из модулей побудило бы нас создать (если я вас правильно понял) какое-то одно место, где бы формировались роуты. Это ничем не отличается от того, что есть сейчас в классической. Это место пришлось бы также отдельно структурировать, чтобы все это не болталось в одном файле, а структура скорее всего повторяла бы структуру entrypoints. Либо я идею не так понял.

Вынести api в shared мы тоже рассматривали. Отказались от этого, увидев еще на старте надвигающийся ком методов, которые снова пришлось бы структурировать. В модулях они уже находятся там, где нужно. Так что, отказались от этой идеи.

Если я правильно понял, модулем вы предлагаете называть только бизнес-логику. Даже если он будет содержать ui-компоненты. Для меня модуль -- это некая максимально самостоятельная архитектурная единица. Грубо говоря, это то, что можно вынести в отдельный репозиторий (кстати тот же shared вполне мог бы стать общей либой для остальных модулей-репозиториев). Слишком намельчить в модуле и проект сразу становится сложно осознаваемым. Модулей станет очень много, а пользоваться ими все равно будут совершенно конкретные страницы.

Тесты. Тесты у нас только unit, лежат, как я показал в папке с компонентами (либо, когда у нас, скажем, папка /helpers, лежат в /helpers/__tetsts__) Моки специфичны для каждого теста. Особо тут нечего рассказать.

Спасибо еще раз за ответ. Подумаем над предложениями!

Докину немного про разделение entrypoints/modules/shared.
Entrypoints - интерфейс сайта, точки входа, разделы, страницы. Entrypoint имеет свой роутинг, собирает свои страницы с помощью других модулей и базовых компонентов. Modules - переиспользуемые блоки, закрывающие бизнес-задачи, например карточка товара, появляющаяся на многих страницах и меняющая стор корзины, или модалка чата с поддержкой, который обычно расположен в правом нижнем углу.
Shared - переиспользуемые блоки без бизнес-логики, фундамент приложения, на базе которого строится все остальное.
Мне видится это очень естественным, такая модель понятна и читаема, но она по-сути мало на что влияет, в конечном счете это просто папки.
Если в вашем случае удобнее хранить и разделять все в одном месте, то пусть будет так.

Хочу остановиться на api и спросить: что вы делаете в случае надобности apiClient одного модуля в другом? Импортируете или копируете?
Я вижу тут сложность в том, что интерфейс сервера не копирует интерфейс сайта, поэтому такое разделение выглядит натянутым. К тому же сейчас набирает популярность подход автогенерации апи клиентов и их типов, например tRPC.

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

Shared - переиспользуемые блоки без бизнес-логики, фундамент приложения, на базе которого строится все остальное.

Можете привести пример структуры такого модуля?

По поводу FSD. Мы сами для себя решили, что есть энтити, что фича. Например, фича- это все то, что взаимодействует с пользователем, кнопки, поля ввода и т.п. Когда кнопки и поля объединяются - это уже виджет. Энтити - компоненты, отображающие данные из api, без взаимодействия с пользователем. Страницу стараемся компоновать из виджетов.

По поводу модулей. Мы вынесли модули в отдельные npm-либы. Это резко упростило разработку, количество мерж конфликтов, тк каждый разработчик работает над своим модулем. В главном приложении по сути только подключаются эти либы.

В больших проектах к этому так или иначе приходишь. 👍

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

Для чего нужен Pinio в vue3 ? Понимаю его использование в vue2, но в vue3 добавили реактивность на любой компонент. И получается компактный файл без внешних зависимостей например jobs.js вроде:

const state = reactive({
jobs: []
});

const actions = {
addJob(job) {
state.jobs.push(job);
},
};

export default {
state: state,
...actions,
};

Какие преимущества у данного подхода перед пиньей кроме того, что у нас нет внешней зависимости? Стор пиньи легко просматривать в средствах разработчика, поскольку они регистрируются, сразу видно, какие сторы в данный момент присутствуют на странице. Также стор пиньи легко выгружать из памяти. Как избавиться от этой конструкции? Однажды импортированный модуль такого дизайна остается в памяти на все время работы приложения

store.$dispose();

export function disposePinia(pinia: Pinia) {
  pinia._e.stop()
  pinia._s.clear()
  pinia._p.splice(0)
  pinia.state.value = {}
  // @ts-expect-error: non valid
  pinia._a = null
}

Также стор пиньи легко выгружать из памяти. Как избавиться от этой конструкции? Однажды импортированный модуль такого дизайна остается в памяти на все время работы приложения


Что мешает сделать то же самое в предложенном варианте - обнулить все реактивные переменные модуля?

Вы это серьезно? ))))))) даже на первый взгляд есть пара значимых отличий.

Впрочем, каждому своё. Если вам так удобнее, настаивать не буду

upd: но самое главное "Зачем?" Чем самописный хук будет лучше стора пиньи?

Хех.. разрабатываешь продвинутые библиотеки, пишешь подробные статьи, записываешь разъясняющие видео.. а люди всё равно спорят как лучше говнокодить..

А что если

.. "сторы" будут сами лениво создаваться при необходимости и автоматически уничтожаться, когда никому больше не нужны?

Если есть конкретный пример, буду признателен, если вы уточните, о чем речь. Пока не уверен, что правильно понимаю идею.

https://mol.hyoo.ru/#!section=docs/=4o989b_9d4gjg

Выглядит так, будто вы предлагаете мне использовать ваш фреймворк

О чём я и говорю - они даже не читают, ибо нет времени точить топор.

Когда приложение разрастается, появляются новые члены команды разработки

Мне кажется, это и есть основной причиной краха любых фронт-приложений после нескольких лет развития.

Фронтенд приложение - это все-лишь один из клиентов к бизнес-логике (бек + бд). Его должно разрабатывать максимум 3-4 человека, и только один должен быть главный.

И второе условие - разработчику давать не более трёх проектов (на одном он главный и закреплён пожизненно, на остальных двух - не главный, и может перемещаться между проектами). Таким образом решается проблема бас-фактора\ротации\выгорания\обмена знаниями.

Какое бы большое или толстое не было это фронтенд-приложение, нужно по максимуму сопротивляться желанию нанимать больше разработчиков на него. Это будет залогом успеха на любом промежутке времени, при любом архитектурном подходе (даже без тестов и на голом js).

PS. в определения крах и успех пусть каждый вкладывает своё.


Хорошая мысль, но слишком радикальна.
Почему 3-4? Сколько фронтенд-разработчиков должно поддерживать, например, Booking, Reddit или Gitlab? Бизнес может иметь очень много потребностей, и большинство из них требуют работы на фронте тоже, и бизнес не должен упираться в кол-во 4 разработчиков, у которых в сумме всего 160 (после митингов и то меньше) рабочих часов в неделю.
В принципе тезис "чем меньше разработчиков, тем эффективнее" применим не только к фронту, так везде. Поэтому тоже считаю, что каждый разработчик должен иметь свою зону ответственности в приложении, в котором он главный. Так и разрабы растут в скиллах, и работать легче, когда каждый несет какую-то ответственность.

Сколько фронтенд-разработчиков должно поддерживать, например, Booking, Reddit или Gitlab?

3-4 человека..) Другое дело что сейчас состояние кодовой базы такое, что необходимо содержать штат в 100 человек. И чтобы сделать спиннер - нужно три месяца.

Никто ж не знает как бы повернулась история развития фронт-кодовой базы этих проектов, если бы всё-время на них работало не более 3-4 человек. Думаю скорость разработки фич была бы одинаковая и предсказуемая - что 10-ть лет назад, что сейчас.

Существует ли хоть один успешный продукт с широким ui функционалом, который поддерживается 3-4 фронтенд-разработчиками много лет?

Понятия не имею) Как минимум нужно дать определение для "успешный продукт с широким ui".

У меня на прошлой работе было примерно пять фронт-проектов (репозиториев). Возраст от 2 до 10 лет. Достаточно сложные (webrtc, websockets, electron, wasm). Когда я пришёл - там был один выделенный фронтенд-разработчик. Он был главный на всю фронт-часть компании. Ему помогали хардкорные бородатые фуллстеки. Помогали плохо, ибо ж появлялось spa\react и вот это вот всё. Поэтому взяли меня. Потом мы взяли ещё двух. И в таком составе проработал я там около 4.5 лет. Новые фичи разрабатывались предельно комфортно и предсказуемо что вначале, что вконце. Рефакторинг также происходил в фоновом режиме без боли. С моей точки зрения - продукт вполне себе успешный, учитывая известность, зарплаты, размах корпоративов и плюшек в моменты мировых потрясений (ковид, война..).
И как я вижу сейчас - лёгкость и комфортность разработки фронт-части как раз получилось из-за того, что они не раздували штат фронтендеров. Но и никогда не подгоняли палкой со сроками.. продуктовая компания всё-таки.

Вот вам анализ этих "архитектур": https://mol.hyoo.ru/#!section=docs/=nekayw_7ibj8f

На некликабельность ссылок жалуйтесь в спортлото: @Boomburum

Категорически поддерживаю, что группировка по доменам - это лучшее решение. Смотрится естественно.

Поскольку общей точкой сбора компонентов является компонент layout-а, который может быть общим для совершенно разных страниц, пришлось решать, как организовать это горизонтальное взаимодействие. Было несколько вариантов, но в итоге остановились на использовании pinia-сторов, которые создаются под каждую страницу. Проблему накопления этих сторов в памяти решили размонтированием стора при переходе на другую страницу.

И тут можно подробней?

Layout - это шаблон, как располагаются блоки на странице/подстранице. Не должен быть привязан в семантике (бизнес-логике) программы/модуля, хотя может использоваться только одним модулем.

Каким образом сюда цепляются сторы и для чего?

Вы не так поняли идею. К бизнес-логике привязаны компоненты, которые "встраиваются" в layout. Пример лэйаута в тесте статьи, как и роут, который его использует. Так вот, сторы юзаются самими компонентами - header, table, footer

Layout -- просто шаблон, который наполняется этими компонентами, как и должно ему быть

в итоге остановились на использовании pinia-сторов, которые создаются под каждую страницу.

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

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

Есть сторы, которые юзают сторы из shared-модуля. Но это чаще всего исключение, чем правило. Главная задача сторов -- обеспечить взаимодействие модулей одного уровня.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий