Английский перевод оригинальной статьи Манфреда Штайера «The Microfrontend Revolution: Module Federation with Angular» обновлен 06-06-2022
Важно: Эта статья написана с использованием Angular и Angular CLI 14.
В предыдущей статье я показал, как использовать Module Federation, который является частью webpack, начиная с версии 5, для реализации микрофронтендов. В этой статье показано, как создать микрофронтенд на основе Angular, используя маршрутизатор для загрузки отдельно скомпилированного и развернутого микрофронтенда.
Результат похож на предыдущую статью, но с использованием Angular:
Загруженный фронтенд Micro показан внутри красной пунктирной границы без оболочки:
Вы можете загрузить код с Github
Использование федерации модулей в проектах Angular
В нашем примере предполагается, что и оболочка, и микрофронтенд являются проектами в одном и том же рабочем пространстве Angular. Для начала нам нужно указать CLI использовать Module Federation при их генерации. Однако, поскольку CLI упрощает для нас настройку webpack, нам нужно сделать webpack пользовательским.
Для этого пакет @angular-architects/module-federation предоставляет промежуточный интерфейс для взаимодействия и модификации. Чтобы начать работу, использовать его и добавить в проект, мы можем использовать команду ng add
:
ng add @angular-architects/module-federation --project shell --port 4200 --type host
ng add @angular-architects/module-federation --project mfe1 --port 4201 --type remote
Если вы используете Nx, вы должны установить библиотеку отдельно. После этого вы можете использовать флаг :init
npm i @angular-architects/module-federation -D
ng g @angular-architects/module-federation:init --project shell --port 4200 --type host
ng g @angular-architects/module-federation:init --project mfe1 --port 4201 --type remote
Аргумент —type был добавлен в версии 14.3 и гарантирует, что будет сгенерирована только необходимая конфигурация.
Хотя очевидно, что оболочка проекта содержит код оболочки, mfe1 означает Micro Frontend 1, показанная команда выполняет несколько действий:
- Сгенерируйте базовый webpack.config.js для использования Module Federation.
- Установите пользовательский билдер, заставляющий webpack внутри CLI использовать сгенерированный
webpack.config.js
. - Назначьте новый порт для ng serve, чтобы можно было одновременно обслуживать несколько проектов.
Обратите внимание, что webpack.config.js — это лишь частичная конфигурация webpack. Он содержит только то, что позволяет управлять модулем федерации. Остальное генерируется CLI как обычно.
Ракушка (ведущий)
Начнем с оболочки, которая также называется хостом в федерации модулей, она использует маршрутизатор для асинхронной загрузки FlightModule
:
export const APP_ROUTES: Routes = [
{
path: '',
component: HomeComponent,
pathMatch: 'full'
},
{
path: 'flights',
loadChildren: () => import('mfe1/Module').then(m => m.FlightsModule)
},
];
Однако путь mfe1/Modul
e, который импортируется здесь, не существует внутри оболочки. Это просто виртуальный путь, указывающий на другой проект.
Чтобы облегчить работу компилятора TypeScript, нам необходимо определение .d.ts
для него:
// decl.d.ts
declare module 'mfe1/Module';
Кроме того, нам нужно сообщить webpack, что все пути, начинающиеся с mfe1
, указывают на другой проект. Это можно сделать в сгенерированном webpack.config.js
:
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
remotes: {
"mfe1": "http://localhost:4201/remoteEntry.js",
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
});
Секция remote отображает путь mfe1
к микрофронтенду на ваш удаленный вход. Это небольшой файл, создаваемый webpack при сборке remote, он загружается во время выполнения для получения всей информации, необходимой для взаимодействия с микрофронтендом.
Хотя указание URL удаленного входа таким образом удобно для разработки, для производства нам нужен более динамичный подход.
В следующей статье этой серии будет предложено решение для этого: Dynamic Federation.
Свойство shared
определяет пакеты npm, которые будут совместно использоваться оболочкой и микрофронтендом(ами). Для этого свойства в конфигурации используется свойство shareAll
, которое в основном разделяет все зависимости, найденные в вашем package.json
. Хотя это помогает быстро получить конфигурацию, это может привести к слишком большому количеству общих зависимостей.
Подробнее об этом позже.
Комбинация singleton: true
и strictVersion: true
заставляет webpack выдавать ошибку времени выполнения, когда оболочка и микрофронтенд(ы) требуют разных несовместимых версий (т.е. двух разных основных версий). Если бы мы пропустили strictVersion или установили значение false, webpack просто выдал бы предупреждение во время выполнения.
Мы поговорим об управлении версиями в другой статье этого цикла.
Свойство requiredVersion: 'auto'
— это небольшое дополнение, предоставляемое плагином @angular-architects/module-federation
. Он проверяет версию, используемую в вашем package.json, и позволяет избежать нескольких проблем.
Устанавливая значение auto в requiredVersion, он заменяет значение ‘auto’ на версию, найденную в вашем package.json.
Фронтенд Micro (он же Remote)
Фронтенд Micro, также называемый в Module Federation remote, представляет собой обычное приложение Angular. Он имеет пути, определенные внутри модуля AppModule:
export const APP_ROUTES: Routes = [
{ path: '', component: HomeComponent, pathMatch: 'full'}
];
Кроме того, модуль FlightsModule:
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(FLIGHTS_ROUTES)
],
declarations: [
FlightsSearchComponent
]
})
export class FlightsModule { }
Этот модуль имеет несколько собственных маршрутов:
export const FLIGHTS_ROUTES: Routes = [
{
path: 'flights-search',
component: FlightsSearchComponent
}
];
Чтобы сделать возможным загрузку модуля FlightsModule в оболочке, нам также необходимо раскрыть его через конфигурацию webpack удаленного сервера:
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'mfe1',
exposes: {
'./Module': './projects/mfe1/src/app/flights/flights.module.ts',
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
});
Конфигурация, показанная здесь, раскрывает FlightsModule
под публичным именем Module. Раздел shared
указывает на библиотеки, совместно используемые с оболочкой.
Запуск фронтенда Micro
Чтобы протестировать все, нам нужно просто запустить оболочку и микрофронтенд:
ng serve shell -o
ng serve mfe1 -o
Затем, нажав на Flights в оболочке, вы загрузите микрофронтенд:
Совет: Вы также можете использовать npm скрипт
run:all
, который устанавливается вместе со схемами ng-add и init:
npm run run:all
Чтобы запустить только некоторые приложения, добавьте их имена в качестве аргументов:
npm run run:all shell mfe1
Рассматривая детали
Хорошо, это сработало довольно хорошо, но вы посмотрели на свой main.ts?
Это выглядит следующим образом:
import('./bootstrap')
.catch(err => console.error(err));
Код, обычно находящийся в файле main.ts
, был перемещен в файл bootstrap.ts
. Все это было сделано с помощью плагина @angular-architects/module-federation.
.
Хотя на первый взгляд в этом нет особого смысла, это типичная схема, встречающаяся в приложениях на базе Module Federation. Причина в том, что Федерации модулей необходимо решить, какую версию разделяемой библиотеки загрузить. Если оболочка, например, использует версию 12.0, а один из микрофронтендов уже собран с версией 12.1, она решит загрузить последний.
Для поиска метаданных, необходимых для принятия этого решения, Module Fedaration использует дополнительные динамические импорты, такие как этот здесь. В отличие от более традиционного статического импорта, динамический импорт является асинхронным. Поэтому Федерация модулей может решить, какие версии использовать, и фактически загрузить их.
Подробнее: Совместное использование зависимостей
Как упоминалось выше, использование shareAll
позволяет быстро выполнить первую настройку, которая «просто работает». Однако это может привести к появлению слишком большого количества общих пучков. Поскольку разделяемые зависимости не могут treeshaken
и по умолчанию оказываются в отдельных пакетах, которые необходимо загружать, вы можете захотеть оптимизировать это поведение, переключившись с shareAll
на share
:
// Import share instead of shareAll:
const { share, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
// Explicitly share packages:
shared: share({
"@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
}),
});
Заключение
Реализация Micro frontend до сих пор включала в себя множество уловок и обходных путей. Федерация модулей Webpack наконец-то предоставляет простое и надежное решение для этого. Для повышения производительности можно совместно использовать библиотеки, а также настроить стратегии для работы с несовместимыми версиями.
Также интересно, что микрофронтенды загружаются Webpack и без ссылок в исходном коде хоста или удаленного сайта. Это упрощает использование Федерации модулей и полученного исходного кода, который не требует дополнительных микрофронтенд-фреймворков.
Однако такой подход также возлагает большую ответственность на разработчиков. Например, необходимо убедиться, что компоненты, которые загружаются только во время выполнения и еще не были известны во время компиляции, также взаимодействуют как нужно.
Вам также придется иметь дело с возможными конфликтами версий. Например, вполне вероятно, что компоненты, скомпилированные с совершенно разными версиями Angular, не будут работать вместе во время выполнения. Этих случаев следует избегать с помощью конвенций или, по крайней мере, распознавать как можно раньше с помощью интеграционного тестирования.