Разделяй и не страдай: что выбрать для микрофронтенда?

2941800d1d4cd1d51ed61ab4a9060141.png

Привет! Меня зовут Алексей. Я занимаюсь проектированием фронтенд-составляющей ИТ-систем в архитектурном комитете SimbirSoft. Последние два-три года во фронтенд-сообществе активно обсуждается и используется термин «микрофронтенд» (далее МФ). Разные компании делятся своими подходами к организации подобного архитектурного решения, но до сих пор в Сети мало описания проблематики, которую призваны решить МФ-ы, критерии их применимости и ограничения в использовании. В этой статье постарался сравнить разные способы организация МФ-ов, а также сформировать рекомендации, где какой подход использовать.

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

Микрофронтовый подход: что это и для чего нужен 

Перед тем как перейдем к определению МФ-а, давайте рассмотрим несколько проблем, которые можно встретить на проектах:

  • У вас большой проект. Величина проекта, как правило, субъективная и может быть эмпирически определена объемом функционала и количеством разработчиков. Если у вас хватает работы, чтобы озадачить 1–2 фронтендеров и они при этом не будут «толкаться локтями» — это маленький проект, 3–6 — средний, больше 6–8 — уже большой.

  • У вас большая команда. Опять же эмпирически это больше 10 фронтендеров, остальные участники не в счет. Как правило, команда такой численности уже может разделиться на подкоманды, которые берут на поддержку конкретный функционал, обзаводятся своими аналитиками, бэкендерами и QA.

  • У вас большой функционал. Отдельно взятый разработчик может поддерживать только кусок кода своей подкоманды. Доработка остального кода может обходиться дорого в силу незнания предметной области или сложности реализации сторонней логики.

Проблемы могут дополнительно усиливаться:

  • желанием сменить стек;

  • требованием компании поддерживать семейство родственных проектов.

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

Эволюция разработки Web-приложенийЭволюция разработки Web-приложений

Таким образом, микрофронтовый подход — разделение монолитного фронта на отдельные кодовые базы, хранящиеся в отдельных репозиториях, к которым есть доступ у отдельных подкоманд. При этом у них могут/должны быть свои демо стенды, тесты и релизный цикл. Соответственно, микрофронт — отделяемый кусок интерфейса. Делить не обязательно постранично, функционал может быть и сквозным (например, корпоративный ui-kit). Отдельно стоит подсветить, что МФ-ы — это больше организационное решение о том, как управлять сложностью разработки в большом проекте. МФ-ы не помогут вам ускорить фронтенд, некоторые реализации наоборот даже замедлят. Но такой подход ускорит саму разработку за счет выделения зон ответственности и изолированного тестирования.

Формирование полноценных команд (front, back, QA) согласно поддерживаемым фичамФормирование полноценных команд (front, back, QA) согласно поддерживаемым фичам

MF vs LL

Отдельно стоит упомянуть Lazy loading в сравнении с МФ-ами. Они решают разные задачи, но люди иногда думают, что это все об одном, ведь в обоих случаях мы «дробим» приложение. 

Lazy loading решает проблему производительности: как не заставлять пользователя грузить весь фронтовый бандл целиком, как не ждать больше, чем нужно, как быстрее запустить фронт на клиенте и скорее начать с ним взаимодействовать.

МФ-ы проблему производительности не решают, а иногда даже усугубляют. Зато они помогают организовать разработку в более комфортном для конкретной подкоманды виде, минимизируя вышеперечисленные проблемы.

Buildtime vs Runtime

Теперь поговорим о подходе объединения МФ-ов в единое приложение. Что бы вы ни выбрали, для пользователя это должно смотреться единым приложением. Объединять можно как на этапе сборки, так и динамически — во время исполнения кода на стороне пользователя. 

Таким образом все способы организации МФ-ов можно отнести к buildtime или runtime. У каждого свои плюсы и минусы.

Как объединяются МФ-ы во время сборки (buildtime) и во время работы уже в браузере на стороне клиента (runtime)Как объединяются МФ-ы во время сборки (buildtime) и во время работы уже в браузере на стороне клиента (runtime)

Buildtime

Runtime

Проверка типов

есть возможность

нет такой возможности

Версионирование

есть возможность

нет имеет смысла

