Micro front-end module federation: Aplicação extensível
Wanderson Alves Rodrigues
Posted on September 17, 2024
A arquitetura de micro front-end é uma abordagem que visa trazer os princípios de microserviços para o desenvolvimento de front-ends, permitindo que grandes aplicações web sejam divididas em unidades menores e independentes. Uma das tecnologias-chave que possibilita essa abordagem é o Module Federation, um recurso poderoso do Webpack 5. Vamos exploraremos o que é o Module Federation, como ele funciona e como pode ser utilizado para construir aplicações modernas e escaláveis.
O que é Module Federation?
O Module Federation é um recurso do Webpack 5 que permite a execução de módulos JavaScript compartilhados entre diferentes aplicações ou diferentes partes de uma mesma aplicação. Ele facilita a criação de aplicações distribuídas, onde diferentes equipes podem desenvolver e implantar partes distintas da aplicação de forma independente.
Com o Module Federation, você pode:
- Compartilhar Módulos: Permite que módulos sejam compartilhados entre diferentes aplicações sem a necessidade de duplicação.
- Carregar Módulos Dinamicamente: Carregar módulos de forma dinâmica a partir de diferentes origens, o que é ideal para aplicações que utilizam uma arquitetura de micro front-end.
- Atualizar Módulos Independentemente: Atualizar uma parte da aplicação sem precisar republicar toda a aplicação.
Como Funciona o Module Federation?
O funcionamento do Module Federation baseia-se em dois conceitos principais:
- Host Application: É a aplicação que consome módulos de outras aplicações. Pode ser vista como a aplicação principal que integra outros módulos.
- Remote Application: São as aplicações ou partes de aplicações que expõem módulos para outras aplicações. Eles fornecem seus módulos de forma que podem ser consumidos por aplicações host.
Vamos colocar a mão na massa!
Vamos desenvolver a aplicação com Angular onde teremos uma aplicação host e duas aplicações remotas isolad.
Requisitos:
- Angular 18
- Node 20.17.0
Bibliotecas:
No nosso caso teremos uma aplicação Host e duas outras que será os plugins das aplicações remotas
1 - Host Application
Para criar om mfe-host execute o comando:
ng new mfe-app-host
ng add @angular-architects/module-federation --project mfe-app-host --port 4200 --type host --skip-confirmation
a - app/home
home.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-home',
standalone: true,
imports: [],
templateUrl: './home.component.html',
styleUrl: './home.component.css',
})
export class HomeComponent {}
home.component.html
<h1>Home</h1>
b - app/navbar
nabbar.component.ts
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-navbar',
standalone: true,
imports: [RouterLink],
templateUrl: './nabbar.component.html',
styleUrl: './nabbar.component.css',
})
export class NavbarComponent {}
nabbar.component.html
<nav class="navbar">
<div class="logo">
<a href="#home">MFE</a>
</div>
<div class="nav-links">
<a routerLink="/home">Home</a>
<a routerLink="/dashboard-one">Dashboard One</a>
<a routerLink="/dashboard-two">Dashboard Two</a>
</div>
</nav>
nabbar.component.css
.navbar {
background-color: #333;
overflow: hidden;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
}
.navbar a {
color: white;
text-decoration: none;
padding: 14px 16px;
}
.navbar a:hover {
background-color: #ddd;
color: black;
}
.logo {
font-size: 24px;
font-weight: bold;
}
.nav-links {
display: flex;
}
@media screen and (max-width: 600px) {
.navbar {
flex-direction: column;
}
.nav-links {
flex-direction: column;
width: 100%;
}
.navbar a {
text-align: center;
}
}
c) App Component
app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { NavbarComponent } from './navbar/nabbar.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, NavbarComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent {
title = 'mfe-app-host';
}
app.component.html
<app-navbar></app-navbar>
<router-outlet />
d) Routes
app.routes.ts
import { Routes } from '@angular/router';
import { loadRemoteModule } from '@angular-architects/module-federation';
import { HomeComponent } from './home/home.component';
export const routes: Routes = [
{
path: 'mfe-dashboard-one',
loadComponent: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4201/remoteEntry.js',
exposedModule: './Component',
}).then((m) => m.AppComponent),
},
{
path: 'mfe-dashboard-two',
loadComponent: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4202/remoteEntry.js',
exposedModule: './Component',
}).then((m) => m.AppComponent),
},
{ path: 'home', component: HomeComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' },
];
Detalhes:
Este código é uma configuração de rotas em um projeto Angular que utiliza a Federação de Módulos para carregar dinamicamente componentes de micro front-end remotos. Vamos detalhar cada parte do código:
Importações
import { Routes } from '@angular/router';
import { loadRemoteModule } from '@angular-architects/module-federation';
import { HomeComponent } from './home/home.component';
- Routes: É a interface do Angular usada para definir as rotas da aplicação. Uma rota associa um caminho (URL) a um componente ou módulo específico.
- loadRemoteModule: Função fornecida pela biblioteca @angular-architects/module-federation. Ela é usada para carregar dinamicamente módulos ou componentes expostos por microfrontends remotos, permitindo que a aplicação principal consuma esses componentes sem carregá-los no início.
- HomeComponent: O componente local que será renderizado quando a rota /home for acessada.
Definição de Rotas
export const routes: Routes = [
{
path: 'dashboard-one',
loadComponent: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4201/remoteEntry.js',
exposedModule: './Component', // Expondo o componente standalone
}).then((m) => m.AppComponent), // Nome do componente standalone
},
{
path: 'dashboard-two',
loadComponent: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4202/remoteEntry.js',
exposedModule: './Component', // Expondo o componente standalone
}).then((m) => m.AppComponent), // Nome do componente standalone
},
{ path: 'home', component: HomeComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' },
];
Rota dashboard-one
{
path: 'dashboard-one',
loadComponent: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4201/remoteEntry.js',
exposedModule: './Component',
}).then((m) => m.AppComponent),
},
path: 'dashboard-one': Define o caminho da URL para esta rota. Quando o usuário acessar /dashboard-one, esta rota será ativada.
loadComponent: Esta chave define que o componente para esta rota será carregado dinamicamente, em vez de ser carregado logo no início da aplicação. Isso ajuda a melhorar o tempo de carregamento inicial.
loadRemoteModule(): Função que carrega o módulo ou componente de um micro front-end remoto. O módulo exposto é definido no remoteEntry.js da aplicação remota que está rodando em http://localhost:4201/. A função carrega o módulo remoto e expõe o componente AppComponent.
type: 'module': Especifica que o tipo de módulo a ser carregado é um JavaScript ES module.
remoteEntry: 'http://localhost:4201/remoteEntry.js': Define o local do ponto de entrada (entry point) do micro front-end remoto, que é o arquivo remoteEntry.js gerado durante o build.
exposedModule: './Component': Este caminho especifica qual módulo ou componente foi exposto no micro front-end remoto. O remoteEntry.js expõe esse componente.
then((m) => m.AppComponent): Após carregar o módulo remoto, a função .then() extrai o componente específico (neste caso, AppComponent) e o associa à rota. Esse AppComponent é o componente standalone que foi exposto pelo micro front-end.
Rota dashboard-two
{
path: 'dashboard-two',
loadComponent: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4202/remoteEntry.js',
exposedModule: './Component',
}).then((m) => m.AppComponent),
},
Esta rota é semelhante à dashboard-one, mas carrega um componente de outro micro front-end, disponível em http://localhost:4202/remoteEntry.js.
Rota home
{ path: 'home', component: HomeComponent },
Define a rota /home, que carrega o componente local HomeComponent quando acessada. Este componente já está no projeto principal, então não requer carregamento dinâmico.
Rota padrão (redirecionamento)
{ path: '', redirectTo: '/home', pathMatch: 'full' },
- path: '': Define a rota padrão (a rota vazia).
- redirectTo: '/home': Redireciona qualquer usuário que acessar a raiz (/) da aplicação diretamente para a rota /home.
- pathMatch: 'full': Garante que o redirecionamento ocorra apenas quando o caminho completo for igual a '' (raiz). Isso evita redirecionamentos em URLs parciais.
e) Webpack
webpack.config.js
const {
shareAll,
withModuleFederationPlugin,
} = require("@angular-architects/module-federation/webpack");
module.exports = withModuleFederationPlugin({
remotes: {
mfeDashboardOne: "http://localhost:4201/remoteEntry.js",
mfeDashboardTwo: "http://localhost:4202/remoteEntry.js",
},
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
}),
},
});
Detalhes:
Este código configura a Module Federation no Webpack em um projeto Angular, permitindo que múltiplas aplicações compartilhem módulos de forma dinâmica. Vamos detalhar cada parte:
Importações
const {
shareAll,
withModuleFederationPlugin
} = require("@angular-architects/module-federation/webpack");
- shareAll: Esta função faz parte do pacote @angular-architects/module-federation e é usada para compartilhar dependências entre micro front-end. Ela permite que você especifique quais bibliotecas ou módulos devem ser compartilhados entre as diferentes aplicações que estão federadas, como dependências comuns (Angular, RxJS, etc.).
- withModuleFederationPlugin: Essa função é um wrapper em torno do ModuleFederationPlugin do Webpack. O objetivo dela é simplificar a configuração do plugin, tornando-o mais fácil de usar com Angular. Ela gera a configuração necessária para a federação de módulos e integra-se automaticamente ao processo de build do Angular.
Exportação da Configuração
module.exports = withModuleFederationPlugin({
remotes: {
mfeDashboardOne: "http://localhost:4201/remoteEntry.js",
mfeDashboardTwo: "http://localhost:4202/remoteEntry.js",
},
- remotes: Esta chave define quais micro front-end serão carregados remotamente.
No caso, foi definido dois micro front-end:
- mfeDashboardOne: O micro front-end disponível no endereço http://localhost:4201/remoteEntry.js. Esse remoteEntry.js é o ponto de entrada da aplicação federada, gerado durante o build.
- mfeDashboardTwo: O segundo micro front-end, disponível em http://localhost:4202/remoteEntry.js.
Cada micro front-end tem sua própria aplicação Angular rodando e exporta componentes, módulos ou serviços que podem ser consumidos por outros aplicativos.
Dependências Compartilhadas
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
}),
},
});
- shared: Esta chave especifica as dependências que serão compartilhadas entre a aplicação principal e os micro front-end. O uso de dependências compartilhadas permite que você evite carregar múltiplas cópias de bibliotecas (como Angular ou outras bibliotecas comuns) em cada micro front-end, o que melhora o desempenho e reduz o tamanho do bundle.
O método shareAll() instrui a aplicação a compartilhar todas as dependências que já foram instaladas no node_modules, com algumas opções:
singleton: true: Esta opção garante que apenas uma instância de cada biblioteca será carregada e compartilhada entre os micro front-end. Isso é importante, especialmente no caso de bibliotecas como o Angular, que não funcionam corretamente se houver múltiplas instâncias da mesma dependência rodando.
strictVersion: true: Garante que todos os micro front-end usarão exatamente a mesma versão das bibliotecas compartilhadas. Isso previne problemas de incompatibilidade entre diferentes versões da mesma biblioteca.
requiredVersion: "auto": O Webpack irá automaticamente definir qual versão das dependências será usada, baseada no que está no arquivo package.json da aplicação.
2 - Remote Application Dashboard One
Para criar om mfe-dashboard-one execute o comando:
ng new mfe-dashboard-one
ng add @angular-architects/module-federation --project mfe-dashboard-one --port 4201 --type remote --skip-confirmation
npm install chart.js
a - App Component
app.component.ts
import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { Chart } from 'chart.js/auto';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent implements OnInit {
ngOnInit() {
this.createBarChart();
}
createBarChart() {
new Chart('barChart', {
type: 'bar',
data: {
labels: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho'],
datasets: [
{
label: 'Vendas',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'rgba(75, 192, 192, 0.6)',
},
],
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
},
},
},
});
}
}
app.component.html
<div class="dashboard">
<h1>Dashboard</h1>
<div class="chart-container">
<canvas id="barChart"></canvas>
</div>
</div>
app.component.css
.dashboard {
padding: 20px;
}
.chart-container {
width: 100%;
max-width: 600px;
margin-bottom: 20px;
}
b - Webpack
webpack.config.js
const {
shareAll,
withModuleFederationPlugin,
} = require("@angular-architects/module-federation/webpack");
module.exports = withModuleFederationPlugin({
name: "mfe-dashboard-one",
exposes: {
"./Component": "./src/app/app.component.ts",
},
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
}),
},
});
Este código configura o Module Federation no Webpack para expor um componente de um micro front-end Angular chamado mfe-dashboard-one. Esse micro front-end poderá ser consumido por outras aplicações remotas. Vamos explicar cada parte em detalhes:
const {
shareAll,
withModuleFederationPlugin
} = require("@angular-architects/module-federation/webpack");
- shareAll: Função que facilita o compartilhamento de dependências entre a aplicação principal e os micro front-end. Ela configura o que será compartilhado, como bibliotecas de terceiros ou módulos Angular comuns.
- withModuleFederationPlugin: Função usada para configurar o Module Federation Plugin no Webpack de uma forma simplificada para projetos Angular. Ela ajuda a integrar as configurações necessárias da federação de módulos com o processo de build do Angular.
Exportação da Configuração
module.exports = withModuleFederationPlugin({
name: "mfe-dashboard-one",
exposes: {
"./Component": "./src/app/app.component.ts",
},
name: "mfe-dashboard-one": Define o nome da aplicação federada. Esse nome é utilizado internamente no Webpack e também quando outras aplicações querem consumir os módulos ou componentes expostos por este micro front-end. No caso, o nome do micro front-end é "mfe-dashboard-one".
exposes: Esta chave define quais módulos ou componentes do micro front-end estarão disponíveis para outras aplicações consumirem. No caso:
"./Component": Esse é o nome do módulo ou caminho pelo qual o componente será acessado externamente. Quando outra aplicação precisar usar esse componente, ela o referenciará como mfe-dashboard-one/Component.
"./src/app/app.component.ts": Especifica o arquivo onde o componente está localizado no projeto. Neste caso, o componente a ser exposto é o AppComponent, que está definido no arquivo app.component.ts **dentro do diretório **src/app.
Isso significa que o componente AppComponent será exportado e poderá ser carregado dinamicamente por outras aplicações usando o caminho exposto (./Component).
Dependências Compartilhadas
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
}),
},
});
- shared: Esta chave é usada para definir as dependências que serão compartilhadas entre o micro front-end e outras aplicações que consomem este micro front-end. O objetivo é evitar carregar múltiplas instâncias das mesmas dependências, o que otimiza a performance e reduz o tamanho do bundle.
shareAll: A função shareAll compartilha todas as bibliotecas instaladas no node_modules, configurando as opções para controlar o compartilhamento.
singleton: true: Esta opção garante que somente uma instância de cada biblioteca seja carregada e utilizada em toda a aplicação e nos micro front-end. Isso é crucial, especialmente para bibliotecas que não funcionam corretamente com múltiplas instâncias, como o Angular.
strictVersion: true: Assegura que as versões das bibliotecas compartilhadas sejam exatamente as mesmas entre as diferentes aplicações e micro front-end. Isso evita problemas de compatibilidade de versões.
requiredVersion: "auto": Permite ao Webpack determinar automaticamente a versão correta das bibliotecas a serem compartilhadas, com base nas versões definidas no package.json.
3 - Remote Application Dashboard two
Para criar **mfe-dashboard-two **execute o comando:
ng new mfe-dashboard-one
ng add @angular-architects/module-federation --project mfe-dashboard-two --port 4202 --type remote --skip-confirmation
npm install chart.js
a - App Component
app.component.ts
import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { Chart } from 'chart.js/auto';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent implements OnInit {
ngOnInit() {
this.createPieChart();
}
createPieChart() {
new Chart('pieChart', {
type: 'pie',
data: {
labels: ['Vermelho', 'Azul', 'Amarelo'],
datasets: [
{
label: 'Cores Favoritas',
data: [300, 50, 100],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
],
},
],
},
options: {
responsive: true,
},
});
}
}
app.component.html
<div class="dashboard">
<h1>Dashboard</h1>
<div class="chart-container">
<canvas id="pieChart"></canvas>
</div>
</div>
app.component.css
.dashboard {
padding: 20px;
}
.chart-container {
width: 100%;
max-width: 600px;
margin-bottom: 20px;
}
b - Webpack
webpack.config
const {
shareAll,
withModuleFederationPlugin,
} = require("@angular-architects/module-federation/webpack");
module.exports = withModuleFederationPlugin({
name: "mfe-dashboard-two",
exposes: {
"./Component": "./src/app/app.component.ts",
},
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
}),
},
});
4 - Testar
Para testar execute cada aplicação com o comando:
npm start
Acesse o link http://localhost:4200/, ao abrir será apresentado a tela:
Ao acessar no menu Dashboard Barra ou Dashboard Pizza, será apresentada as aplicações remotas que estão sendo executadas nas portas 4201 e 4202.
O código completo: https://github.com/wandealves/arquitetura-microfront-end
5 - Conclusão
O Module Federation é uma ferramenta poderosa que pode transformar a forma como desenvolvemos aplicações web complexas. Ao permitir a integração e compartilhamento de módulos entre diferentes aplicações, ele facilita a construção de sistemas escaláveis e modulares, alinhados com os princípios de micro front-end. No entanto, é essencial considerar os desafios e gerenciar cuidadosamente as dependências e a performance para tirar o máximo proveito dessa tecnologia.
Referências:
Posted on September 17, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.