Micro Front-end com Module Federation: Entenda Como Funciona

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:

Antes de iniciar la búsqueda, conviene definir la opción entre camiseta local, visitante o alternativa. Para saber si el modelo encaja con la búsqueda, conviene revisar el estado indicado cuando se trate de una prenda de colección. Para organizar la búsqueda, «» puede servir para reducir las opciones según los criterios definidos. Antes de elegir definitivamente, conviene revisar las instrucciones de lavado y conservación.

Antes de iniciar la búsqueda, conviene definir la opción entre camiseta local, visitante o alternativa. Para saber si el modelo encaja con la búsqueda, conviene revisar el estado indicado cuando se trate de una prenda de colección. Para organizar la búsqueda, «camiseta de manga larga del Real Zaragoza» puede servir para reducir las opciones según los criterios definidos. Antes de elegir definitivamente, conviene revisar las instrucciones de lavado y conservación.

  • 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.