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

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

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 5:

// 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,
  },
};

Remote Application (Micro-app)

Cada remote também precisa de sua configuração. O importante é expor os módulos que serão consumidos:

// 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,
  },
};

Consumindo e Integrando Remotes no Host

Lazy Loading com React.lazy

A forma mais limpa é usar React.lazy e Suspense para carregar os remotes dinamicamente:

// 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>
  );
}

Compartilhamento de State com Context

Para comunicação entre micro-apps sem acoplamento forte, use Context:

// 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>;
}

Padrões Arquiteturais Avançados

Versionamento e Compatibilidade de Dependências

Module Federation permite definir requisitos de versão para dependências compartilhadas:

// 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',
    },
  },
});

Error Boundaries para Isolamento

Proteja o host contra falhas de remotes:

// 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>

Conclusão

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: (1) configuração adequada do Webpack com remotes e shared corretamente definidos, evitando duplicação de dependências; (2) importação dinâmica com Suspense para isolamento e performance, permitindo que cada remote carregue sob demanda; (3) patterns de comunicação desacoplados como Context API, evitando dependências circulares entre micro-apps. Domine esses conceitos e você terá autonomia para escalar arquiteturas com múltiplas equipes.

Referências


Artigos relacionados