jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Webpack · Part 11 — Module Federation

Share code between independently deployed apps at runtime: hosts, remotes, exposes, remotes, and the shared singleton trick that stops React being downloaded twice. With a live federation visualizer.

For ten parts, “the bundle” meant one app built together {Suốt mười phần, “bundle” nghĩa là một app build chung}. Module Federation breaks that assumption: separate apps, built and deployed independently, can import each other’s code at runtime {Module Federation phá vỡ giả định đó: các app riêng biệt, build và triển khai độc lập, có thể import code của nhau lúc runtime}. This is the engine behind modern micro-frontends {Đây là động cơ phía sau micro-frontend hiện đại}.

Click the exposed modules below to load remotes into a host, and toggle shared to see why React isn’t downloaded twice {Bấm các module được expose bên dưới để tải remote vào host, và bật/tắt shared để xem vì sao React không tải hai lần}:


1. The problem it solves {Vấn đề nó giải quyết}

A big app split across teams: cart team, profile team, search team {Một app lớn chia cho nhiều team: cart, profile, search}. Without federation, every change to any part forces a rebuild and redeploy of the whole app {Không có federation, mọi thay đổi ở bất kỳ phần nào buộc build lại và deploy lại cả app}. With federation, each team ships their slice independently and the shell stitches them together in the browser {Với federation, mỗi team ship phần của mình độc lập và shell ghép chúng lại trong trình duyệt}.

Term {Thuật ngữ}Meaning {Nghĩa}
hostthe shell app that consumes remote modules {app shell tiêu thụ module remote}
remotean app that exposes modules for others {app expose module cho người khác}
exposeswhat a remote makes available {remote cung cấp gì}
remoteswhat a host wants to consume {host muốn tiêu thụ gì}
shareddependencies loaded once and reused {phụ thuộc tải một lần và dùng lại}

2. The remote {Bên remote}

A remote declares a name, an entry filename, and what it exposes {Remote khai báo tên, tên file entry, và những gì nó expose}:

const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "cart",
      filename: "remoteEntry.js", // the manifest other apps fetch
      exposes: {
        "./Cart": "./src/Cart",
        "./MiniCart": "./src/MiniCart",
      },
      shared: { react: { singleton: true } },
    }),
  ],
};

remoteEntry.js is a tiny manifest — a map of what’s exposed and how to fetch it {remoteEntry.js là một manifest nhỏ — bản đồ những gì được expose và cách lấy chúng}. The actual Cart chunk loads only when someone imports it {Chunk Cart thực tế chỉ tải khi có người import nó}.


3. The host {Bên host}

The host lists which remotes it wants and where to find their remoteEntry.js {Host liệt kê remote nào nó muốn và tìm remoteEntry.js của chúng ở đâu}:

new ModuleFederationPlugin({
  name: "shell",
  remotes: {
    cart: "cart@https://cart.example.com/remoteEntry.js",
    profile: "profile@https://profile.example.com/remoteEntry.js",
  },
  shared: { react: { singleton: true }, "react-dom": { singleton: true } },
});

Then you import a remote module as if it were local — but it resolves over the network at runtime {Rồi bạn import module remote như thể nó là local — nhưng nó phân giải qua mạng lúc runtime}:

const Cart = React.lazy(() => import("cart/Cart"));

The URL can even point at a remote that didn’t exist when the host was built {URL thậm chí có thể trỏ tới một remote chưa tồn tại khi host được build}. Deploy the remote, refresh, done {Deploy remote, refresh, xong}.


4. shared — the killer feature {shared — tính năng sát thủ}

Without shared, the host and each remote would each bundle their own React — three copies, broken hooks, wasted bytes {Không có shared, host và mỗi remote sẽ tự bundle React riêng — ba bản, hook hỏng, lãng phí byte}. shared makes React a singleton: loaded once, reused everywhere {shared biến React thành singleton: tải một lần, dùng lại khắp nơi}.

