JavaScript Avançado

Micro-frontends com React: Module Federation e Arquitetura Distribuída na Prática

8 min de leitura

Micro-frontends com React: Module Federation e Arquitetura Distribuída na Prática

O que são Micro-frontends e Module Federation 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. 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. Configurando Module Federation no Webpack Host Application (Shell) 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

<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(&#039;webpack/lib/container/ModuleFederationPlugin&#039;);

const path = require(&#039;path&#039;);

module.exports = {

mode: &#039;production&#039;,

entry: &#039;./src/index&#039;,

output: {

path: path.resolve(__dirname, &#039;dist&#039;),

filename: &#039;[name].[contenthash].js&#039;,

},

plugins: [

new ModuleFederationPlugin({

name: &#039;host&#039;,

remotes: {

dashboard: &#039;dashboard@http://localhost:3001/remoteEntry.js&#039;,

profile: &#039;profile@http://localhost:3002/remoteEntry.js&#039;,

},

shared: [&#039;react&#039;, &#039;react-dom&#039;],

}),

],

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(&#039;webpack/lib/container/ModuleFederationPlugin&#039;);

const path = require(&#039;path&#039;);

module.exports = {

mode: &#039;production&#039;,

entry: &#039;./src/index&#039;,

output: {

path: path.resolve(__dirname, &#039;dist&#039;),

filename: &#039;[name].[contenthash].js&#039;,

},

plugins: [

new ModuleFederationPlugin({

name: &#039;dashboard&#039;,

filename: &#039;remoteEntry.js&#039;,

exposes: {

&#039;./DashboardApp&#039;: &#039;./src/App&#039;,

&#039;./hooks&#039;: &#039;./src/hooks&#039;,

},

shared: [&#039;react&#039;, &#039;react-dom&#039;],

}),

],

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 &#039;react&#039;;

import { BrowserRouter, Routes, Route } from &#039;react-router-dom&#039;;

// Importação dinâmica dos remotes

const DashboardApp = lazy(() =&gt; import(&#039;dashboard/DashboardApp&#039;));

const ProfileApp = lazy(() =&gt; =&gt; import(&#039;profile/ProfileApp&#039;));

const LoadingFallback = () =&gt; &lt;div&gt;Carregando módulo...&lt;/div&gt;;

export default function App() {

return (

&lt;BrowserRouter&gt;

&lt;nav&gt;

&lt;h1&gt;Shell Application&lt;/h1&gt;

&lt;a href=&quot;/&quot;&gt;Home&lt;/a&gt; | &lt;a href=&quot;/dashboard&quot;&gt;Dashboard&lt;/a&gt;

&lt;/nav&gt;

&lt;Suspense fallback={&lt;LoadingFallback /&gt;}&gt;

&lt;Routes&gt;

&lt;Route path=&quot;/dashboard&quot; element={&lt;DashboardApp /&gt;} /&gt;

&lt;Route path=&quot;/profile&quot; element={&lt;ProfileApp /&gt;} /&gt;

&lt;/Routes&gt;

&lt;/Suspense&gt;

&lt;/BrowserRouter&gt;

);

}</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 &#039;react&#039;;

export const UserContext = createContext();

export function UserProvider({ children }) {

const [user, setUser] = useState(null);

const login = (userData) =&gt; setUser(userData);

const logout = () =&gt; setUser(null);

return (

&lt;UserContext.Provider value={{ user, login, logout }}&gt;

{children}

&lt;/UserContext.Provider&gt;

);

}

// src/App.jsx (Host) - wrapping com Provider

export default function App() {

return (

&lt;UserProvider&gt;

&lt;BrowserRouter&gt;

{/ Routes aqui /}

&lt;/BrowserRouter&gt;

&lt;/UserProvider&gt;

);

}

// Em um remote (dashboard/src/App.jsx)

import { useContext } from &#039;react&#039;;

import { UserContext } from &#039;host/UserContext&#039;; // Import remoto

export default function DashboardApp() {

const { user } = useContext(UserContext);

return &lt;div&gt;Bem-vindo, {user?.name}&lt;/div&gt;;

}</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: &#039;host&#039;,

remotes: {

dashboard: &#039;dashboard@http://localhost:3001/remoteEntry.js&#039;,

},

shared: {

react: {

singleton: true, // Apenas uma instância do React

requiredVersion: &#039;^18.0.0&#039;,

strictVersion: false, // Permite versões compatíveis

},

&#039;react-dom&#039;: {

singleton: true,

requiredVersion: &#039;^18.0.0&#039;,

strictVersion: false,

},

axios: {

singleton: true,

requiredVersion: &#039;^1.0.0&#039;,

},

},

});</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 &#039;react&#039;;

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(&#039;Micro-app erro:&#039;, error, info);

}

render() {

if (this.state.hasError) {

return (

&lt;div style={{ padding: &#039;20px&#039;, color: &#039;red&#039; }}&gt;

&lt;h2&gt;Erro ao carregar módulo&lt;/h2&gt;

&lt;p&gt;{this.state.error?.message}&lt;/p&gt;

&lt;/div&gt;

);

}

return this.props.children;

}

}

// Uso no App.jsx

&lt;ErrorBoundary&gt;

&lt;Suspense fallback={&lt;LoadingFallback /&gt;}&gt;

&lt;Routes&gt;

&lt;Route path=&quot;/dashboard&quot; element={&lt;DashboardApp /&gt;} /&gt;

&lt;/Routes&gt;

&lt;/Suspense&gt;

&lt;/ErrorBoundary&gt;</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>

Comentários

Mais em JavaScript Avançado

Programação Funcional em JavaScript: Imutabilidade, Pureza e Composição: Do Básico ao Avançado
Programação Funcional em JavaScript: Imutabilidade, Pureza e Composição: Do Básico ao Avançado

Imutabilidade: O Fundamento da Programação Funcional A imutabilidade é o pila...

O que Todo Dev Deve Saber sobre Contract Testing com Pact: Garantindo Compatibilidade entre Serviços
O que Todo Dev Deve Saber sobre Contract Testing com Pact: Garantindo Compatibilidade entre Serviços

O Que é Contract Testing com Pact? Contract Testing é uma metodologia que val...

Guia Completo de Async Generators e Async Iterators em JavaScript na Prática
Guia Completo de Async Generators e Async Iterators em JavaScript na Prática

Entendendo Async Iterators Um async iterator é um objeto que implementa o pro...