Frontend

Micro Front-end com Module Federation: Entenda Como Funciona

Parte 2 - Micro Front-end com Module Federation: Construindo Aplicações Extensíveis

Micro Front-end com Module Federation: Entenda Como Funciona
Photo by James Harrison / Unsplash

Introdução

O Module Federation, introduzido no Webpack 5, revolucionou a forma como aplicações web podem compartilhar módulos em tempo de execução. Antes, o compartilhamento entre aplicações dependia de builds monolíticos ou bibliotecas externas, mas agora é possível carregar código de outra aplicação dinamicamente, mantendo isolamento e compatibilidade.

No coração do Module Federation estão três conceitos-chave:

  1. Host Application
  2. Remote Application
  3. Shared Dependencies

1. Host Application

O host é a aplicação principal que consome módulos externos. Ele define quem irá consumir, mas não necessariamente quem irá fornecer.

Principais responsabilidades do host:

  • Carregar módulos remotos dinamicamente.
  • Resolver dependências compartilhadas com os remotes.
  • Integrar os módulos carregados à sua própria aplicação, incluindo rotas, UI e estado.

Em tempo de execução, o host faz requisições para os arquivos remoteEntry.js dos remotes. Cada remoteEntry.js é um manifesto gerado pelo Webpack, contendo informações sobre módulos expostos e suas dependências.

Exemplo Conceitual:

const remotes = {
  mfeDashboardOne: "http://localhost:4201/remoteEntry.js",
  mfeDashboardTwo: "http://localhost:4202/remoteEntry.js"
};

Aqui, o host sabe onde buscar os remotes, mas não precisa conhecer os detalhes internos de implementação de cada módulo.

2. Remote Application

O remote é a aplicação que exponibiliza seus módulos para consumo externo. Ele não precisa saber quem vai usar os módulos; sua responsabilidade é disponibilizá-los de forma clara e consistente.

Principais conceitos:

  • Exposes: Define quais módulos podem ser consumidos externamente.
  • Entry Points: Arquivos como remoteEntry.js que registram os módulos expostos.
  • Independência: Cada remote pode ter seu próprio ciclo de deploy, dependências e versão.

Exemplo de configuração do remote:

module.exports = withModuleFederationPlugin({
  name: "mfe-dashboard-one",
  exposes: {
    "./Component": "./src/app/app.component.ts",
  },
  shared: {
    ...shareAll({ singleton: true, strictVersion: true, requiredVersion: "auto" })
  }
});
  • name: Nome único do remote, usado pelo host para identificação.
  • exposes: Caminho dos módulos que serão consumidos externamente.
  • shared: Bibliotecas que podem ser reutilizadas para evitar duplicação.

3. Shared Dependencies

Um dos maiores desafios de micro front-ends é o gerenciamento de dependências. Sem cuidado, cada micro front-end poderia carregar sua própria cópia de bibliotecas comuns (Angular, React, Lodash), resultando em:

  • Bundles maiores.
  • Incompatibilidades de versão.
  • Bugs complexos e difíceis de reproduzir.

O Module Federation resolve isso através do shared, permitindo que uma única instância de cada biblioteca seja compartilhada entre host e remotes.

Configuração típica de shared:

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

Explicando cada opção:

  • singleton: true: Garante que apenas uma instância da biblioteca será carregada. Fundamental para frameworks como Angular.
  • strictVersion: true: Exige que todas as aplicações usem a mesma versão da biblioteca.
  • requiredVersion: "auto": O Webpack define automaticamente a versão com base no package.json.

Isso garante compatibilidade e performance, evitando que cada módulo carregue sua própria versão de dependências comuns.

RemoteEntry.js e Runtime do Module Federation

O arquivo remoteEntry.js é o coração do módulo federado. Ele contém:

  • Informações sobre os módulos expostos (exposes).
  • Mapas de dependências compartilhadas (shared).
  • Código para registrar os módulos no runtime do Webpack.

Quando o host carrega um módulo remoto, o Webpack cria dinamicamente promises para cada dependência e injeta os módulos carregados no contexto do host.

Fluxo de carregamento:

  1. Host solicita remoteEntry.js.
  2. Webpack registra os módulos expostos e suas dependências.
  3. Host consome os módulos usando loadRemoteModule().
  4. Webpack resolve as dependências compartilhadas, evitando duplicação.
  5. Componente ou módulo é renderizado no host.

Estratégias de Versionamento

Gerenciar versões em micro front-ends é crucial. Algumas estratégias:

  1. Single Version: Todos os micro front-ends usam a mesma versão das bibliotecas compartilhadas. Fácil de manter, mas menos flexível.
  2. Multiple Versions: Permite múltiplas versões da mesma biblioteca, isolando cada remote. Útil quando migrações graduais são necessárias, mas aumenta o bundle.
  3. Semver Awareness: Usar requiredVersion: "auto" e strictVersion para garantir compatibilidade mínima, sem forçar versões idênticas.

Boas práticas:

  • Sempre documentar a versão das dependências compartilhadas.
  • Evitar alterações bruscas de versão sem testar a compatibilidade com o host.
  • Para grandes sistemas, considerar pipelines de CI/CD que validem todos os remotes juntos antes do deploy.

Comunicação entre Host e Remotes

Além de compartilhar módulos, muitas vezes precisamos compartilhar estado ou dados entre host e remotes. Algumas abordagens comuns:

  1. Eventos Customizados: Remotes emitem eventos, host escuta e reage.
  2. Serviços Compartilhados: Criar serviços singleton que podem ser injetados em múltiplos módulos.
  3. State Management Global: Usar bibliotecas como NgRx, Redux ou RxJS BehaviorSubjects compartilhados via shared.

Exemplo simplificado usando BehaviorSubject compartilhado:

// shared-state.ts
import { BehaviorSubject } from 'rxjs';

export const userState = new BehaviorSubject<{name: string}>({ name: '' });

Tanto host quanto remotes podem importar userState e reagir a mudanças em tempo real.

Desafios Técnicos do Module Federation

Apesar de poderoso, Module Federation exige cuidado em:

  • Carregamento assíncrono: Dependências críticas podem atrasar a renderização.
  • Bundle duplication: Evitar múltiplas versões de bibliotecas.
  • Debugging: Problemas de versão e dependências podem ser difíceis de rastrear.
  • Rotas complexas: Micro front-ends com múltiplas rotas internas requerem configuração adequada no host.

Conclusão

Nesta parte, entendemos profundamente como o Module Federation funciona:

  • Host consome, remote expõe.
  • Shared dependencies resolvem conflitos de bibliotecas.
  • remoteEntry.js atua como ponto de registro de módulos.
  • Estratégias de versionamento e compartilhamento de estado são cruciais para sucesso em sistemas grandes.

Na Parte 3, vamos aplicar tudo isso em Angular, construindo um host e dois remotes, detalhando código, rotas, webpack e comunicação entre módulos, mostrando o passo a passo completo.

Você também pode gostar