Micro frontends em 2023, Parte II: Module Federation e NextJS
Esta é a segunda parte de uma série, leia também a parte I em: Micro frontends em 2023, parte I: Quando usar?
Opções de implementação
São muitas as implementações possíveis: pacotes npm compartilhados entre projetos; ESModules via tag script, iframe, importmaps, web components, url rewriting, NextJS Multi Zones, etc. Mas neste primeiro guia vou documentar uma delas em específico: webpack module federation.
Passo-a-passo com dois projetos NextJS
Vamos montar o seguinte exemplo, um site base principal (o hospedeiro, o host, o app shell, a container application) representada abaixo na cor laranja (app1), com um mini-app remoto (o módulo, o remote) representada em violeta (app2), embutido dentro, este módulo vem de outro site. Ambos os sites são projetos NextJS.
1. Criar os dois projetos NextJS
npx create-next-app app1
npx create-next-app app2
2. Instalar o plugin @module-federation/nextjs-mf e o bundler webpack 5 nos dois projetos
Existe um pacote que facilita um pouco a configuração de module federation em projetos Next, o @module-federation/nextjs-mf. Este plugin já foi proprietário e pago um dia, mas atualmente é livre e gratuito. Isto me confundiu muito pois varios resultados do Google e exemplos online ainda usam a referência dele no registry de pacotes fechados PrivJS.
cd app1
yarn add @module-federation/nextjs-mf webpack
cd ../app2
yarn add @module-federation/nextjs-mf webpack
cd ..
3. Criar um módulo no projeto 2
cd app2
mkdir src/components
vim -p src/components/ModuleA.tsx src/pages/index.tsx
app2/src/components/ModuleA.tsx
export default function ModuleA() {
return <h2 style={{ color: "darkviolet" }}>Módulo A from app2</h2>;
}
app2/src/pages/index.tsx
import ModuleA from '@/components/ModuleA'
export default function Home() {
return (
<ModuleA/>
)
}
Para testar suba o servidor de desenvolvimento na porta 3001:
yarn dev -p 3001
e acesse http://localhost:3001/
4. Configurar a build do app2 para expor um remoteEntry contendo o módulo
vim next.config.js
app2/next.config.js
const { NextFederationPlugin } = require("@module-federation/nextjs-mf");
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack(config, options) {
const { isServer } = options;
config.plugins.push(
new NextFederationPlugin({
name: "app2",
remotes: {},
filename: "static/chunks/remoteEntry.js",
exposes: {
"./ModuleA": "./src/components/ModuleA",
},
shared: {},
})
);
return config;
},
};
module.exports = nextConfig;
Para testar se o arquivo remoteEntry.js
está sendo exposto corretamente suba o servidor de desenvolvimento:
yarn dev -p 3001
e acesse http://localhost:3001/_next/static/chunks/remoteEntry.js
Se aparecer um arquivo grande e enigmático, é porque deu certo, pode fazer o bundle de produção e manter este servidor no ar:
yarn build
yarn start -p 3001
5. Configurar a build do app1 para usar o módulo remoto do app2
Em outro terminal:
cd ../app1
vim -p next.config.js src/pages/index.tsx
app1/next.config.js
const { NextFederationPlugin } = require('@module-federation/nextjs-mf');
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack(config, options) {
const { isServer } = options;
config.plugins.push(
new NextFederationPlugin({
name: 'app1',
remotes: {
app2: `app2@http://localhost:3001/_next/static/${isServer ? 'ssr' : 'chunks'}/remoteEntry.js`,
},
})
);
return config;
},
}
module.exports = nextConfig
src/pages/index.tsx
import dynamic from "next/dynamic";
import styles from "@/styles/Home.module.css";
const ModuleA = dynamic(() => import("app2/ModuleA"), {
ssr: false,
});
export default function Home() {
return (
<>
<main className={styles.main}>
<header>App1 Header</header>
<nav>
<h2>App1 Menu</h2>
</nav>
<ModuleA />
<footer>App1 Footer</footer>
</main>
</>
);
}
suba o servidor do app1 para testar se funcionou:
yarn dev -p 3002
e acesse http://localhost:3002/
Repositório
O código desta demo se encontra em https://github.com/fczuardi/host-remote-nextjs-mf
Para correções dúvidas e discussões, tanto do código de exemplo quanto desta série de blog posts, use a página de issues lá.
Próximos passos
Este passo a passo foi só uma tentativa de demonstrar um conceito utilizando código, este exemplo foi um bem básico onde a Application Shell consome módulos de outro servidor, mas extender ele para que haja um app3, ou para que o app1 exponha alguns componentes para a app2 são outras possibilidades também. A app principal poderia por exemplo ser a responsável pelos widgets do Design System, ou um MFE só para isto poderia ser outra opção, um módulo para as strings de localização pode dar a chance de updates simples de texto não requererem uma nova build do site todo, agrupar os hooks de consumo de serviço em outro módulo, etc... As fronteiras e os contratos nos pontos de interface entre os módulos vão variar de caso a caso.
Ter uma separação de responsabilidades com espaço para donas diferentes cuidarem de diferentes produtos no mesmo site é um caminho arquitetural que eu sinto que alguns projetos maiores já estão trilhando, então é bom conhecer o conceito e ter no repertório :)
PS: Mas e os BFFs? Bom, este é um outro assunto para um outro momento, eu acho que na mesma filosofia de dar autonomia para times menores que queiram experimentar com diferentes stacks, trabalhar com unidades separadas pode ser um caminho também, algo como a figura abaixo.
Referências
Alguns outros textos / abas abertas sobre o tema para quem quiser ler mais:
- Tanenbaum–Torvalds debate (Jan 1992)
- clássico debate "Linux is Obsolete" entre Tanenbaum e Torvalds, a respeito de microkernel vs monolithc system
- Microservices: a definition of this new architectural term (Mar 2014)
- bem completo no contexto de microservices, as partes "Organized around Business Capabilities" e "Decentralized Governance" são boas
- Lukasz Gornicki on Twitter: "@mpjme you still at Spotify? can you confirm if they are still using iframes and postMessage for the micro frontend architecture?" / Twitter (Sep 2017)
- MPJ sobre Spotify abandonando microfrontends em favor de um big monorepo para todos
- Micro Frontends
- texto de onde tirei a definição usada na parte I, lista como benefício a capacidade de incrementalmente atualizar um software legado parte por parte
- Micro Frontends in the Wild (Jan 2020)
- Webpack 5 Module Federation: A game-changer in JavaScript architecture (Mar 2020)
- post do maintainer do plugin para next usado no passo a passo (e criador do module federation do Webpack), esta mesma pessoa trabalha numa ferramenta comercial voltada a module federation e twitta bastante sobre estes temas
- Frontend Architectural Patterns: Micro Frontends (Sep 2020)
- tem um meme legal
- Micro Frontends – Revolutionizing Front-end Development with Microservices (Nov 2020)
- fala da divisão por business domain e tem um diagrama que vale por mil palavras
- You Might Not Need Module Federation: Orchestrate your Microfrontends at Runtime with Import Maps (Jan 2023)
- fala sobre usar Import Maps ao invés de Webpack Module Federation
Foto do cabeçalho Steve Harvey on Unsplash