Micro-Frontend & Module Federation
Tổng quan
Hệ thống sử dụng Vite Module Federation (@module-federation/vite) để tách ứng dụng thành nhiều MFE độc lập. Mỗi MFE:
- Có build riêng (Vite config riêng, Dockerfile riêng)
- Có dev server riêng (port riêng)
- Expose 1 remote entry (
RemoteAppcomponent) - Import shared dependencies dạng singleton (React, Router, Redux)
Container Host
apps/container/ là host application — chịu trách nhiệm:
- Load MFE qua Module Federation
- Routing —
routes.config.tsđịnh nghĩa tất cả routes - Auth Guard — kiểm tra authentication & authorization
- Layout — Sidebar, Header, Main content area
- Error Handling —
MfeErrorBoundarybắt lỗi MFE crash
MFE Registry
Tất cả MFE được đăng ký trong mfeRegistry.ts — nguồn dữ liệu duy nhất:
export const MFE_REGISTRY = {
dashboard: { name: "dashboard", devPort: 8081, basePath: "/dashboard" },
auth: { name: "auth", devPort: 8082, basePath: "/auth" },
content: { name: "content", devPort: 8083, basePath: "/content" },
inventory: { name: "inventory", devPort: 8084, basePath: "/inventory" },
analytics: { name: "analytics", devPort: 8085, basePath: "/analytics" },
rolePermission: { name: "role-permission", devPort: 8086, basePath: "/role-permission" },
saleManage: { name: "sale-manage", devPort: 8087, basePath: "/sale-manage" },
setting: { name: "setting", devPort: 8088, basePath: "/setting" },
workforce: { name: "workforce", devPort: 8089, basePath: "/workforce" },
electronicInvoice: { name: "e-invoice", devPort: 8091, basePath: "/e-invoice" },
payment: { name: "payment", devPort: 8092, basePath: "/payment" },
crm: { name: "crm", devPort: 8093, basePath: "/crm" },
mobile: { name: "mobile", devPort: 8094, basePath: "/mobile" },
partner: { name: "partner", devPort: 8095, basePath: "/partner" },
accounting: { name: "accounting", devPort: 8096, basePath: "/accounting" },
eSignature: { name: "e-signature", devPort: 8097, basePath: "/e-signature" },
};
Vite Config (Container)
// apps/container/vite.config.ts
federation({
name: "container",
remotes: generateRemotes(process.env), // Từ MFE_REGISTRY
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
"react-router-dom": { singleton: true },
"react-redux": { singleton: true },
},
});
Remote MFE
Mỗi MFE expose 1 RemoteApp component:
// apps/<module>/src/RemoteApp.tsx
export default function RemoteApp() {
// Sync token & URL từ container
useTokenSync();
useUrlSync();
return (
<Provider store={store}>
<Routes>
{/* Internal routes */}
</Routes>
</Provider>
);
}
Vite Config (Remote)
// apps/<module>/vite.config.ts
federation({
name: "<module-name>",
exposes: {
"./RemoteApp": "./src/RemoteApp.tsx",
},
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
"react-router-dom": { singleton: true },
"react-redux": { singleton: true },
},
});
Luồng load MFE
Development vs Production
Development
- Container proxy forwards requests tới MFE dev servers:
/inventory/*→localhost:8084/auth/*→localhost:8082
- MFE dev servers serve
remoteEntry.jstrực tiếp
Production
- Mỗi MFE build thành static files → Docker image → K8s pod
- Nginx reverse proxy forward theo path
REMOTE_*env vars cho phép override entry URLs
Shared Dependencies
Các dependencies sau được share giữa Container và MFEs (chỉ load 1 lần):
| Package | Strategy | Lý do |
|---|---|---|
react | singleton | Context, hooks phải cùng instance |
react-dom | singleton | DOM rendering |
react-router-dom | singleton | Routing context |
react-redux | singleton | Store context |
Không thêm shared dependency mà không cân nhắc kỹ. Mỗi shared dep tăng coupling giữa các MFEs.
Giao tiếp giữa MFE và Container (MFE Communication)
Do kiến trúc tách biệt hoàn toàn giữa Host (Container) và Remotes (MFE), chúng ta sử dụng cơ chế Custom Events (window.dispatchEvent) truyền tin qua window object làm kênh giao tiếp chính.
Để đảm bảo tính đồng bộ và kiểm soát kiểu (Typesafe), các MFE không được tự ý gọi window.dispatchEvent thủ công. Hãy sử dụng các hàm helper dùng chung trong thư viện @vppos/core/utils:
1. Đồng bộ Store
emitMfeStoreListChanged(): Gọi khi danh sách cửa hàng thay đổi (Thêm mới, Chỉnh sửa, Vô hiệu hoá, Kích hoạt lại). Container Header sẽ tự động fetch lại danh sách cửa hàng mới nhất từ API.emitMfeStoreChanged(store): Gọi khi thông tin cửa hàng hiện tại đang chọn bị thay đổi. Container sẽ cập nhật lạiselectedStoretrong Redux State và LocalStorage để toàn bộ ứng dụng nhận biết.
2. Điều hướng & Xác thực
emitMfeNavigate(path, replace?): Gọi khi MFE muốn yêu cầu Container điều hướng (chuyển trang) trên Router chính.emitMfeLogout(): Gọi khi muốn thực hiện đăng xuất tài khoản và xóa sạch token session từ Container.