Основы Webpack Module Federation и микрофронтендов

Этот курс познакомит вас с архитектурой микрофронтендов с использованием Webpack 5 Module Federation. Вы научитесь разделять монолитные приложения на независимые модули, настраивать хост и удаленные компоненты, а также управлять общими зависимостями.

1. Введение в микрофронтенды и концепцию Module Federation

Введение в микрофронтенды и концепцию Module Federation

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

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

Когда над одним проектом работают десятки человек, возникают организационные и технические барьеры. Время сборки проекта увеличивается экспоненциально. Если в начале пути Continuous Integration (CI) занимает пару минут, то в крупных проектах ожидание сборки может достигать часа. Любая, даже самая незначительная ошибка в одном модуле, способна привести к падению всего приложения.

Представим ситуацию: команда из 30 разработчиков трудится над крупным интернет-магазином. Разработчик из команды оформления заказов исправляет опечатку в тексте кнопки. Чтобы это изменение попало к пользователям, системе необходимо пересобрать весь проект целиком, прогнать тысячи тестов, никак не связанных с корзиной, и заново развернуть весь фронтенд. Если время сборки составляет 45 минут, а в день происходит 20 таких мелких изменений, суммарные потери времени становятся колоссальными.

> Микрофронтенды — это архитектурный подход, при котором единое монолитное приложение разделяется на множество небольших, независимых частей, каждая из которых разрабатывается, тестируется и развертывается автономно. > > Мартин Фаулер, эксперт в области архитектуры ПО

Архитектура микрофронтендов

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

Каждая команда получает возможность выбрать собственный технологический стек, независимый цикл релизов и изолированную кодовую базу. Команда каталога может использовать React, в то время как команда блога предпочтет Vue.js, и их код не будет конфликтовать.

Сравнение подходов

| Характеристика | Монолитный фронтенд | Микрофронтенды | | :--- | :--- | :--- | | Развертывание | Развертывается целиком | Каждый модуль развертывается независимо | | Сборка | Единый длительный процесс | Быстрая сборка отдельных частей | | Масштабирование команд | Сложное, частые конфликты при слиянии кода | Простое, команды изолированы друг от друга | | Технологический стек | Единый для всего проекта | Возможность использовать разные фреймворки | | Отказоустойчивость | Ошибка может сломать всё приложение | Ошибка локализована в одном модуле |

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

Эволюция интеграции: от iframe до Module Federation

Долгое время разработчики пытались реализовать микрофронтенды различными способами. Использовались iframes, которые обеспечивали идеальную изоляцию, но создавали огромные проблемы с производительностью и маршрутизацией. Затем популярность обрела интеграция во время сборки (через NPM-пакеты).

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

Настоящая революция произошла с выходом Webpack 5, в котором был представлен плагин Module Federation.

Module Federation — это технология, позволяющая JavaScript-приложениям динамически загружать код из других независимых сборок прямо во время выполнения (в рантайме), а не на этапе компиляции.

Ключевые термины Module Federation

