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} |
|---|---|
| host | the shell app that consumes remote modules {app shell tiêu thụ module remote} |
| remote | an app that exposes modules for others {app expose module cho người khác} |
| exposes | what a remote makes available {remote cung cấp gì} |
| remotes | what a host wants to consume {host muốn tiêu thụ gì} |
| shared | dependencies 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.tsfrom remotes (tools like@module-federation/typescript) {An toàn kiểu — import remote không có kiểu local; sinh.d.tstừ 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
exposesmodules viaremoteEntry.js; a host lists them inremotes{remoteexposesmodule quaremoteEntry.js; host liệt kê trongremotes}. shared+singletonprevents duplicate copies of React and friends {shared+singletonngă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}.