DEV Community

Hernani Almeida
Hernani Almeida

Posted on

Angular 21 + Native Federation — Parte 2: Integração, Debug e Boas Práticas

Na primeira parte, vimos como criar uma arquitetura de Micro Frontends utilizando Angular 21 e Native Federation.

Nesta segunda parte, vamos aprofundar a integração entre Host e Remote, entendendo o que acontece por trás da configuração e, principalmente, alguns pontos que podem gerar erros durante a implementação.

A ideia é sair de uma configuração que simplesmente "funciona" para entender por que ela funciona.


1. Arquitetura que vamos construir

Teremos dois projetos:

angular-micro-frontends/
│
├── mfe-host-2/
│   └── Angular 21
│       └── Host
│
└── mfe-remote-2/
    └── Angular 21
        └── Remote
Enter fullscreen mode Exit fullscreen mode

Durante o desenvolvimento:

Host
http://localhost:4200

Remote
http://localhost:4201
Enter fullscreen mode Exit fullscreen mode

O fluxo será:

                 HOST
              localhost:4200
                   │
                   │
                   ▼
       federation.manifest.json
                   │
                   │
                   ▼
              Remote :4201
                   │
                   ▼
           remoteEntry.json
                   │
                   ▼
             ./Component
                   │
                   ▼
                 App
Enter fullscreen mode Exit fullscreen mode

O ponto importante é entender que o Host não importa diretamente o arquivo app.ts do Remote.

Ele conhece apenas o nome do Remote e o módulo que o Remote decidiu expor.


2. O papel do federation.config.js

No Remote temos:

const {
  withNativeFederation,
  shareAll
} = require('@angular-architects/native-federation/config');

module.exports = withNativeFederation({
  name: 'mfe-remote-2',

  exposes: {
    './Component': './src/app/app.ts',
  },

  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
    }),
  },

  skip: [
    'rxjs/ajax',
    'rxjs/fetch',
    'rxjs/testing',
    'rxjs/webSocket',
  ],

  features: {
    ignoreUnusedDeps: true,
  },
});
Enter fullscreen mode Exit fullscreen mode

Aqui temos uma das partes mais importantes da configuração:

exposes: {
  './Component': './src/app/app.ts',
}
Enter fullscreen mode Exit fullscreen mode

Isso significa:

"Quando outro aplicativo solicitar ./Component, entregue o módulo localizado em ./src/app/app.ts."

O nome ./Component é um nome de exposição, não precisa ser igual ao nome do arquivo.

Por exemplo, também poderíamos ter:

exposes: {
  './Products': './src/app/pages/products/products.ts',
}
Enter fullscreen mode Exit fullscreen mode

E no Host:

loadRemoteModule('mfe-remote-2', './Products')
Enter fullscreen mode Exit fullscreen mode

3. ./Component não é o nome da classe

Esse é um ponto que pode gerar bastante confusão.

Temos:

exposes: {
  './Component': './src/app/app.ts',
}
Enter fullscreen mode Exit fullscreen mode

e dentro do arquivo:

export class App {
}
Enter fullscreen mode Exit fullscreen mode

No Host:

loadRemoteModule('mfe-remote-2', './Component')
  .then((m) => m.App);
Enter fullscreen mode Exit fullscreen mode

São três coisas diferentes:

mfe-remote-2
     │
     └── nome do Remote

./Component
     │
     └── nome da exposição

App
     │
     └── exportação JavaScript/TypeScript
Enter fullscreen mode Exit fullscreen mode

Podemos visualizar assim:

Host
 │
 │ mfe-remote-2
 │
 ▼
Remote
 │
 │ ./Component
 │
 ▼
src/app/app.ts
 │
 │ export class App
 │
 ▼
m.App
Enter fullscreen mode Exit fullscreen mode

Portanto, se seu componente se chama:

export class App {
}
Enter fullscreen mode Exit fullscreen mode

o correto é:

.then((m) => m.App)
Enter fullscreen mode Exit fullscreen mode

e não:

.then((m) => m.AppComponent)
Enter fullscreen mode Exit fullscreen mode

4. O papel do federation.manifest.json

No Host criamos:

public/
└── assets/
    └── federation.manifest.json
Enter fullscreen mode Exit fullscreen mode

Com:

{
  "mfe-remote-2": "http://localhost:4201/remoteEntry.json"
}
Enter fullscreen mode Exit fullscreen mode

Esse arquivo funciona como um catálogo dos Remotes que o Host conhece.

Podemos ter vários:

{
  "mfe-1": "http://localhost:4201/remoteEntry.json",
  "mfe-2": "http://localhost:4202/remoteEntry.json",
  "mfe-3": "http://localhost:4203/remoteEntry.json"
}
Enter fullscreen mode Exit fullscreen mode

Isso facilita bastante a configuração de uma arquitetura com vários Micro Frontends.


5. Cuidado com o caminho do manifest

No main.ts do Host:

import { initFederation } from '@angular-architects/native-federation';

initFederation('/assets/federation.manifest.json')
  .catch((err) => console.error(err))
  .then(() => import('./bootstrap'))
  .catch((err) => console.error(err));