Для понимания работы технологии необходимо разобраться в ее базовой терминологии. Архитектура строится вокруг взаимодействия двух основных ролей:

  • Хост (Host) — основное приложение, которое загружается первым. Оно выступает в роли контейнера или оболочки. Хост инициализирует базовый интерфейс, маршрутизацию и принимает решение о том, когда и откуда загружать дополнительные модули.
  • Удаленный модуль (Remote) — независимое приложение, часть кода которого экспортируется для использования другими приложениями. Удаленный модуль может работать как самостоятельное приложение (иметь свой собственный хост), так и встраиваться в другие хосты.
  • Общие зависимости (Shared dependencies) — механизм, позволяющий хосту и удаленным модулям использовать одни и те же библиотеки (например, React или Lodash) без их повторной загрузки по сети.
  • Представьте, что хост — это материнская плата компьютера, а удаленные модули — это видеокарта, оперативная память и звуковая карта. Вы можете заменить видеокарту на новую, не меняя материнскую плату. Точно так же вы можете обновить код удаленного модуля, и хост автоматически начнет использовать новую версию при следующем обновлении страницы пользователем, без пересборки самого хоста.

    Как это работает на практике

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

    Рассмотрим базовый пример конфигурации для удаленного модуля (Remote):

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

    Самое интересное происходит в блоке exposes. Здесь мы буквально говорим: «Я хочу сделать компонент CartWidget доступным для внешнего мира по пути ./CartWidget».

    Блок shared решает критическую проблему производительности. Если и хост, и удаленный модуль используют библиотеку React, браузеру не нужно скачивать ее дважды.

    Допустим, размер библиотеки React составляет 130 КБ. Если на странице отображается один хост и три удаленных модуля, без механизма общих зависимостей браузер скачал бы КБ только одного фреймворка. Благодаря shared, библиотека скачивается ровно один раз, экономя 390 КБ сетевого трафика и значительно ускоряя время до первого взаимодействия с интерфейсом.

    Интеграция в рантайме

    Главное отличие Module Federation от классических подходов заключается в моменте объединения кода. При использовании NPM-пакетов код объединяется на сервере разработчика во время сборки. Если вы обновили пакет, вам нужно запустить процесс сборки заново.

    В случае с Module Federation интеграция происходит непосредственно в браузере пользователя. Когда пользователь заходит на сайт, браузер скачивает хост-приложение. Как только пользователь переходит на страницу корзины, хост делает асинхронный запрос к серверу, скачивает актуальный код удаленного модуля cart_app и мгновенно встраивает его в интерфейс.

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

    2. Базовая настройка Webpack 5 и плагина Module Federation

    Базовая настройка Webpack 5 и плагина Module Federation

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

    Архитектура любого приложения на базе Module Federation строится вокруг правильной конфигурации двух сущностей: приложения-потребителя и приложения-поставщика. В терминах технологии они называются Хост (Host) и Удаленный модуль (Remote).

    > Плагин Module Federation позволяет приложению экспортировать один или несколько модулей в отдельный JS файл. Отличный способ строить микрофронтенд приложения. Сторонние приложения могут импортировать себе готовые модули, это могут быть например реакт компоненты. Причём, импорт зависимостей Webpack берёт на себя. > > habr.com

    Конфигурация удаленного модуля (Remote)

    Удаленный модуль — это самостоятельное приложение, которое предоставляет часть своего функционала (компоненты, функции, хранилища данных) для использования во внешнем мире. Чтобы превратить обычное приложение в удаленный модуль, необходимо добавить ModuleFederationPlugin в конфигурационный файл webpack.config.js.

    Рассмотрим базовый пример настройки для приложения, которое экспортирует кнопку и шапку сайта:

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

  • Свойство name задает глобальную переменную, через которую хост будет обращаться к этому модулю. Имя должно быть уникальным и не содержать дефисов или пробелов.
  • Свойство filename определяет имя файла-манифеста. Общепринятый стандарт — remoteEntry.js. Этот файл содержит карту всех экспортируемых компонентов и их зависимостей.
  • Объект exposes — это витрина модуля. Ключи (например, ./Button) указывают, по какому пути компонент будет доступен извне, а значения (./src/components/Button) — где физически лежит файл в проекте.
  • Массив shared указывает, какие библиотеки модуль готов делить с другими приложениями, чтобы избежать дублирования кода в браузере.
  • Конфигурация хоста (Host)

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

    Вместо свойства exposes здесь используется объект remotes. Он сообщает сборщику, где искать внешний код. Ключ ui — это локальный алиас, который будет использоваться внутри кода хоста при импорте. Значение состоит из имени удаленного модуля (ui_components), символа @ и абсолютного URL-адреса до файла remoteEntry.js.

    Сравнение параметров конфигурации

    | Параметр | Хост (Потребитель) | Удаленный модуль (Поставщик) | Назначение | | :--- | :--- | :--- | :--- | | name | Обязателен | Обязателен | Уникальный идентификатор приложения | | filename | Не используется | Обязателен | Имя генерируемого файла-манифеста | | exposes | Не используется | Обязателен | Список компонентов для экспорта | | remotes | Обязателен | Опционально | Ссылки на внешние приложения |

    Тонкая настройка общих зависимостей (Shared)

    Простое перечисление библиотек в массиве shared работает для базовых случаев, но в реальных проектах (особенно при использовании React) этого недостаточно. Библиотека React использует внутреннее глобальное состояние. Если на странице окажется две разные копии React, приложение неминуемо упадет с ошибкой.

    Для решения этой проблемы объект shared поддерживает расширенную конфигурацию:

    Параметр singleton: true гарантирует, что в браузере будет загружен и исполнен только один экземпляр библиотеки, независимо от того, сколько микрофронтендов ее запрашивают.

    Параметр requiredVersion позволяет управлять совместимостью. Представим ситуацию: хост использует React версии 18.2.0, а удаленный модуль требует версию 17.0.2. Если параметр singleton включен, Webpack проанализирует требования и попытается загрузить старшую совместимую версию. Если совместимость нарушена, в консоли разработчика появится предупреждение.

    Экономия ресурсов при правильной настройке колоссальна. Размер базового пакета React и ReactDOM составляет около 130 КБ. Если пользователь открывает страницу, на которой работает один хост и три независимых микрофронтенда, без механизма shared браузер скачал бы КБ только базового фреймворка. Благодаря паттерну Singleton, библиотека загружается ровно один раз, экономя 390 КБ сетевого трафика и ускоряя время инициализации скриптов.

    Интеграция компонентов в коде

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

    В экосистеме React для работы с асинхронными компонентами используются функции lazy и компонент Suspense:

    Когда интерпретатор доходит до функции import('ui/Button'), Webpack перехватывает этот вызов. Он обращается к объекту remotes, находит алиас ui и делает сетевой запрос к http://localhost:3001/remoteEntry.js. Получив манифест, он понимает, где физически лежит скомпилированный код компонента Button, скачивает его, разрешает общие зависимости и передает готовый компонент в React.lazy.

    Пока идет загрузка по сети, компонент Suspense показывает пользователю запасной интерфейс (fallback). Как только код загружен и выполнен, на экране появляется полноценная кнопка из другого приложения.

    3. Хост и удаленные модули: экспорт и импорт компонентов

    Хост и удаленные модули: экспорт и импорт компонентов

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

    В архитектуре Module Federation нет жесткой привязки к конкретному фреймворку, однако принципы экспорта и импорта удобнее всего рассматривать на примере экосистемы React. Именно здесь концепция ленивой загрузки раскрывается максимально полно.

    Механика экспорта компонентов (Remote)

    Удаленный модуль выступает в роли поставщика функциональности. Чтобы компонент стал доступен внешним потребителям, его необходимо правильно подготовить и зарегистрировать в объекте exposes конфигурации Webpack.

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

    Когда этот файл указывается в конфигурации exposes: { './Widget': './src/components/AnalyticsWidget' }, сборщик применяет специальный алгоритм. Он не просто копирует код, а создает изолированный чанк (chunk) — отдельный JavaScript-файл, который содержит сам компонент и инструкции по разрешению его локальных зависимостей (в данном случае библиотеки recharts).

    > Module Federation — это плагин, созданный изначально для webpack, который позволяет разделять код между несколькими независимыми приложениями и динамически загружать его в браузере пользователя. > > habr.com

    Рассмотрим конкретный пример с числами. Если размер всего приложения аналитики составляет 850 КБ, то при экспорте виджета Webpack проанализирует дерево зависимостей. Он выделит только код самого AnalyticsWidget (около 5 КБ) и код библиотеки recharts (около 120 КБ). В результате для внешнего мира будет сгенерирован чанк размером 125 КБ. Хост-приложение скачает только этот минимально необходимый объем данных, а не все 850 КБ исходного проекта.

    Механика импорта компонентов (Host)

    На стороне хоста процесс выглядит как обращение к локальному файлу, но под капотом происходит сложный сетевой обмен. Поскольку код удаленного модуля физически отсутствует на сервере хоста в момент сборки, классический статический импорт (import Widget from '...') приведет к фатальной ошибке компиляции.

    Для решения этой проблемы используется динамический импорт в связке с инструментами асинхронного рендеринга. В React за это отвечают функция lazy и компонент Suspense.

    В этом коде компонент Suspense играет роль предохранителя. Когда React пытается отрендерить RemoteAnalyticsWidget, он обнаруживает, что код еще не загружен. В этот момент рендеринг приостанавливается, и на экран выводится содержимое свойства fallback (индикатор загрузки). Как только браузер скачивает и выполняет чанк виджета, React автоматически заменяет индикатор на полноценный интерфейс.

    Сравнение подходов к импорту

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

    | Характеристика | Статический импорт | Динамический импорт (локальный) | Импорт Module Federation | | :--- | :--- | :--- | :--- | | Синтаксис | import A from './A' | import('./A') | import('app/A') | | Время разрешения | На этапе сборки (Build time) | В рантайме (Run time) | В рантайме (Run time) | | Расположение кода | В итоговом бандле | В отдельном локальном файле | На стороннем сервере | | Сетевой запрос | Нет (уже в бандле) | Да (к своему домену) | Да (к чужому домену) |

    Передача данных и управление состоянием

    Важнейшее преимущество Module Federation заключается в том, что после загрузки удаленный компонент становится полноправной частью дерева хост-приложения. Это означает, что разработчики могут передавать в него пропсы (props), контекст и функции обратного вызова точно так же, как в обычные локальные компоненты.

    В примере выше мы передали строку dataEndpoint="/api/v1/stats". Удаленный виджет принял этот пропс и использовал его для внутреннего запроса. Аналогичным образом можно передавать сложные объекты, например, данные авторизованного пользователя, или функции для обновления глобального состояния хоста.

    Общий объем данных, загружаемых браузером при такой архитектуре, можно описать следующей формулой:

    Где — итоговый объем загруженных JavaScript-файлов, — размер базового бандла хоста, — количество уникальных микрофронтендов на текущей странице, а — размер чанка конкретного удаленного компонента.

    Если пользователь заходит на страницу, где нужен только один виджет, браузер загрузит (например, 200 КБ) и (125 КБ). Общий объем составит 325 КБ. Если бы приложение было монолитным, пользователю пришлось бы скачивать весь код всех возможных виджетов сразу, что могло бы превысить несколько мегабайт.

    Обработка сетевых ошибок

    Поскольку импорт удаленных модулей напрямую зависит от состояния сети и доступности сторонних серверов, архитектура должна быть устойчивой к сбоям. Если сервер, раздающий analytics/Widget, упадет, функция import() вернет ошибку (Promise rejection).

    Если эту ошибку не перехватить, все хост-приложение завершит работу с белым экраном. Именно поэтому в примере кода выше используется ErrorBoundary (Предохранитель). Это специальный классовый компонент React, который отлавливает ошибки JavaScript в своем дочернем дереве компонентов, логирует их и выводит резервный пользовательский интерфейс вместо упавшего дерева.

    Использование ErrorBoundary вокруг каждого удаленного модуля — это золотой стандарт разработки микрофронтендов. Он гарантирует, что падение одного микросервиса (например, блока рекомендаций) не заблокирует работу критически важных частей приложения (например, корзины товаров или формы оплаты).

    4. Управление общими зависимостями и оптимизация загрузки

    Управление общими зависимостями и оптимизация загрузки

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

    Для решения этой проблемы в Webpack предусмотрен механизм общих зависимостей (Shared dependencies). Он позволяет микрофронтендам договариваться между собой в рантайме и переиспользовать уже загруженные в память браузера библиотеки.

    Механизм работы общих библиотек

    Когда сборщик обрабатывает конфигурацию плагина Module Federation, он обращает внимание на секцию shared. В этой секции разработчик перечисляет пакеты, которые приложение готово предоставить другим модулям или, наоборот, хочет получить от них.

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

    > Module Federation — это плагин, созданный изначально для webpack, который позволяет разделять код между несколькими независимыми приложениями и динамически загружать его в браузере пользователя. > > habr.com

    Рассмотрим математическую модель экономии трафика при использовании этого подхода. Объем сэкономленных данных можно выразить следующей формулой:

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

    Приведем конкретный пример с числами. Допустим, на странице работают хост и два удаленных модуля (всего 3 потребителя). Все они используют библиотеку React размером 130 КБ и библиотеку Lodash размером 70 КБ. Без оптимизации браузер скачал бы КБ. Применяя формулу, считаем экономию: для React это КБ, для Lodash это КБ. Итого мы экономим 400 КБ трафика, загружая всего 200 КБ вместо 600 КБ.

    Тонкая настройка конфигурации

    Простое перечисление библиотек в массиве shared работает не всегда идеально. Для сложных экосистем требуется детальная настройка каждого пакета с помощью передачи объекта с параметрами.

    В этом примере используются ключевые параметры, которые управляют поведением сборщика:

  • singleton — гарантирует, что в приложении будет загружен только один экземпляр библиотеки. Это критически важно для фреймворков, хранящих внутреннее состояние. Если загрузить две копии React, приложение упадет с ошибкой нарушения правил хуков.
  • requiredVersion — указывает семантическую версию пакета, которая необходима модулю. Обычно сюда передают версию из локального package.json, чтобы избежать ручного дублирования номеров версий.
  • eager — управляет моментом загрузки. Если установить значение true, библиотека будет включена в начальный чанк и загрузится синхронно. По умолчанию используется false, что позволяет загружать библиотеку асинхронно только тогда, когда она действительно понадобится.
  • Сравнение стратегий загрузки

    Выбор между синхронной и асинхронной загрузкой общих зависимостей напрямую влияет на производительность.

    | Параметр | Поведение | Плюсы | Минусы | | :--- | :--- | :--- | :--- | | eager: false (по умолчанию) | Выделяется в отдельный файл. Загружается по требованию. | Минимальный размер стартового бандла. Максимальная экономия трафика. | Требует использования асинхронных границ при старте приложения. | | eager: true | Вшивается в основной файл приложения. | Приложение стартует мгновенно, без дополнительных сетевых запросов. | Увеличивает размер стартового бандла. Невозможно переиспользовать версию от другого модуля, если она загрузится позже. |

    На практике параметр eager: true часто применяют в хост-приложениях для базовых библиотек маршрутизации или UI-китов, чтобы избежать мерцания интерфейса при первой отрисовке. Для удаленных модулей этот параметр почти всегда оставляют в значении false.

    Разрешение конфликтов версий

    Архитектура независимых команд подразумевает, что разные микрофронтенды могут обновлять свои зависимости в разное время. Возникает ситуация: хост использует React версии 17.0.2, а новый удаленный виджет написан на React 18.2.0.

    Если для библиотеки не установлен флаг singleton, Webpack поступит безопасно: он загрузит обе версии. Хост будет работать на 17-й версии, а виджет — на 18-й. Это увеличит потребление памяти, но сохранит работоспособность изолированных компонентов.

    Однако, если установлен флаг singleton: true, сборщик вынужден выбрать только одну версию. По умолчанию Module Federation выбирает наивысшую доступную версию, удовлетворяющую требованиям requiredVersion. Если удаленный модуль требует строго ^18.0.0, а хост предоставляет 17.0.2, сборщик выдаст предупреждение в консоль браузера и попытается загрузить 18-ю версию из ресурсов удаленного модуля.

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

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

    Анализ сетевых запросов и кэширование

    Когда общие зависимости выносятся в отдельные файлы, это кардинально меняет профиль сетевых запросов приложения. Вместо загрузки одного огромного файла размером 2 МБ, браузер скачивает несколько небольших файлов: код хоста (150 КБ), React (130 КБ), Lodash (70 КБ) и так далее.

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

    Рассмотрим это на примере с числами. При монолитной сборке любое изменение в коде заставляет пользователя заново скачивать весь бандл размером 2 МБ (2048 КБ). В микрофронтенд-архитектуре с настроенным shared, обновление бизнес-логики хоста потребует скачивания только измененного чанка размером 150 КБ. Экономия при повторном визите пользователя составит КБ, что ускоряет загрузку страницы в несколько раз, особенно на мобильных сетях.

    Кроме того, браузеры способны загружать несколько файлов параллельно. Современный протокол HTTP/2 отлично справляется с мультиплексированием множества мелких запросов по одному соединению, нивелируя накладные расходы на установку соединения, которые были критичны во времена HTTP/1.1.

    Грамотное управление общими зависимостями превращает набор разрозненных микрофронтендов в монолитное с точки зрения производительности приложение. Пользователь получает быстрый отклик и минимальный расход трафика, а разработчики сохраняют независимость кодовых баз и свободу выбора инструментов.

    5. Продвинутые техники: интеграция разных фреймворков и роутинг

    Продвинутые техники: интеграция разных фреймворков и роутинг

    Архитектура микрофронтендов достигает своей максимальной гибкости, когда независимые команды могут выбирать не только время релиза, но и технологический стек. Представьте ситуацию: крупная корпорация покупает перспективный стартап. Основной продукт корпорации написан на Angular, а интерфейс стартапа — на Vue. Переписывать готовый и работающий код долго и дорого.

    Именно здесь раскрывается истинный потенциал Webpack Module Federation. Поскольку этот плагин работает на уровне сборщика и оперирует обычными JavaScript-чанками, он абсолютно не привязан к конкретным фреймворкам. Однако, чтобы заставить разные технологии работать на одной странице без конфликтов, требуются особые архитектурные подходы.

    Паттерн «Универсальный адаптер»

    В предыдущих материалах мы экспортировали компоненты напрямую. Если хост и удаленный модуль используют React, хост может просто импортировать удаленный компонент и вставить его в свое дерево рендеринга. Но Angular не понимает компоненты React, а React не умеет напрямую рендерить компоненты Vue.

    Чтобы решить эту проблему, удаленный модуль должен экспортировать не специфичный для фреймворка компонент, а универсальную функцию монтирования (Mount function). Эта функция принимает обычный HTML-элемент (DOM-узел) и самостоятельно инициализирует внутри него свое приложение.

    В этом примере удаленный модуль скрывает детали своей реализации. Хост-приложению не нужно знать, что внутри находится Vue. Ему достаточно создать пустой <div>, импортировать функцию mount и передать ей этот элемент.

    Рассмотрим, как выглядит интеграция такого модуля на стороне хоста, написанного на React:

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

    Сравнение подходов к экспорту

    Выбор между прямым экспортом и использованием адаптера зависит от однородности вашей экосистемы.

    | Характеристика | Прямой экспорт компонента | Экспорт функции монтирования | | :--- | :--- | :--- | | Стек технологий | Строго одинаковый (например, только React) | Любой (агностический подход) | | Сложность настройки | Минимальная | Средняя (требуются обертки) | | Передача пропсов | Напрямую через атрибуты | Через аргументы функции mount | | Контекст фреймворка | Общий (доступны общие провайдеры) | Изолированный (каждый модуль имеет свой контекст) |

    Изоляция и синхронизация роутинга

    Интеграция разных приложений на одной странице неизбежно приводит к конфликтам маршрутизации. В традиционном SPA (Single Page Application) за изменение URL-адреса в браузере отвечает один глобальный роутер, использующий History API.

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

    > Использование полноценных адаптеров и изоляция роутинга позволяют соединить Angular, React и Vue в одном SPA без конфликтов навигации. > > habr.com

    Для решения этой проблемы применяется правило единого владельца URL. Только хост-приложение имеет право напрямую читать и изменять адресную строку браузера (используя Browser Router). Все удаленные модули должны работать в режиме маршрутизации в памяти (Memory Router).

    Маршрутизатор в памяти хранит текущий путь в локальной переменной и не взаимодействует с адресной строкой. Однако, чтобы пользователь мог скопировать ссылку на конкретную страницу внутри микрофронтенда и отправить ее коллеге, состояния роутеров необходимо синхронизировать.

    Процесс двусторонней синхронизации состоит из следующих шагов:

  • От хоста к модулю: При загрузке удаленного модуля хост передает ему начальный путь. Если пользователь зашел на example.com/dashboard/settings, хост выделяет префикс /dashboard для себя, а часть /settings передает в функцию mount удаленного модуля.
  • Внутренняя навигация модуля: Когда пользователь кликает по ссылкам внутри микрофронтенда, его внутренний Memory Router меняет состояние. Модуль вызывает специальный callback-колбэк onNavigate, переданный хостом, сообщая ему новый путь.
  • Обновление URL: Хост получает сигнал от модуля и обновляет адресную строку браузера, не вызывая при этом перезагрузку собственных компонентов.
  • Навигация кнопками браузера: Если пользователь нажимает «Назад» в браузере, хост перехватывает это событие и отправляет команду удаленному модулю обновить его внутренний роутер.
  • Рассмотрим математическую модель количества возможных состояний маршрутизации. Если хост-приложение имеет уникальных маршрутов, а каждый из подключенных микрофронтендов имеет внутренних маршрутов, то общее количество уникальных URL-адресов , которые должно корректно обработать приложение, вычисляется по формуле:

    Где — общее количество уникальных путей, — маршруты самого хоста, — количество удаленных модулей, а — количество маршрутов внутри -го модуля.

    Допустим, хост имеет 5 собственных страниц. Мы подключаем модуль аналитики с 4 страницами и модуль профиля с 3 страницами. Общее количество уникальных состояний URL составит . Синхронизация гарантирует, что при прямом переходе по любому из этих 12 адресов, приложение восстановит правильное состояние как хоста, так и нужного микрофронтенда.

    Производительность полиглот-архитектуры

    Интеграция нескольких фреймворков в одном приложении (полиглот-архитектура) имеет свою цену. Главный компромисс — это увеличение объема загружаемого JavaScript-кода и времени инициализации.

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

    Где — общее время до полной интерактивности, — время инициализации хоста, — количество удаленных модулей на других фреймворках, — время скачивания кода модуля по сети, — время компиляции скрипта браузером, а — время выполнения функции монтирования.

    Приведем пример с реальными числами. Допустим, инициализация хоста на React занимает 200 миллисекунд. Пользователь переходит на страницу, где встроен виджет на Vue. Скачивание бандла Vue и кода виджета занимает 100 мс, парсинг в браузере — 40 мс, а выполнение функции mount — 30 мс. Итоговое время составит миллисекунд.

    Чтобы минимизировать эти задержки, продвинутые команды используют ленивую загрузку (Lazy Loading) не только для самих компонентов, но и для их фреймворков. Код удаленного модуля начинает скачиваться только в тот момент, когда пользователь прокручивает страницу до места его расположения. До этого момента хост отображает легковесный скелетон (Skeleton Loader), сохраняя высокую отзывчивость основного интерфейса.