Module Federation com Webpack 5: compartilhando código entre apps em runtime
1. O que é Module Federation e por que surgiu?
O desenvolvimento de aplicações frontend modernas frequentemente enfrenta o desafio da duplicação de código. Em arquiteturas de micro frontends e monorepos, equipes diferentes criam componentes que realizam funções similares, resultando em redundância e aumento do tamanho do bundle final. Antes do Module Federation, as soluções comuns envolviam:
- NPM packages: compartilhamento em build time, exigindo nova compilação de todas as aplicações consumidoras a cada atualização.
- Iframes: isolamento completo, mas com comunicação limitada e problemas de performance e UX.
O Module Federation, introduzido no Webpack 5, resolve esse problema permitindo o compartilhamento de código em runtime. Isso significa que uma aplicação pode carregar componentes de outra aplicação em tempo real, sem necessidade de rebuild ou deploy coordenado. O conceito fundamental é que o código é baixado e executado dinamicamente no navegador, eliminando a duplicação e permitindo que equipes trabalhem de forma independente.
2. Arquitetura básica: host, remote e exposição de módulos
A arquitetura do Module Federation é baseada em dois papéis principais:
- Host (container): aplicação principal que consome módulos de outras aplicações.
- Remote: aplicação que expõe componentes ou módulos para serem consumidos.
A configuração é feita através do plugin ModuleFederationPlugin no Webpack 5. O remote declara quais módulos deseja expor usando a opção exposes, enquanto o host especifica quais remotes deseja consumir via remotes. Ambos podem definir bibliotecas compartilhadas através da opção shared.
// Exemplo conceitual da configuração do ModuleFederationPlugin
// Remote (app-micro) expõe um componente
new ModuleFederationPlugin({
name: 'app_micro',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./Header': './src/components/Header'
},
shared: ['react', 'react-dom']
})
// Host (app-shell) consome o remote
new ModuleFederationPlugin({
name: 'app_shell',
remotes: {
app_micro: 'app_micro@http://localhost:3001/remoteEntry.js'
},
shared: ['react', 'react-dom']
})
3. Configurando o primeiro projeto com Module Federation
Vamos criar uma estrutura simples com duas aplicações: app-shell (host) e app-micro (remote). Cada uma terá seu próprio webpack.config.js.
Estrutura de pastas:
projeto/
├── app-shell/
│ ├── src/
│ │ └── index.js
│ ├── public/
│ │ └── index.html
│ └── webpack.config.js
└── app-micro/
├── src/
│ ├── components/
│ │ └── Button.js
│ └── index.js
├── public/
│ └── index.html
└── webpack.config.js
Configuração do remote (app-micro/webpack.config.js):
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'development',
devServer: { port: 3001 },
plugins: [
new ModuleFederationPlugin({
name: 'app_micro',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button'
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } }
}),
new HtmlWebpackPlugin({ template: './public/index.html' })
]
};
Configuração do host (app-shell/webpack.config.js):
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'development',
devServer: { port: 3000 },
plugins: [
new ModuleFederationPlugin({
name: 'app_shell',
remotes: {
app_micro: 'app_micro@http://localhost:3001/remoteEntry.js'
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } }
}),
new HtmlWebpackPlugin({ template: './public/index.html' })
]
};
4. Compartilhamento de dependências: shared e fallbacks
O parâmetro shared é crucial para evitar múltiplas instâncias de bibliotecas como React. Quando configurado como singleton: true, o Module Federation garante que apenas uma instância da biblioteca seja carregada, mesmo que diferentes versões sejam solicitadas.
// Configuração avançada de shared
shared: {
react: {
singleton: true,
requiredVersion: '^17.0.0',
eager: false
},
'react-dom': {
singleton: true,
requiredVersion: '^17.0.0',
eager: false
},
lodash: {
singleton: false,
requiredVersion: false
}
}
- singleton: força uma única instância global.
- eager: carrega a dependência imediatamente, sem lazy loading.
- requiredVersion: especifica a versão mínima aceitável.
Quando versões conflitantes são detectadas, o Module Federation tenta resolver usando a versão mais alta compatível ou lança um aviso.
5. Carregamento assíncrono, lazy loading e fallbacks
Para carregar módulos remotos de forma eficiente, combinamos o Module Federation com React.lazy e Suspense. Isso permite que o componente seja baixado apenas quando necessário.
// app-shell/src/App.js
import React, { Suspense, lazy } from 'react';
const RemoteButton = lazy(() => import('app_micro/Button'));
function App() {
const [error, setError] = React.useState(null);
if (error) {
return <div>Erro ao carregar módulo remoto: {error.message}</div>;
}
return (
<div>
<h1>App Shell</h1>
<Suspense fallback={<div>Carregando...</div>}>
<RemoteButton onError={setError} />
</Suspense>
</div>
);
}
export default App;
O tratamento de fallbacks é essencial para quando o remote está offline. Podemos usar um Error Boundary ou verificar a disponibilidade do remote antes de tentar carregar.
// Exemplo de fallback com Error Boundary
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>Módulo indisponível</div>;
}
return this.props.children;
}
}
6. Versionamento, deploy e estratégias de evolução
O versionamento de módulos compartilhados pode ser gerenciado de duas formas principais:
- Versionamento semântico: usar tags ou branches no repositório para controlar versões estáveis.
- Hash-based: cada build gera um hash único no
remoteEntry.js, garantindo cache busting automático.
// Exemplo de configuração com hash no filename
new ModuleFederationPlugin({
name: 'app_micro',
filename: 'remoteEntry.[contenthash].js', // Hash único por build
exposes: {
'./Button': './src/components/Button'
}
})
Para deploy independente, cada remote pode ser publicado em um servidor separado (CDN, S3, etc.) e o host apenas referencia a URL atualizada. Estratégias de rollout incluem:
- Blue-green deployment: manter duas versões do remote ativas simultaneamente.
- Canary releases: expor gradualmente a nova versão para uma porcentagem dos usuários.
- Rollback automático: manter a URL anterior disponível por um período.
7. Casos de uso reais e armadilhas comuns
Quando Module Federation é a melhor escolha:
- Grandes equipes desenvolvendo features independentes.
- Aplicações com componentes compartilhados que mudam frequentemente.
- Migração gradual de monólitos para micro frontends.
Problemas comuns e soluções:
- Conflitos de versão de shared: sempre configurar
singleton: truepara bibliotecas críticas como React. - Problemas de CORS: configurar cabeçalhos CORS no servidor do remote.
- Performance: evitar expor muitos módulos pequenos; agrupar componentes relacionados.
- Memory leaks: garantir que componentes remotos sejam desmontados corretamente.
// Exemplo de erro comum: esquecer de configurar shared
// Isso pode causar duas instâncias do React
// Solução: sempre configurar shared no host e remote
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
8. Futuro do Module Federation e alternativas
O Module Federation 2.0, atualmente em desenvolvimento, promete melhorias como:
- Suporte nativo a TypeScript.
- Melhor integração com server-side rendering (SSR).
- Compartilhamento de tipos entre host e remote.
Alternativas emergentes incluem:
- Vite Module Federation: plugin para Vite que oferece configuração mais simples.
- Piral: framework específico para micro frontends com suporte a Module Federation.
- Single SPA: orquestrador de micro frontends que pode usar Module Federation como mecanismo de carregamento.
Tendências futuras apontam para integração com edge computing, onde módulos podem ser servidos de CDNs com latência mínima, e SSR híbrido, onde partes da aplicação são renderizadas no servidor enquanto outras são carregadas dinamicamente no cliente.
Referências
- Webpack 5 Module Federation Documentation — Documentação oficial do Webpack 5 sobre Module Federation, com exemplos completos de configuração.
- Module Federation Examples by Zack Jackson — Repositório oficial com dezenas de exemplos práticos de Module Federation em diferentes cenários.
- Micro Frontends with Module Federation - A Complete Guide — Guia completo da Angular Architects sobre implementação de micro frontends usando Module Federation.
- Module Federation: The Missing Guide — Artigo técnico detalhado que cobre configurações avançadas e armadilhas comuns.
- Module Federation with React: Step-by-Step Tutorial — Tutorial do Smashing Magazine sobre implementação de micro frontends com React e Module Federation.
- Vite Module Federation Plugin — Plugin oficial para usar Module Federation com Vite, alternativa ao Webpack.
- Module Federation 2.0 RFC — Discussão oficial sobre as próximas features do Module Federation 2.0 no repositório do Webpack.