DEV Community

Cover image for Micro Frontends em Angular: Guia Prático com Module Federation

Micro Frontends em Angular: Guia Prático com Module Federation

✍️ Autor e Código-Fonte

Este artigo documenta um projeto real e de código aberto.

Explore o código-fonte completo nos repositórios oficiais do projeto:


Nos últimos anos, a arquitetura de Micro Frontends (MFE) tem ganhado destaque como solução para times que precisam escalar aplicações frontend de forma independente. Neste artigo, vou mostrar como implementar MFE em Angular usando Module Federation do Webpack 5.

O que são Micro Frontends?

Micro Frontends é um padrão arquitetural que estende os conceitos de microserviços para o frontend. A ideia é dividir uma aplicação monolítica em partes menores e independentes, onde cada time pode desenvolver, testar e fazer deploy de forma autônoma.

Principais Benefícios

  • Autonomia dos times: Cada equipe trabalha em seu próprio repositório e ciclo de deploy
  • Escalabilidade técnica: Diferentes tecnologias e versões podem coexistir
  • Deploy independente: Alterações em um módulo não exigem rebuild de toda aplicação
  • Manutenibilidade: Código mais organizado e com responsabilidades bem definidas

Module Federation: O Game Changer

Module Federation é uma funcionalidade nativa do Webpack 5 que permite compartilhar código entre aplicações JavaScript em tempo de execução, sem precisar recompilar ou fazer bundle novamente.

Como funciona?

O Module Federation divide as aplicações em dois tipos:

  • Host (Shell): Aplicação principal que consome os módulos remotos
  • Remote: Aplicação que expõe módulos para serem consumidos

Implementando na Prática

Vamos criar um exemplo com uma aplicação shell (host) e dois micro frontends remotos.

Passo 1: Configurando o Projeto

Primeiro, vamos usar o @angular-architects/module-federation para facilitar a configuração:

# Instalar o Angular CLI
npm install -g @angular/cli

# Criar workspace
ng new mfe-workspace --create-application=false
cd mfe-workspace

# Criar aplicações
ng generate application shell --routing --style=scss
ng generate application mfe-products --routing --style=scss
ng generate application mfe-orders --routing --style=scss
Enter fullscreen mode Exit fullscreen mode

Passo 2: Adicionar Module Federation

Instale o plugin em cada aplicação:

# No diretório raiz
ng add @angular-architects/module-federation --project shell --port 4200 --type host
ng add @angular-architects/module-federation --project mfe-products --port 4201 --type remote
ng add @angular-architects/module-federation --project mfe-orders --port 4202 --type remote
Enter fullscreen mode Exit fullscreen mode

Passo 3: Configurar o Remote (mfe-products)

No arquivo webpack.config.js do mfe-products:

const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');

module.exports = withModuleFederationPlugin({
  name: 'mfeProducts',

  exposes: {
    './ProductsModule': './projects/mfe-products/src/app/products/products.module.ts',
  },

  shared: {
    ...shareAll({ 
      singleton: true, 
      strictVersion: true, 
      requiredVersion: 'auto' 
    }),
  },
});
Enter fullscreen mode Exit fullscreen mode

Crie o módulo de produtos:

// products.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { ProductsComponent } from './products.component';

const routes: Routes = [
  {
    path: '',
    component: ProductsComponent
  }
];

@NgModule({
  declarations: [ProductsComponent],
  imports: [
    CommonModule,
    RouterModule.forChild(routes)
  ]
})
export class ProductsModule { }
Enter fullscreen mode Exit fullscreen mode

Passo 4: Configurar o Host (Shell)

No arquivo webpack.config.js do shell:

const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');

module.exports = withModuleFederationPlugin({
  remotes: {
    "mfeProducts": "http://localhost:4201/remoteEntry.js",
    "mfeOrders": "http://localhost:4202/remoteEntry.js",
  },

  shared: {
    ...shareAll({ 
      singleton: true, 
      strictVersion: true, 
      requiredVersion: 'auto' 
    }),
  },
});
Enter fullscreen mode Exit fullscreen mode

Configure as rotas no app-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { loadRemoteModule } from '@angular-architects/module-federation';