Независимый деплой

нет такой возможности

есть возможность

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

Объединяя микрофронты во время сборки, вы не лишены возможности проверить типы. В случае с рантаймовским объединением придется писать интеграционные тесты, чтобы фронт неожиданно не «взорвался» на проде.

Версионирование и независимый деплой сильно противоречат друг другу:

  1. Версионирование предполагает, что можно взять любую версию МФ другой команды. Это особенно актуально, когда нужно провести дополнительные работы по апгрейду зависимостей МФ-а от других. Каждая команда выбирает более удачное время для апгрейда.

  2. Независимый деплой дает больше автономности и независимости командам. Тут важно всегда использовать последние версии МФ-ов. Это обязывает соблюдать обратную совместимость.

Версионирование можно реализовать и при runtime-объединении, но это нецелесообразно. Связываться с рантаймом имеет смысл только ради независимого деплоя, а последний не может существовать вместе с версионированием.

Далее мы увидим примеры конкретных реализаций каждого подхода объединения МФ-ов.

Способы организации микрофронтенда: особенности, преимущества и недостатки

Iframe

Самый древний способ организации микрофронтов. iframe — это специальный тэг для передачи адреса ресурса, который будет отображен на основном сайте. В итоге получается сайт внутри сайта. 

Схема подключения МФ-а через iframeСхема подключения МФ-а через iframe

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

За эти положительные качества придется заплатить производительностью, поскольку каждая вставка iframe приводит к загрузке ресурсов. Вы не сможете избежать этого: попытки детачить и реатачить DOM-узел не сохранят ранее загруженные ресурсы, придется скачивать их заново. Минимизировать снижение производительности можно путем настройки кэширования. Для этого нужно настроить инвалидацию кэша по времени для иммутабельных ресурсов. К счастью, все современные cli из коробки для собранных js и css файлов крепят небольшой хэш в название. К недостаткам этого способа также отнесем неспособность поисковых роботов рендерить iframe-ы для последующей индексации.

Преимущества

Недостатки

Легкость реализации

Производительность

Изоляция логики и стилей

SEO

Независимый деплой

Отсутствие привязки к фреймворку

Оправданный сценарий применения этого способа — единичная вставка небольшого куска стороннего приложения, написанного на другом стеке, когда нет необходимости в SEO.

WebComponents

Создания нативных компонентов фронтенд-сообщество ждало давно, но в итоге они так и не завоевали массовую популярность, на которую многие рассчитывали. Тройка самых популярных фронтенд-фреймворков (React, Vue, Angular) до сих пор создают компоненты по-своему. 

Компоненты собираются и регистрируются через нативное APIКомпоненты собираются и регистрируются через нативное API

Несмотря на возможность создавать МФ-ы на веб-компонентах, на практике я такое не встречал. И это неспроста, тут есть ряд блокеров:

  • Либы вроде Lit или Stencil недостаточно популярны и распространены. К тому же, на рынке недостаточно специалистов, умеющих работать с ними или готовых научиться.

  • Angular-elements или vue-custom-element остаются экзотикой. В родной среде нет особого смысла их применять. Если уж дробить приложение, то на обычные npm-пакеты, чтобы потом можно было подключить компоненты как либы. Использовать веб-компоненты с другими фреймворками неоправданно, поскольку вместе со сгенеренными компонентами нужно подключать мини-версию фреймворка, на котором они были написаны.

  • Выносить сложные куски функционала в веб-компоненты может быть затратно. Поскольку нужно будет настраивать коммуникацию вашего компонента с остальным приложением, выносить целую страницу в отдельный кастомный компонент может быть неоправданно.

  • Поисковые роботы не могут создать веб-компонент, и это скажется на seo-оптимизации.

Преимущества

Недостатки

Подходит для сквозной функциональности

Сложность реализации

Сочетается с любым фреймворком

SEO

Изоляция логики и стилей

В итоге остается только один достойный кейс применения веб-компонентов в МФ-ах: создание корпоративного UI-kit, который можно было бы применить на всех проектах. При этом неважно, на каком фреймворке написано, поскольку логика и стили изолированы. Использование контролов UI-kit на веб-компонентах не скажется на SEO, поскольку сами контролы не нужны для индексации страниц.

