Micro front-end module federation: Aplicação extensível

wandealves

Wanderson Alves Rodrigues

Posted on September 17, 2024

Micro front-end module federation: Aplicação extensível

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:

Image description

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
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

home.component.html

<h1>Home</h1>
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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';
}
Enter fullscreen mode Exit fullscreen mode

app.component.html

<app-navbar></app-navbar>
<router-outlet />
Enter fullscreen mode Exit fullscreen mode

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' },
];
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode
  • 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' },
];
Enter fullscreen mode Exit fullscreen mode

Rota dashboard-one

{
  path: 'dashboard-one',
  loadComponent: () =>
    loadRemoteModule({
      type: 'module',
      remoteEntry: 'http://localhost:4201/remoteEntry.js',
      exposedModule: './Component',
    }).then((m) => m.AppComponent),
},
Enter fullscreen mode Exit fullscreen mode
  • 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),
},
Enter fullscreen mode Exit fullscreen mode

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 },
Enter fullscreen mode Exit fullscreen mode

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' },
Enter fullscreen mode Exit fullscreen mode
  • 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",
    }),
  },
});
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode
  • 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",
  },
Enter fullscreen mode Exit fullscreen mode
  • remotes: Esta chave define quais micro front-end serão carregados remotamente.

No caso, foi definido dois micro front-end:

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",
    }),
  },
});
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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,
          },
        },
      },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

app.component.html

<div class="dashboard">
  <h1>Dashboard</h1>
  <div class="chart-container">
    <canvas id="barChart"></canvas>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

app.component.css

.dashboard {
  padding: 20px;
}
.chart-container {
  width: 100%;
  max-width: 600px;
  margin-bottom: 20px;
}
Enter fullscreen mode Exit fullscreen mode

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",
    }),
  },
});
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode
  • 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",
  },
Enter fullscreen mode Exit fullscreen mode
  • 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",
    }),
  },
});
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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,
      },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

app.component.html

<div class="dashboard">
  <h1>Dashboard</h1>
  <div class="chart-container">
    <canvas id="pieChart"></canvas>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

app.component.css

.dashboard {
  padding: 20px;
}
.chart-container {
  width: 100%;
  max-width: 600px;
  margin-bottom: 20px;
}
Enter fullscreen mode Exit fullscreen mode

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",
    }),
  },
});
Enter fullscreen mode Exit fullscreen mode

4 - Testar

Para testar execute cada aplicação com o comando:

npm start
Enter fullscreen mode Exit fullscreen mode

Acesse o link http://localhost:4200/, ao abrir será apresentado a tela:

Image description

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:

Module Federation

💖 💪 🙅 🚩
wandealves
Wanderson Alves Rodrigues

Posted on September 17, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related