Uma Arquitetura para Aplicações Web Multi-sistema com Module Federation

Em aplicações web de grande escala, é comum a existência de diversas subaplicações. Frequentemente, essas subaplicações precisam interagir ou compartilhar funcionalidades e componentes entre si. O Module Federation, introduzido no Webpack 5, oferece uma solução robusta para esse cenário de micro-frontends, permitindo o carregamento dinâmico e o compartilhamento de código entre aplicações independentes.

Exemplo Prático

Para ilustrar a implementação, vamos configurar um monorepo com três aplicações React: uma aplicação principal (main) e duas subaplicações (app1 e app2). Utilizaremos o lerna para gerenciar o monorepo.

Estrutura do Projeto

Inicialize o projeto com o Lerna e crie a estrutura básica:

lerna init

Crie os diretórios para as três aplicações dentro de packages/: main, app1 e app2. Cada aplicação terá sua estrutura fonte e configuração do Webpack.

Código das Subaplicações (app1 e app2)

Cada subaplicação possui um ponto de entrada simples e um componente principal.

src/entry.js

import ReactDOM from 'react-dom';
import React from 'react';
import AppComponent from './AppComponent';

ReactDOM.render(<AppComponent />, document.getElementById('root'));

src/AppComponent.js (para app1)

import React from 'react';

const AppOne = () => {
  return (
    <div>
      <h2>Conteúdo da Aplicação 1</h2>
    </div>
  );
};

export default AppOne;

Código da Aplicação Principal (main)

A aplicação principal atua como orquestradora, carregando as subaplicações de forma assíncrona.

src/AppShell.js

import React, { useState, Suspense } from 'react';
const RemoteAppOne = React.lazy(() => import('app1/Component'));
const RemoteAppTwo = React.lazy(() => import('app2/Component'));

const ApplicationShell = () => {
  const [currentView, setCurrentView] = useState('app1');

  const toggleView = () => {
    setCurrentView(prev => prev === 'app1' ? 'app2' : 'app1');
  };

  return (
    <div>
      <button onClick={toggleView}>Alternar Módulo</button>
      <Suspense fallback={<div>Carregando...</div>}>
        {currentView === 'app1' ? <RemoteAppOne /> : <RemoteAppTwo />}
      </Suspense>
    </div>
  );
};

export default ApplicationShell;

Configuração do Module Federation

A mágica acontece na configuração do Webpack. Cada aplicação define quais módulos expõe e quais consome de forma remota.

webpack.config.js (exemplo para app1)

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;

const buildRemoteUrl = (port) => {
  const host = process.env.HOST || 'localhost';
  return `//${host}:${port}/remoteModule.js`;
};

module.exports = {
  entry: './src/entry',
  mode: 'development',
  devServer: {
    port: 3001,
    static: path.join(__dirname, 'public'),
  },
  output: {
    publicPath: 'auto',
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-react'],
          },
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteModule.js',
      exposes: {
        './Component': './src/AppComponent',
      },
      remotes: {
        main: `main@${buildRemoteUrl(3000)}`,
        app2: `app2@${buildRemoteUrl(3002)}`,
      },
      shared: {
        react: { singleton: true, requiredVersion: '^17.0.2' },
        'react-dom': { singleton: true, requiredVersion: '^17.0.2' },
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

Arquivo de Dependências (package.json)

Cada pacote no monorepo gerencia suas próprias dependências, mas as bibliotecas compartilhadas (React) são definidas como singletões na configuração do Module Federation para evitar duplicatas e conflitos de versão.

{
  "name": "@demo/app1",
  "version": "1.0.0",
  "scripts": {
    "start": "webpack serve --open",
    "build": "webpack --mode production"
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.15.0",
    "@babel/preset-react": "^7.14.5",
    "babel-loader": "^8.2.2",
    "html-webpack-plugin": "^5.3.2",
    "webpack": "^5.50.0",
    "webpack-cli": "^4.7.2",
    "webpack-dev-server": "^3.11.2"
  }
}

Executando a Solução

Para iniciar, execute lerna bootstrap para instalar as dependências. Em seguida, inicie cada aplicação individualmente com seu respectivo script start. A aplicação principal na porta 3000 caregará e exibirá os componentes de app1 (porta 3001) e app2 (porta 3002) dinamicamente.

O Module Federation permite criar um ecossistema de aplicações descentralizadas onde cada uma mantém sua independência e ciclo de vida próprios, enquanto podem compartilhar recursos de forma eficiente. Este modelo é particularmente vantajoso para equipes autônomas que trabalham em diferentes partes de um grande produto digital, facilitando tanto a colaboração quanto a implantação independente.

Tags: Module Federation Webpack 5 micro-frontends React Lerna

Publicado em 7-18 08:51