Hidden text

Примечание.

В качестве механизма доставки веб-компонент можно использовать как npm, так и просто деплой на известный url, откуда потом забирать через тег script[src]. В случае npm повышается удобство использования при шаринге между проектами, но теряется независимый деплой. В случае обычного деплоя статики придется выстраивать порядок работы скриптов, а перед использованием кастомных элементов нужно убедиться в их регистрации — например, через механизм CustomElementRegistry.whenDefined ()

NPM

Разработка через npm-пакеты имеет много преимуществ. Разработчики просто импортируют нужные им компоненты и файлы из либы. При этом в проекте сохраняется типизация, есть версионирование. Сборка оптимальна: работает tree-shaking (удаление неиспользуемого кода при сборке), а разработчики могут легко настроить lazy loading.

Схема подключения МФ-а через npmСхема подключения МФ-а через npm

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

Преимущества

Недостатки

Производительность

Нет независимого деплоя

SEO

Единый стек

Проверка типов (при наличии TS)

Версионирование

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

git submodules

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

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

По своим достоинствам и недостаткам данный подход очень близок к npm-пакетам. Мы также просто что-то импортируем, но уже из соседней папки, а не из пакета, и используем. Далее разберем отличия этих двух способов:

  1. NPM-пакеты — это, по сути, конечный в меру изолированный микропродукт со своими релизами и версионированием. Все заточено на создание переиспользуемого функционала. Но приложение может быть сложным/запутанным и с «душком легаси», поэтому разбиение большого монолита на пакеты может быть весьма дорогостоящим. Именно тут будет разумным рассмотреть сабмодули, ведь они позволяют очень грубо нарезать репозиторий, когда мы без всякой дополнительной подготовки выносим папку в отдельный репозиторий.

  2. NPM-пакеты могут быть вложены друг в друга рекурсивно. Сабмодули тоже, но на уровне сборки они могут начать дублировать функционал, если один из сабмодулей подключен несколько раз в разных папках как отдельный сабмодуль. В этом случае стоит использовать более плоскую структуру модулей.

  3. Если нужно быстро выкатить фичу сразу во всех пакетах, вести кросспакетную разработку может быть крайне неудобно. В то время как на сабмодулях все остается единым целым, вы можете делать правки, которые затрагивают другие сабмодули. В этом случае легко заниматься отладкой. Но в итоге сами изменения вы не смержите — на уровне merge request сторонняя команда, чей модуль вы потрогали, может потребовать привести код в соответствие с их правилами.

npm

git submodules

Переиспользуемость

Грубая нарезка функционала

Произвольно вложенные зависимости

Плоская структура

Сложная кросспакетная разработка

Разработка с любым количеством модулей

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

nginx

Интересным и не совсем очевидным способом может быть разбиение вашего большого приложения на несколько маленьких, которые будут работать с конкретными роутами. После этого на уровне nginx можно указать, на каких роутах какой бандл отдавать. Это напоминает паттерн API-шлюз, который часто используется в классической микросервисной архитектуре.

При разделении МФ-ов на уровне nginx у нас несколько самостоятельных приложений на одном домене. Как-такого главного приложения (App Shell) нетПри разделении МФ-ов на уровне nginx у нас несколько самостоятельных приложений на одном домене. Как-такого главного приложения (App Shell) нет

Мини-приложение может отвечать за несколько роутов, переход между которыми реализуется средствами вашего SPA-фреймворка. Уход в другое мини-приложение выполняется через обычный a[href]. Очевидно, что перемещения между приложениями будут приводить к перезагрузке страниц. 

Также неприятностей добавляет и использование общих компонент (хедер, футер, меню), логика и верстка которых должны быть продублированы в каждом приложении. Это можно минимизировать путем веб-компонент, но в целом это скажется на производительности. Однако данный подход обладает и преимуществами в виде изоляции логики со стилями и независимостью от фреймворка.

Преимущества

Недостатки

Легкость реализации

Производительность

Изоляция логики и стилей

Дублирование кода

Независимый деплой

SEO

Без привязки к фреймворку

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

Hidden text

Примечание.

Вместо nginx может использоваться любой другой прокси-сервер (Apache, HAProxy и т.п.)

single-spa

