Как применять Module Federation для шеринга виджетов из UI-kit
В последнее время все активнее в обиход входит понятие экосистемы. Многие IT-компании предлагают различные цифровые решения для людей и бизнеса под одним брендом - от заказа еды до онлайн-сделок по покупке недвижимости.
Непосредственной составляющей подобных решений в рамках одной экосистемы является общий дизайн. Однако по мере роста компании увеличивается количество элементов экосистемы, и поддержка консистентности UI-составляющей становится непростой задачей.
В ДОМ.РФ мы создаем продукты для рынка недвижимости, в частности предназначенные для электронного взаимодействия между застройщиками, банками и государственными органами. Задачи и цели систем отличаются друг от друга, но их объединяет общий дизайн. Безусловно, создание библиотеки UI-компонентов с нуля в каждом новом сервисе является крайне сомнительной идеей по следующим причинам:
Стоимость разработки заметно увеличиваются;
Сроки разработки также увеличиваются;
Консистентность: возникает проблема поддержки UI в одном виде.
Казалось бы, создание отдельной библиотеки компонентов с вынесением всех UI-элементов в нее с возможностью дальнейшего подключения на проектах решает вышеперечисленные проблемы. Но что делать, если необходимо поддерживать определенные элементы библиотеки в актуальном состоянии на всех проектах одновременно в real-time?
Изображение 1. Виджет Topline
Один из таких элементов в нашей библиотеке компонентов – виджет Topline.
Если кратко, то он представляет собой совокупность некоторого количество компонентов, реализующих некую логику и подразумевающих использование на клиенте “из коробки”.
Но почему бы не вынести виджет в NPM-пакет и подключать его, как остальные элементы из UI-kit? Проблема заключается в том, что, с одной стороны, данный элемент периодически обновляется - например, добавляется новый сервис в боковую панель, который нужно отобразить на всех остальных проектах, или добавляется новый функционал, а с другой стороны, Topline должен быть актуальным абсолютно на всех проектах.
При изменениях в виджете обновлять его вручную на каждом проекте и производить редеплой, возможно, не самая сложная, но весьма трудоемкая задача. Более того, на каждом проекте потребуется вклиниваться с данным обновлением в его релизный цикл. При возникновении различного рода проблем на проекте релизный цикл может быть нарушен, соответственно неизвестно, когда изменения с виджетом доедут по продакшена.
Кроме того существует набор сервисов, активная разработка в рамках которых завершена, и за которыми уже не прикреплены разработчики. В данном случае потребуется найти ресурсы для обновления зависимостей и инициализировать новый релиз, что также отнимет драгоценное время.
Микрофронтенд с Webpack Module Federation
В вебпаке в 5-й версии появился полноценный инструмент для организации микрофронтенда. Подробно останавливаться на понятии микрофронтенда не будем, но если кратко, микрофронтенд – это микросервисный подход при разработке фронтовых приложений. В рамках одного приложения разные компоненты системы представляют собой независимые модули, разработка которых может вестись совершенно разными командами и с помощью разных инструментов.
Как эта технология может решить проблему с нашим виджетом? Одно из основных достоинств данного инструмента – подключение элементов в runtime. Это означает, что мы можем настроить интеграцию виджета таким образом, что при использовании сервиса у нас всегда будет подгружаться актуальный бандл с виджетом Topline.
Более того, в рамках приложения мы можем подключать виджет как самый обыкновенный React-компонент. Таким образом, мы можем передавать в виджет props.
Пример подключения
Подключение в конфиге вебпака нужного модуля:
plugins: [
name: 'eisgs_topline',
remotes: {
eisgs_topline: `eisgs_topline@${process.env.TOPLINE_URL}`,
},
shared: {
react: {
eager: true,
requiredVersion: packageJsonDependencies.react
},
},
]
Как видно из конфига, необходимо, чтобы бандл с виджетом был доступен по определенной ссылке. Соответственно, должен быть стабильный файловый сервер, на котором будет располагаться вышеуказанный бандл.
Также можно указать набор зависимостей с помощью shared, которые будут переданы из клиента в виджет (аналог Peer Dependencies).
На клиенте виджет подключается следующим образом:
<Topline
navConfig={navConfig}
profileConfig={profileConfig}
/>
Подключаться модуль будет асинхронно, поэтому необходимо обернуть его в <Suspense>:
const ToplineModule = lazy(async () => import('eisgs_topline/topline'));
<Suspense fallback={<>Loading…</>}>
<ToplineModule
navConfig={navConfig}
profileConfig={profileConfig}
/>
</Suspense>
Более подробную информацию о подключении можно найти в документации Webpack.
Изображение 2. Виджет Topline в реальном проекте. По сравнению с изображением №1 был добавлен функционал сервиса уведомлений.
Что делать, если возникли проблемы с подключением через федерации?
Так как бандл с виджетом располагается на файловом сервере, крайне желательно обработать сценарий, при котором бандл не будет выгружен по тем или иным причинам.
Для этих целей мы также публикуем виджет Topline в NPM, который подключается, если возникли проблемы с Module Federation:
const ToplineBoundary = () => (
<ToplineErrorBoundary fallback={<ToplineFallback />}>
<ToplineModule />
</ToplineErrorBoundary>
);
При возникновении ошибки ErrorBoundary отрендерит Topline, подключенный через NPM. Стоит учесть, что в таком случае версия виджета будет задана явно через package.json.
Итого
Мы рассмотрели возможности Module Federation для решения проблем, связанных с консистентностью виджета, как можно подключить модуль с помощью данной технологии и как обрабатывать ситуации, при которых виджет не сможет быть загружен и подключен.
Безусловно, у данного решения есть свои недостатки:
Клиент должен собираться Webpack версии 5.0.0 или старше. Другие сборщики не подойдут;
Сервер, который будет раздавать бандл, должен быть стабильным;
Нужно настроить кэшировние бандла;
Особо тщательно следить, чтобы обновления не принесли с собой breaking changes (при передаче props);
Расшерить типы для TS с помощью федераций не получится.
Но несмотря на данные недостатки нами было принято решение использовать Module Federation для шеринга виджетов. Если говорить о Topline, то такой метод используется с весны 2021 года – на данный момент проблем с таким подходом не возникало, если не считать нескольких падений файлового хранилища, хранящего бандл – но на такой случай используется fallback.