Chuyển tới nội dung chính

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 (RemoteApp component)
  • Import shared dependencies dạng singleton (React, Router, Redux)

Container Host​

apps/container/ là host application — chịu trách nhiệm:

  1. Load MFE qua Module Federation
  2. Routing — routes.config.ts định nghĩa tất cả routes
  3. Auth Guard — kiểm tra authentication & authorization
  4. Layout — Sidebar, Header, Main content area
  5. Error Handling — MfeErrorBoundary bắ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.js trự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):

PackageStrategyLý do
reactsingletonContext, hooks phải cùng instance
react-domsingletonDOM rendering
react-router-domsingletonRouting context
react-reduxsingletonStore context
cảnh báo

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ại selectedStore trong 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.