single-spa — это, по сути, фреймворк, сочетающий в себе другие фреймворки. Невероятно мощная технология, за которой скрывается огромное количество нюансов, — тема для отдельной статьи.

Схема схожа с iframe, но загрузка МФ-а делается теперь через нативный import + importmapили через Systemjs, если нужны полифилы.

Коротко разберем достоинства и недостатки:

Преимущества

Недостатки

Независимый деплой

Большая документация, которая все равно покрывает не все кейсы

Без привязки к фреймворку

Сложности c SEO (все рабочие демки только на React)

Мощный CLI для поднятия разных МФ

В отличие от всех способов организации МФ-ов этот сильно заточен под объединение под собой разных фреймворков. Но стоит предостеречь от использования технологии ради самой технологии. Если есть возможность обойтись одним стеком, нужно этим пользоваться. Разработка может быть навсегда обременена поддержкой технически сложного проекта и правкой каких-либо багов от сайд эффектов разных приложений. Пользователь же может ощущать дискомфорт, т.к. количество кода для скачивания на клиент будет увеличено (ядра разных фреймворков для разных кусков функционала + ядро самого single-spa и его плагинов).

Этот подход подойдет при объединении разных фреймворков.

Примечание. Использование import maps делает разработку очень гибкой. Но поддержка этой фичи на конец 2022 не внушает полного доверия.

543abbbd05944a73fef130a5e6139353.png

Приходится использовать полифил на основе Systemjs, с которым не всегда совместима сборка фреймворков.

module federation

Плагин webpack 5, который разрабатывался специально для создания МФ-ов. Перспективная технология: небольшой плагин для сборки для более корректного бандлинга и динамических импортов в рантайме. 

Схема почти один в один повторяет single-spa, но теперь для подгрузки МФ-ов применяются динамические импорты

Соответственно, топ 3 cli (create-react-app, vue-cli, angular-cli) в «ближайших» релизах могут обзавестись поддержкой этого плагина и нужными консольными командами.

Преимущества

Недостатки

Независимый деплой

Низкоуровневость

Легкость реализации (демки от создателей под разные фреймворки)

Нет противоречий с SSR

Если есть возможности и желание поддерживать единый стек, а также необходимость дробления на микрофронтенды, то в 2022 году этот способ является одним из самых достойных решений. Стоит лишь отметить, что плагин в меру примитивный, он лишь делает правильную сборку. В случае недоступности МФ-а, нужно озадачиться правильной обработкой исключений. 

Что выбрать для проекта?

Давайте еще раз пробежимся, что и для чего можно применить:

  • iframe — одиночная вставка для сочетания несочетаемого

  • web components — когда нужна небольшая сквозная функциональность без привязки к фреймворку, вроде корпоративного ui-kit

  • npm-пакеты — если есть переиспользуемость между проектами и/или нужна проверка типов в билд тайме

  • git submodules — когда нужно грубо нарезать проект и распределить зоны ответственности

  • nginx — если хотите поскорее сменить фреймворк на проекте, желательно не застрять на этом решении, ведь «нет ничего более постоянного, чем временное»

  • single-spa — когда есть сильная необходимость сочетать несколько фреймворков неопределенно долгое время, желательно без SSR

  • module-federation — все остальные сценарии применения МФ-ов при условии единства стека.

Каждый подход хорош по-своему и всему должно быть свое место. Перед тем как переходить на МФ-ы, советуем задуматься, точно ли вам это нужно. Какой подход бы не был выбран он неизбежно что-то усложнит на уровне разработки, CI/CD или производительности. Если есть возможности оставаться на едином стеке и монолитном приложение, с радостью принимайте такую возможность. 

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

А как вы относитесь к микрофронтендам? Используете ли в своей работе? Будет интересно узнать

Hidden text

Если интересно почитать и другие наши материалы об архитектуре проектов, ссылки ниже:

https://habr.com/ru/company/simbirsoft/blog/512310/

https://habr.com/ru/company/simbirsoft/blog/556270/

https://habr.com/ru/company/simbirsoft/blog/571554/

Больше кейсов и полезных материалов в наших каналах:

  • ВК и Telegram для разработчиков

  • ВК и Telegram для владельцев продуктов и IT-управленцев

© Habrahabr.ru