Enter fullscreen mode Exit fullscreen mode

Prefira:

/assets/federation.manifest.json
Enter fullscreen mode Exit fullscreen mode

em vez de:

../assets/federation.manifest.json
Enter fullscreen mode Exit fullscreen mode

O motivo é simples: o primeiro é resolvido a partir da raiz da aplicação.

Se o Host estiver rodando em:

http://localhost:4200
Enter fullscreen mode Exit fullscreen mode

o navegador buscará:

http://localhost:4200/assets/federation.manifest.json
Enter fullscreen mode Exit fullscreen mode

Podemos validar isso diretamente no navegador.

Se retornar:

{
  "mfe-remote-2": "http://localhost:4201/remoteEntry.json"
}
Enter fullscreen mode Exit fullscreen mode

o manifest está sendo servido corretamente.


6. remoteEntry.json é diferente do antigo remoteEntry.js

Quem está acostumado com Webpack Module Federation pode estranhar isso.

No modelo tradicional era comum encontrar:

remoteEntry.js
Enter fullscreen mode Exit fullscreen mode

No Native Federation trabalhamos com:

remoteEntry.json
Enter fullscreen mode Exit fullscreen mode

Por exemplo:

http://localhost:4201/remoteEntry.json
Enter fullscreen mode Exit fullscreen mode

Esse arquivo contém as informações necessárias para que o runtime do Native Federation descubra os módulos expostos pelo Remote.

Por isso, uma boa estratégia de debug é sempre testar:

http://localhost:4201/remoteEntry.json
Enter fullscreen mode Exit fullscreen mode

Se ele não abrir, não adianta investigar a rota do Host ainda.

Primeiro precisamos resolver o Remote.


7. A rota do Host

No Angular 21 podemos utilizar componentes standalone diretamente:

import { loadRemoteModule } from '@angular-architects/native-federation';
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'mfe-1',
    loadComponent: () =>
      loadRemoteModule('mfe-remote-2', './Component')
        .then((m) => m.App),
  },
];
Enter fullscreen mode Exit fullscreen mode

Quando o usuário acessa:

http://localhost:4200/mfe-1
Enter fullscreen mode Exit fullscreen mode

o Angular executa:

/mfe-1
   │
   ▼
loadComponent()
   │
   ▼
loadRemoteModule()
   │
   ▼
mfe-remote-2
   │
   ▼
./Component
   │
   ▼
App
Enter fullscreen mode Exit fullscreen mode

8. path precisa bater com o link

Um erro simples, mas fácil de passar despercebido.

Se temos:

{
  path: 'mfe-2',
  loadComponent: () =>
    loadRemoteModule('mfe-remote-2', './Component')
      .then((m) => m.App),
}
Enter fullscreen mode Exit fullscreen mode

o link precisa ser:

<a routerLink="/mfe-2">
  Acesse MFE-2
</a>
Enter fullscreen mode Exit fullscreen mode

Não:

<a routerLink="/mfe-1">
  Acesse MFE-2
</a>
Enter fullscreen mode Exit fullscreen mode

Parece óbvio, mas em uma aplicação com vários MFEs esse tipo de erro pode fazer parecer que o Federation não está funcionando.


9. Não esquecer o RouterOutlet

O Host precisa ter onde renderizar o componente remoto.

No app.html:

<div>
  <h1>Angular 21 - Micro Frontends</h1>

  <nav>
    <a routerLink="/mfe-1">
      Acesse MFE-1
    </a>

    <br />

    <a routerLink="/mfe-2">
      Acesse MFE-2
    </a>
  </nav>
</div>

<hr />

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

E como estamos usando standalone components:

import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  imports: [
    RouterLink,
    RouterOutlet
  ],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App {
}
Enter fullscreen mode Exit fullscreen mode

Não basta configurar:

provideRouter(routes)
Enter fullscreen mode Exit fullscreen mode

Também precisamos que o template possua:

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

10. Não esquecer o provideRouter

No app.config.ts:

import {
  ApplicationConfig,
  provideBrowserGlobalErrorListeners
} from '@angular/core';

import { provideRouter } from '@angular/router';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes)
  ]
};
Enter fullscreen mode Exit fullscreen mode

A cadeia completa fica:

main.ts
   │
   ▼
bootstrap
   │
   ▼
app.config.ts
   │
   ▼
provideRouter(routes)
   │
   ▼
app.routes.ts
   │
   ▼
loadRemoteModule()
Enter fullscreen mode Exit fullscreen mode

Se qualquer uma dessas partes estiver faltando, a rota pode não funcionar.


11. O Remote também pode funcionar sozinho

Uma das características mais interessantes dessa arquitetura é que o Remote não precisa existir somente dentro do Host.

Podemos acessar:

http://localhost:4201/
Enter fullscreen mode Exit fullscreen mode

diretamente.

O mesmo componente:

export class App {
}
Enter fullscreen mode Exit fullscreen mode

pode ser usado como aplicação independente.

E também:

http://localhost:4200/mfe-1
Enter fullscreen mode Exit fullscreen mode

como Micro Frontend.

