<h2>O que são Micro-frontends e Module Federation</h2>
<p>Micro-frontends é uma arquitetura que estende os princípios de microsserviços para o frontend, permitindo que múltiplas equipes desenvolvam, testem e façam deploy de aplicações independentes que funcionam integradas. Module Federation é um plugin do Webpack 5 que resolve o grande desafio dessa arquitetura: compartilhar código e dependências entre aplicações remotas sem duplicação ou conflitos de versão.</p>
<p>Diferente de abordagens antigas (iframes, web components genéricos), Module Federation permite que uma aplicação host importe módulos de aplicações remotas em tempo de execução. Isso significa que você pode ter um shell (host) que orquestra múltiplas aplicações (remotes) desenvolvidas com diferentes tecnologias ou versões do React, cada uma com seu próprio ciclo de vida de build e deploy.</p>
<h2>Configurando Module Federation no Webpack</h2>
<h3>Host Application (Shell)</h3>
<p>O host é a aplicação principal que agrega os remotes. Você configura o Module Federation no arquivo webpack.config.js ou através de ferramentas como Vite. Aqui está uma configuração prática com Webpack 5:</p>
<pre><code class="language-javascript">// webpack.config.js do host
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
},
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
dashboard: 'dashboard@http://localhost:3001/remoteEntry.js',
profile: 'profile@http://localhost:3002/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
devServer: {
port: 3000,
historyApiFallback: true,
},
};</code></pre>
<h3>Remote Application (Micro-app)</h3>
<p>Cada remote também precisa de sua configuração. O importante é expor os módulos que serão consumidos:</p>
<pre><code class="language-javascript">// webpack.config.js de um remote (dashboard)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
},
plugins: [
new ModuleFederationPlugin({
name: 'dashboard',
filename: 'remoteEntry.js',
exposes: {
'./DashboardApp': './src/App',
'./hooks': './src/hooks',
},
shared: ['react', 'react-dom'],
}),
],
devServer: {
port: 3001,
historyApiFallback: true,
},
};</code></pre>
<h2>Consumindo e Integrando Remotes no Host</h2>
<h3>Lazy Loading com React.lazy</h3>
<p>A forma mais limpa é usar React.lazy e Suspense para carregar os remotes dinamicamente:</p>
<pre><code class="language-javascript">// src/App.jsx (Host)
import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// Importação dinâmica dos remotes
const DashboardApp = lazy(() => import('dashboard/DashboardApp'));
const ProfileApp = lazy(() => => import('profile/ProfileApp'));
const LoadingFallback = () => <div>Carregando módulo...</div>;
export default function App() {
return (
<BrowserRouter>
<nav>
<h1>Shell Application</h1>
<a href="/">Home</a> | <a href="/dashboard">Dashboard</a>
</nav>
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route path="/dashboard" element={<DashboardApp />} />
<Route path="/profile" element={<ProfileApp />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}</code></pre>
<h3>Compartilhamento de State com Context</h3>
<p>Para comunicação entre micro-apps sem acoplamento forte, use Context:</p>
<pre><code class="language-javascript">// src/context/UserContext.jsx (Host)
import React, { createContext, useState } from 'react';
export const UserContext = createContext();
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
const login = (userData) => setUser(userData);
const logout = () => setUser(null);
return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
}
// src/App.jsx (Host) - wrapping com Provider
export default function App() {
return (
<UserProvider>
<BrowserRouter>
{/ Routes aqui /}
</BrowserRouter>
</UserProvider>
);
}
// Em um remote (dashboard/src/App.jsx)
import { useContext } from 'react';
import { UserContext } from 'host/UserContext'; // Import remoto
export default function DashboardApp() {
const { user } = useContext(UserContext);
return <div>Bem-vindo, {user?.name}</div>;
}</code></pre>
<h2>Padrões Arquiteturais Avançados</h2>
<h3>Versionamento e Compatibilidade de Dependências</h3>
<p>Module Federation permite definir requisitos de versão para dependências compartilhadas:</p>
<pre><code class="language-javascript">// webpack.config.js com shared refinado
new ModuleFederationPlugin({
name: 'host',
remotes: {
dashboard: 'dashboard@http://localhost:3001/remoteEntry.js',
},
shared: {
react: {
singleton: true, // Apenas uma instância do React
requiredVersion: '^18.0.0',
strictVersion: false, // Permite versões compatíveis
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
strictVersion: false,
},
axios: {
singleton: true,
requiredVersion: '^1.0.0',
},
},
});</code></pre>
<h3>Error Boundaries para Isolamento</h3>
<p>Proteja o host contra falhas de remotes:</p>
<pre><code class="language-javascript">// src/ErrorBoundary.jsx
import React from 'react';
export class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, info) {
console.error('Micro-app erro:', error, info);
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '20px', color: 'red' }}>
<h2>Erro ao carregar módulo</h2>
<p>{this.state.error?.message}</p>
</div>
);
}
return this.props.children;
}
}
// Uso no App.jsx
<ErrorBoundary>
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route path="/dashboard" element={<DashboardApp />} />
</Routes>
</Suspense>
</ErrorBoundary></code></pre>
<h2>Conclusão</h2>
<p>Module Federation com React transforma a forma como construímos aplicações em larga escala. Os três pontos-chave que você deve dominar são: <strong>(1) configuração adequada do Webpack com remotes e shared corretamente definidos</strong>, evitando duplicação de dependências; <strong>(2) importação dinâmica com Suspense para isolamento e performance</strong>, permitindo que cada remote carregue sob demanda; <strong>(3) patterns de comunicação desacoplados</strong> como Context API, evitando dependências circulares entre micro-apps. Domine esses conceitos e você terá autonomia para escalar arquiteturas com múltiplas equipes.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://webpack.js.org/concepts/module-federation/" target="_blank" rel="noopener noreferrer">Webpack Module Federation Oficial</a></li>
<li><a href="https://react.dev/reference/react/lazy" target="_blank" rel="noopener noreferrer">React 18 Lazy Loading Docs</a></li>
<li><a href="https://martinfowler.com/articles/micro-frontends.html" target="_blank" rel="noopener noreferrer">Micro Frontends by Cam Jackson</a></li>
<li><a href="https://github.com/module-federation/module-federation-examples" target="_blank" rel="noopener noreferrer">Module Federation Examples - Zack Jackson</a></li>
<li><a href="https://blog.logrocket.com/building-micro-frontends-with-module-federation-and-react/" target="_blank" rel="noopener noreferrer">Building Micro-frontends with Module Federation - LogRocket</a></li>
</ul>