Que es Micro Frontend
Micro Frontend es un patron de arquitectura que aplica los conceptos de microservicios al frontend. Divide aplicaciones grandes en aplicaciones pequenas que pueden desplegarse de forma independiente.
flowchart TB
subgraph Monolithic["Frontend Monolitico"]
subgraph Single["Single Application"]
M1["Product<br/>Module"]
M2["Cart<br/>Module"]
M3["Account<br/>Module"]
end
Note1["Un solo despliegue, un solo equipo"]
end
subgraph MicroFE["Micro Frontend"]
subgraph Shell["Container App (Shell)"]
Header["Shared Header"]
subgraph Apps["Aplicaciones Independientes"]
A1["Product App<br/>(Equipo A)"]
A2["Cart App<br/>(Equipo B)"]
A3["Account App<br/>(Equipo C)"]
end
end
Note2["Despliegue independiente, equipos independientes, libertad de eleccion tecnologica"]
end
Casos para Considerar su Adopcion
| Caso | Razon |
|---|
| Equipos grandes (10+ personas) | Asegurar independencia entre equipos |
| Migracion gradual de sistemas legacy | Posibilidad de coexistencia de tecnologias nuevas y antiguas |
| Diferentes ciclos de lanzamiento | Despliegue independiente por funcionalidad |
| Organizacion orientada al dominio | Division de equipos por dominio de negocio |
| Diversidad de stack tecnologico | Coexistencia de React, Vue, Angular |
Patrones de Integracion
1. Module Federation (Webpack 5)
flowchart TB
subgraph Host["Host App (Shell)"]
Config["webpack.config.js<br/>remotes: {<br/> products, cart, account<br/>}"]
end
Host --> Products
Host --> Cart
Host --> Account
subgraph Products["Products Remote"]
P1["exposes:<br/>- List<br/>- Detail"]
end
subgraph Cart["Cart Remote"]
C1["exposes:<br/>- Cart<br/>- Summary"]
end
subgraph Account["Account Remote"]
A1["exposes:<br/>- Profile<br/>- Settings"]
end
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
products: 'products@http://localhost:3001/remoteEntry.js',
cart: 'cart@http://localhost:3002/remoteEntry.js',
account: 'account@http://localhost:3003/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'products',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/components/ProductList',
'./ProductDetail': './src/components/ProductDetail',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
import React, { Suspense, lazy } from 'react';
const ProductList = lazy(() => import('products/ProductList'));
const Cart = lazy(() => import('cart/Cart'));
const AccountSettings = lazy(() => import('account/Settings'));
function App() {
return (
<div>
<Header />
<Routes>
<Route
path="/products/*"
element={
<Suspense fallback={<Skeleton />}>
<ProductList />
</Suspense>
}
/>
<Route
path="/cart"
element={
<Suspense fallback={<Skeleton />}>
<Cart />
</Suspense>
}
/>
<Route
path="/account/*"
element={
<Suspense fallback={<Skeleton />}>
<AccountSettings />
</Suspense>
}
/>
</Routes>
</div>
);
}
2. Single-SPA
import { registerApplication, start } from 'single-spa';
registerApplication({
name: '@myorg/products',
app: () => System.import('@myorg/products'),
activeWhen: ['/products'],
});
registerApplication({
name: '@myorg/cart',
app: () => System.import('@myorg/cart'),
activeWhen: ['/cart'],
});
registerApplication({
name: '@myorg/account',
app: () => System.import('@myorg/account'),
activeWhen: ['/account'],
});
registerApplication({
name: '@myorg/navbar',
app: () => System.import('@myorg/navbar'),
activeWhen: () => true,
});
start();
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import App from './App';
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary(err, info, props) {
return <div>Error in products app</div>;
},
});
export const { bootstrap, mount, unmount } = lifecycles;
3. Native Federation (Compatible con Vite/Rspack)
import { defineConfig } from 'vite';
import federation from '@originjs/vite-plugin-federation';
export default defineConfig({
plugins: [
federation({
name: 'host-app',
remotes: {
products: 'http://localhost:3001/assets/remoteEntry.js',
cart: 'http://localhost:3002/assets/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
});
Gestion de Estado y Comunicacion
flowchart TB
subgraph Events["1. Custom Events (Recomendado)"]
EA["App A"] -->|CustomEvent| EB["App B"]
end
subgraph SharedState["2. Estado Compartido (Para casos complejos)"]
SA["App A"] --> Store["Store<br/>(Global)"]
SC["App C"] --> Store
Store --> SB["App B"]
Store --> SD["App D"]
end
subgraph URL["3. URL/Query Params"]
URLNote["Compartir informacion de navegacion"]
end
Custom Events
export const CartEvents = {
ITEM_ADDED: 'cart:item-added',
ITEM_REMOVED: 'cart:item-removed',
CART_UPDATED: 'cart:updated',
} as const;
export interface CartItemEvent {
productId: string;
quantity: number;
price: number;
}
export function dispatchCartEvent(
type: string,
detail: CartItemEvent
): void {
window.dispatchEvent(new CustomEvent(type, { detail }));
}
export function subscribeToCartEvent(
type: string,
handler: (event: CustomEvent<CartItemEvent>) => void
): () => void {
window.addEventListener(type, handler as EventListener);
return () => window.removeEventListener(type, handler as EventListener);
}
import { CartEvents, dispatchCartEvent } from '@myorg/events';
function ProductCard({ product }) {
const handleAddToCart = () => {
dispatchCartEvent(CartEvents.ITEM_ADDED, {
productId: product.id,
quantity: 1,
price: product.price,
});
};
return (
<div>
<h3>{product.name}</h3>
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
);
}
import { useEffect, useState } from 'react';
import { CartEvents, subscribeToCartEvent } from '@myorg/events';
function Cart() {
const [items, setItems] = useState([]);
useEffect(() => {
const unsubscribe = subscribeToCartEvent(CartEvents.ITEM_ADDED, (event) => {
setItems((prev) => [...prev, event.detail]);
});
return unsubscribe;
}, []);
return (
<div>
<h2>Cart ({items.length} items)</h2>
{}
</div>
);
}
Compartir Estado Global
import { create } from 'zustand';
interface User {
id: string;
name: string;
email: string;
}
interface AuthState {
user: User | null;
isAuthenticated: boolean;
login: (user: User) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
login: (user) => set({ user, isAuthenticated: true }),
logout: () => set({ user: null, isAuthenticated: false }),
}));
import { useAuthStore } from '@myorg/shared-store';
function Header() {
const { user, isAuthenticated, logout } = useAuthStore();
return (
<header>
{isAuthenticated ? (
<>
<span>Welcome, {user?.name}</span>
<button onClick={logout}>Logout</button>
</>
) : (
<a href="/login">Login</a>
)}
</header>
);
}
Estrategias de Enrutamiento
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { lazy, Suspense } from 'react';
const ProductsRoutes = lazy(() => import('products/Routes'));
const CartRoutes = lazy(() => import('cart/Routes'));
const AccountRoutes = lazy(() => import('account/Routes'));
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{ index: true, element: <Home /> },
{
path: 'products/*',
element: (
<Suspense fallback={<Loading />}>
<ProductsRoutes />
</Suspense>
),
},
{
path: 'cart/*',
element: (
<Suspense fallback={<Loading />}>
<CartRoutes />
</Suspense>
),
},
{
path: 'account/*',
element: (
<Suspense fallback={<Loading />}>
<AccountRoutes />
</Suspense>
),
},
],
},
]);
export function AppRouter() {
return <RouterProvider router={router} />;
}
Estrategias de Despliegue
flowchart TB
subgraph CDN["Configuracion CDN"]
Shell["shell/<br/>└── v1.2.3/"]
Products["products/<br/>└── v2.1.0/"]
Cart["cart/<br/>└── v1.8.5/"]
Account["account/<br/>└── v3.0.1/"]
end
subgraph Manifest["Gestion de Manifiesto"]
JSON["products: cdn/products/v2.1.0/<br/>cart: cdn/cart/v1.8.5/<br/>account: cdn/account/v3.0.1/"]
end
CDN --> Manifest
Note["Compatible con despliegue Blue-Green / Canary"]
Componentes UI Comunes
export { Button } from './Button';
export { Input } from './Input';
export { Card } from './Card';
export { Modal } from './Modal';
export { theme } from './theme';
{
shared: {
'@myorg/ui-kit': {
singleton: true,
requiredVersion: '^1.0.0',
},
},
}
Estrategias de Testing
| Nivel | Objetivo | Herramientas |
|---|
| Unitario | Componentes individuales | Vitest, Jest |
| Integracion | Micro FE individual | Testing Library |
| E2E | Integracion completa | Playwright, Cypress |
| Contrato | Contratos de API | Pact |
Enlaces de Referencia
← Volver a la lista