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.