shared: {
  react: { singleton: true, requiredVersion: "^18.0.0" },
  "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
}
  • singleton: true — only one copy may exist (mandatory for React, which breaks with two copies) {chỉ một bản tồn tại (bắt buộc cho React, vốn hỏng với hai bản)}.
  • requiredVersion — Webpack negotiates a compatible version across apps at runtime {Webpack thương lượng một phiên bản tương thích giữa các app lúc runtime}.
  • eager: true — load it in the initial chunk instead of async (use sparingly) {tải trong chunk đầu thay vì async (dùng tiết kiệm)}.

Toggle shared in the visualizer: with it on, “react copies loaded” stays at 1 no matter how many remotes you load {Bật/tắt shared trong visualizer: khi bật, “react copies loaded” luôn là 1 dù bạn tải bao nhiêu remote}.


5. Real-world cautions {Cảnh báo thực tế}

  • Version skew — if remotes need incompatible major versions of a shared lib, the singleton negotiation can warn or break {Lệch phiên bản — nếu remote cần phiên bản major không tương thích, thương lượng singleton có thể cảnh báo hoặc hỏng}.
  • Runtime failures — a remote being down is a runtime error, not a build error; wrap in error boundaries and fallbacks {Lỗi runtime — remote sập là lỗi runtime, không phải lỗi build; bọc trong error boundary và fallback}.
  • Type safety — remote imports have no local types; generate .d.ts from remotes (tools like @module-federation/typescript) {An toàn kiểu — import remote không có kiểu local; sinh .d.ts từ remote}.
  • Don’t over-split — federation adds operational complexity; use it for genuine team boundaries, not to split a small app {Đừng tách quá nhỏ — federation thêm phức tạp vận hành; dùng cho ranh giới team thực sự}.

6. Exercises {Bài tập}

1. A host imports cart/Cart, but cart wasn’t even deployed when the host was built. Why does this still work? {Host import cart/Cart, nhưng cart còn chưa được deploy khi host build. Vì sao vẫn chạy?}

Solution {Lời giải}

Federation resolves remotes at runtime via remoteEntry.js fetched over the network, not at build time {Federation phân giải remote lúc runtime qua remoteEntry.js lấy qua mạng, không phải lúc build}.

2. Three federated apps each use React. How do you ensure only one copy loads and hooks don’t break? {Ba app federated đều dùng React. Làm sao đảm bảo chỉ một bản tải và hook không hỏng?}

Solution {Lời giải}

shared: { react: { singleton: true } } in every app’s config {shared: { react: { singleton: true } } trong config của mọi app}.

3. Your Cart remote server is down. What’s the user-facing impact and how do you contain it? {Server remote Cart sập. Tác động tới người dùng và cách kiểm soát?}

Solution {Lời giải}

It’s a runtime load failure — wrap the lazy-loaded remote in an error boundary with a fallback UI so the rest of the shell keeps working {Đó là lỗi tải runtime — bọc remote lazy trong error boundary với UI fallback để phần còn lại của shell vẫn chạy}.

Stretch {Nâng cao}: in the visualizer, disable shared, load all four remote modules, and note how “react copies loaded” and the transferred KB climb compared to the shared case {trong visualizer, tắt shared, tải cả bốn module remote, và để ý “react copies loaded” và KB chuyển tăng so với khi shared}.


Key takeaways {Điểm chính}

  • Module Federation lets independently deployed apps import each other at runtime {Module Federation cho các app deploy độc lập import lẫn nhau lúc runtime}.
  • A remote exposes modules via remoteEntry.js; a host lists them in remotes {remote exposes module qua remoteEntry.js; host liệt kê trong remotes}.
  • shared + singleton prevents duplicate copies of React and friends {shared + singleton ngăn bản trùng của React và bạn bè}.
  • Remote failures are runtime errors — use error boundaries and fallbacks {Lỗi remote là lỗi runtime — dùng error boundary và fallback}.
  • Reach for it at real team boundaries, not to split small apps {Dùng ở ranh giới team thực sự, không để tách app nhỏ}.

Next up {Tiếp theo}

Part 12 — Production capstone + migration: assemble everything into one battle-tested production config, then a practical guide to when (and how) to migrate to Vite or Rspack {Phần 12 — Capstone production + di trú: ghép tất cả thành một config production thực chiến, rồi hướng dẫn thực tế khi nào (và cách) di trú sang Vite hoặc Rspack}.