Visualmente:

                    App
                     │
             ┌───────┴────────┐
             │                │
             ▼                ▼
       Acesso direto       Federation
       :4201               :4200/mfe-1
Enter fullscreen mode Exit fullscreen mode

Isso é muito útil durante o desenvolvimento porque o time responsável pelo MFE pode trabalhar sem precisar subir toda a aplicação Host.


12. SSE: o que significa?

Durante o desenvolvimento podemos encontrar no terminal:

[Federation SSE] Client connected.
Active connections: 1
Enter fullscreen mode Exit fullscreen mode

Isso é esperado.

SSE significa:

Server-Sent Events.

O Native Federation utiliza esse mecanismo para comunicação entre o servidor de desenvolvimento e o navegador.

Uma mensagem como:

[Federation SSE] Client connected.
Enter fullscreen mode Exit fullscreen mode

significa que o navegador conseguiu estabelecer a conexão.

Portanto, essa mensagem isoladamente não representa um erro.

Se aparecer:

[Federation SSE] Client connected.
Enter fullscreen mode Exit fullscreen mode

temos uma indicação positiva de que o canal de desenvolvimento foi estabelecido.


13. Como fazer troubleshooting

Quando o MFE não aparece, evite alterar várias configurações ao mesmo tempo.

Faça os testes em ordem.

Passo 1 — Remote

Abra:

http://localhost:4201/
Enter fullscreen mode Exit fullscreen mode

O Remote deve funcionar sozinho.


Passo 2 — remoteEntry

Abra:

http://localhost:4201/remoteEntry.json
Enter fullscreen mode Exit fullscreen mode

Deve retornar o metadata do Remote.


Passo 3 — Manifest

Abra:

http://localhost:4200/assets/federation.manifest.json
Enter fullscreen mode Exit fullscreen mode

Deve retornar:

{
  "mfe-remote-2": "http://localhost:4201/remoteEntry.json"
}
Enter fullscreen mode Exit fullscreen mode

Passo 4 — Host

Abra:

http://localhost:4200/
Enter fullscreen mode Exit fullscreen mode

ou a rota correspondente:

http://localhost:4200/mfe-1
Enter fullscreen mode Exit fullscreen mode

Passo 5 — DevTools

Abra:

F12
Enter fullscreen mode Exit fullscreen mode

e verifique a aba:

Network
Enter fullscreen mode Exit fullscreen mode

Procure por:

federation.manifest.json
remoteEntry.json
Enter fullscreen mode Exit fullscreen mode

Se ambos carregarem, o próximo ponto a investigar é a resolução do módulo exposto.


14. Um erro importante: 404 no manifest

Um erro como:

GET http://localhost:4200/assets/federation.manifest.json 404
Enter fullscreen mode Exit fullscreen mode

significa que o navegador não encontrou o manifest.

Uma configuração comum no Angular 21 é:

"assets": [
  {
    "glob": "**/*",
    "input": "public"
  }
]
Enter fullscreen mode Exit fullscreen mode

Nesse caso, o arquivo precisa estar em:

public/assets/federation.manifest.json
Enter fullscreen mode Exit fullscreen mode

e não necessariamente em:

src/assets/
Enter fullscreen mode Exit fullscreen mode

Portanto:

mfe-host-2/
│
├── public/
│   └── assets/
│       └── federation.manifest.json
│
└── src/
    └── app/
Enter fullscreen mode Exit fullscreen mode

15. CSS em Micro Frontends

Outro ponto importante é o isolamento dos estilos.

Imagine que o Remote tenha:

src/
└── app/
    ├── app.ts
    ├── app.html
    └── app.css
Enter fullscreen mode Exit fullscreen mode

Podemos utilizar:

@Component({
  selector: 'app-root',
  imports: [],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App {
}
Enter fullscreen mode Exit fullscreen mode

A recomendação para um MFE é priorizar estilos associados aos próprios componentes.

Por exemplo:

.container {
  padding: 20px;
}

.title {
  font-size: 32px;
}
Enter fullscreen mode Exit fullscreen mode

Evite, quando possível, que o MFE altere globalmente:

body {
  ...
}

* {
  ...
}

html {
  ...
}
Enter fullscreen mode Exit fullscreen mode

porque o Remote pode ser executado dentro de outro aplicativo.


16. Por que evitar CSS global no MFE?

Imagine:

Host
│
├── Header
├── Menu
│
└── MFE
     │
     └── styles.css
Enter fullscreen mode Exit fullscreen mode

Se o MFE possui:

body {
  margin: 0;
  background: black;
}
Enter fullscreen mode Exit fullscreen mode

esse estilo pode interferir na aplicação Host.

Por isso, uma boa regra é:

O Host controla o layout global da aplicação. O MFE controla o visual dos componentes que ele entrega.

Isso permite que o mesmo MFE seja executado:

localhost:4201
Enter fullscreen mode Exit fullscreen mode

ou:

localhost:4200/mfe-1
Enter fullscreen mode Exit fullscreen mode

sem depender de um CSS global específico do Host.


17. Um Remote pode expor mais de um componente

Não precisamos expor apenas o App.

Podemos ter:

Top comments (0)