Mục lục

Module Federation

Sâu vào cơ chế Module Federation: Biến đổi Runtime, Share Scope và giải quyết bài toán Dependency Bloat.

Cách Module Federation thay đổi cuộc chơi: Không còn phải đóng gói thư viện vào từng bản build, tất cả được chia sẻ tại Runtime.

1. Phân tích thiết kế: Tải mã nguồn động (Dynamic Loading)

Cách làm truyền thống: Externals/Global Scripts

Dùng Webpack externals để loại bỏ React khỏi bundle và thêm script React vào thẻ <head> của HTML bằng link CDN.

Nhược điểm:

  • Versioning Hell: Nếu Host cần React 18 nhưng một MFE cũ cần React 16, bạn không thể tải cả hai bản vào biến toàn cục window.React mà không gây xung đột.
  • Tải thừa (Static Bloat): Người dùng phải tải toàn bộ thư viện ngay cả khi module sử dụng thư viện đó chưa được hiển thị.

Cách làm mới: Module Federation (Share Scope)

Sử dụng một đối tượng toàn cục __webpack_share_scopes__ để các app tự thỏa thuận phiên bản thư viện với nhau.

Ưu điểm & Vấn đề giải quyết:

  • Singleton Pattern: Đảm bảo chỉ có DUY NHẤT một bản React được chạy, giải quyết triệt để lỗi "Invalid Hook Call".
  • Semver Aware: Webpack tự động so sánh phiên bản (Vd: Host có ^18.0, Remote có 18.2 -> chọn 18.2).
  • Deduplication: Trình duyệt chỉ tải 1 bản react.js duy nhất cho hàng chục MFE.

2. Các thuật ngữ "Sống còn"

  • Expose: MFE chọn module/component nào để "mở cửa" cho bên ngoài vào lấy.
  • Consume/Remote: App Shell định nghĩa danh sách các "nguồn hàng" Remote.
  • Shared: Danh mục các thư viện mà các team đồng ý dùng chung để giảm bundle size.

3. Đánh đổi (Trade-offs)

  • Asset Waterfall: Nếu các MFE phụ thuộc lẫn nhau một cách chéo ngoe (A cần B, B cần C), trình duyệt sẽ phải tải lần lượt từng file manifest, gây chậm trang web.
  • Cấu hình nhạy cảm: Một sai sót trong config shared (như quên đặt singleton: true) có thể làm sập toàn bộ hệ thống ngay trên Production.

4. Code minh họa: Cấu hình chuẩn Senior

javascript:
// catalog-mfe/webpack.config.js (Remote)
new ModuleFederationPlugin({
  name: "catalog",
  filename: "remoteEntry.js",
  exposes: {
    "./ProductCard": "./src/components/ProductCard",
  },
  shared: {
    react: { singleton: true, requiredVersion: deps.react },
    "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
  },
});

// app-shell/webpack.config.js (Host)
new ModuleFederationPlugin({
  name: "shell",
  remotes: {
    catalog: "catalog@http://localhost:3001/remoteEntry.js",
  },
  shared: {
    react: { singleton: true, requiredVersion: deps.react },
    "react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
  },
});

Bài tập: Làm POC

Thực hiện cấu hình thực tế:

  • Host: Nhận và render component ProductCard từ Remote.
  • Remote: Expose ProductCard có sử dụng thư viện animation framer-motion.
  • Yêu cầu: Cả hai cùng đưa framer-motion vào mục shared và kiểm tra Network tab để đảm bảo thư viện này chỉ được tải 1 lần duy nhất.

Câu hỏi phỏng vấn

Q: Bản chất của remoteEntry.js là gì? Tại sao kích thước của nó thường rất nhỏ?

  • Trả lời: remoteEntry.js là một danh mục (manifest) chứa bản đồ các module được expose và danh sách các thư viện shared. Nó không chứa source code của các component. Kích thước nhỏ (vài KB) vì nó chỉ đóng vai trò "chỉ đường" cho Host App biết cần tải chunk code nào từ folder static/ khi cần thiết.

Q: Điều gì xảy ra nếu Host và Remote dùng hai version React khác nhau (Vd: Host 17, Remote 18)?

  • Trả lời: Mặc định, Webpack MF sẽ cố gắng load cả hai nếu không có config đặc biệt. Tuy nhiên, nếu dùng singleton: true, Webpack sẽ so sánh Semver và chọn version cao nhất tương thích (Vd: React 18). Nếu Host App không chịu chạy trên 18, nó sẽ ném ra Warning. Đây là cơ chế Shared Scope giúp tránh tình trạng người dùng phải tải nhiều bản UI library trùng lặp.

Q: Tại sao phải dùng React.lazySuspense khi làm việc với Module Federation?

  • Trả lời: Vì các Remote components được tải qua network (bất đồng bộ). Nếu không dùng lazy, Host App sẽ cố gắng tìm code của Remote ngay lúc khởi tạo (sync), dẫn đến lỗi crash vì lúc đó file remote JS chưa được tải xong. Suspense giúp chúng ta cung cấp một giao diện Loading (Skeleton) trong lúc chờ code từ CDN bay về.
Quảng cáo
mdhorizontal