const routes: Routes = [
  {
    path: 'products',
    loadChildren: () =>
      loadRemoteModule({
        type: 'module',
        remoteEntry: 'http://localhost:4201/remoteEntry.js',
        exposedModule: './ProductsModule'
      })
      .then(m => m.ProductsModule)
  },
  {
    path: 'orders',
    loadChildren: () =>
      loadRemoteModule({
        type: 'module',
        remoteEntry: 'http://localhost:4202/remoteEntry.js',
        exposedModule: './OrdersModule'
      })
      .then(m => m.OrdersModule)
  },
  {
    path: '',
    redirectTo: '/products',
    pathMatch: 'full'
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
Enter fullscreen mode Exit fullscreen mode

Passo 5: Configuração Dinâmica

Para ambientes diferentes, crie um arquivo mfe.manifest.json:

{
  "mfeProducts": "http://localhost:4201/remoteEntry.js",
  "mfeOrders": "http://localhost:4202/remoteEntry.js"
}
Enter fullscreen mode Exit fullscreen mode

Carregue o manifesto dinamicamente:

// main.ts
import { loadManifest } from '@angular-architects/module-federation';

loadManifest('/assets/mfe.manifest.json')
  .catch(err => console.error('Error loading manifest', err))
  .then(() => import('./bootstrap'))
  .catch(err => console.error(err));
Enter fullscreen mode Exit fullscreen mode

Compartilhamento de Estado

Para comunicação entre MFEs, você pode usar diferentes estratégias:

1. Usando RxJS e Services Compartilhados

// shared-state.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class SharedStateService {
  private userSubject = new BehaviorSubject<any>(null);
  public user$: Observable<any> = this.userSubject.asObservable();

  setUser(user: any): void {
    this.userSubject.next(user);
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Usando Custom Events

// Emitir evento
window.dispatchEvent(new CustomEvent('user-updated', { 
  detail: { userId: 123 } 
}));

// Ouvir evento
window.addEventListener('user-updated', (event: any) => {
  console.log('User updated:', event.detail);
});
Enter fullscreen mode Exit fullscreen mode

Boas Práticas

1. Versionamento

Use semantic versioning para suas bibliotecas compartilhadas:

shared: {
  '@angular/core': { 
    singleton: true, 
    strictVersion: true,
    requiredVersion: '^17.0.0'
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Tratamento de Erros

Implemente error boundaries para falhas no carregamento de MFEs:

loadChildren: () =>
  loadRemoteModule({
    type: 'module',
    remoteEntry: 'http://localhost:4201/remoteEntry.js',
    exposedModule: './ProductsModule'
  })
  .then(m => m.ProductsModule)
  .catch(err => {
    console.error('Error loading module', err);
    return import('./fallback/fallback.module').then(m => m.FallbackModule);
  })
Enter fullscreen mode Exit fullscreen mode

3. Performance

  • Use lazy loading para carregar MFEs sob demanda
  • Implemente caching strategies para remoteEntry.js
  • Monitore o tamanho dos bundles compartilhados

4. Testes

Configure testes para cada MFE independentemente:

// products.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProductsComponent } from './products.component';

describe('ProductsComponent', () => {
  let component: ProductsComponent;
  let fixture: ComponentFixture<ProductsComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ ProductsComponent ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ProductsComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
Enter fullscreen mode Exit fullscreen mode

Deploy e CI/CD

Para deploy independente, configure pipelines separados:

# .github/workflows/mfe-products.yml
name: Deploy MFE Products

on:
  push:
    branches: [ main ]
    paths:
      - 'projects/mfe-products/**'

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Setup Node
        uses: actions/setup-node@v2
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build:mfe-products

      - name: Deploy
        run: |
          # Deploy para seu CDN/Storage
          aws s3 sync dist/mfe-products s3://your-bucket/mfe-products
Enter fullscreen mode Exit fullscreen mode

Desafios e Considerações

1. Debugging

Debugging pode ser mais complexo. Use source maps e ferramentas como:

// webpack.config.js
module.exports = {
  devtool: 'source-map',
  // ...
}
Enter fullscreen mode Exit fullscreen mode

2. Dependências Compartilhadas

Cuidado com o tamanho do bundle. Use o bundle analyzer:

npm install --save-dev webpack-bundle-analyzer
Enter fullscreen mode Exit fullscreen mode

3. Consistência de UI

Crie uma biblioteca de design system compartilhada:

ng generate library ui-components
Enter fullscreen mode Exit fullscreen mode

Conclusão

Module Federation transformou a forma como construímos Micro Frontends em Angular. Com ele, conseguimos:

  • Deploy independente de aplicações
  • Compartilhamento de código em runtime
  • Redução significativa no tamanho dos bundles
  • Maior autonomia para os times

A arquitetura de MFE não é uma bala de prata e adiciona complexidade ao projeto, mas para times grandes trabalhando em aplicações enterprise, os benefícios superam os desafios.

Recursos Úteis

Repositório de Exemplo

Você pode encontrar o código completo deste tutorial no GitHub: [https://github.com/GhabryelHenrique/angular-mfe-example]


O que você achou deste artigo? Já trabalha com Micro Frontends? Compartilhe sua experiência nos comentários!

Se este conteúdo foi útil, não esqueça de dar um ❤️ e compartilhar com outros devs!

Top comments (0)