From f602b44150be6dc490edd4db586f68b6fded8a0f Mon Sep 17 00:00:00 2001 From: COSALAU Date: Sat, 21 Mar 2026 12:04:59 +0530 Subject: [PATCH] Added beginner-friendly explanation for Module Federation --- src/content/concepts/module-federation.mdx | 417 +-------------------- 1 file changed, 8 insertions(+), 409 deletions(-) diff --git a/src/content/concepts/module-federation.mdx b/src/content/concepts/module-federation.mdx index a0aa0e7667ea..6da61d66378c 100644 --- a/src/content/concepts/module-federation.mdx +++ b/src/content/concepts/module-federation.mdx @@ -1,414 +1,13 @@ ---- -title: Module Federation -sort: 8 -contributors: - - sokra - - chenxsan - - EugeneHlushko - - jamesgeorge007 - - ScriptedAlchemy - - snitin315 - - XiaofengXie16 - - KyleBastien - - Alevale - - burhanuday - - RexSkz -related: - - title: "Webpack 5 Module Federation: A game-changer in JavaScript architecture" - url: https://medium.com/swlh/webpack-5-module-federation-a-game-changer-to-javascript-architecture-bcdd30e02669 - - title: "Explanations and Examples" - url: https://github.com/module-federation/module-federation-examples - - title: "Module Federation YouTube Playlist" - url: https://www.youtube.com/playlist?list=PLWSiF9YHHK-DqsFHGYbeAMwbd9xcZbEWJ ---- +## Beginner Friendly Explanation -## Motivation +Module Federation allows different applications to share code with each other at runtime. -Multiple separate builds should form a single application. These separate builds act like containers and can expose and consume code among themselves, creating a single, unified application. +For example, one application can use a component (like a button) from another application without copying the code. -This is often known as Micro-Frontends, but is not limited to that. +### Key Concepts - +- **exposes**: What your application shares +- **remotes**: External applications you consume from +- **shared**: Common libraries shared between applications -## Low-level concepts - -We distinguish between local and remote modules. Local modules are regular modules that are part of the current build. Remote modules are modules that are not part of the current build but are loaded at runtime from a remote container. - -Loading remote modules is considered an asynchronous operation. When using a remote module, these asynchronous operations will be placed in the next chunk loading operation(s) that are between the remote module and the entrypoint. It's not possible to use a remote module without a chunk loading operation. - -A chunk loading operation is usually an `import()` call, but older constructs like `require.ensure` or `require([...])` are supported as well. - -A container is created through a container entry, which exposes asynchronous access to the specific modules. The exposed access is separated into two steps: - -1. loading the module (asynchronous) -2. evaluating the module (synchronous). - -Step 1 will be done during the chunk loading. Step 2 will be done during the module evaluation interleaved with other (local and remote) modules. This way, evaluation order is unaffected by converting a module from local to remote or the other way around. - -It is possible to nest containers. Containers can use modules from other containers. Circular dependencies between containers are also possible. - -## High-level concepts - -Each build acts as a container and also consumes other builds as containers. This way, each build is able to access any other exposed module by loading it from its container. - -Shared modules are modules that are both overridable and provided as overrides to nested containers. They usually point to the same module in each build, e.g., the same library. - -The `packageName` option allows setting a package name to look for a `requiredVersion`. It is automatically inferred for the module requests by default, set `requiredVersion` to `false` when automatic infer should be disabled. - -## Building blocks - -### ContainerPlugin (low level) - -This plugin creates an additional container entry with the specified exposed modules. - -### ContainerReferencePlugin (low level) - -This plugin adds specific references to containers as externals and allows to import remote modules from these containers. It also calls the `override` API of these containers to provide overrides to them. Local overrides (via `__webpack_override__` or `override` API when build is also a container) and specified overrides are provided to all referenced containers. - -### ModuleFederationPlugin (high level) - -[`ModuleFederationPlugin`](/plugins/module-federation-plugin) combines `ContainerPlugin` and `ContainerReferencePlugin`. - -## Concept goals - -- It should be possible to expose and consume any module type that webpack supports. -- Chunk loading should load everything needed in parallel (web: single round-trip to server). -- Control from consumer to container - - Overriding modules is a one-directional operation. - - Sibling containers cannot override each other's modules. -- Concept should be environment-independent. - - Usable in web, Node.js, etc. -- Relative and absolute request in shared: - - Will always be provided, even if not used. - - Will resolve relative to `config.context`. - - Does not use a `requiredVersion` by default. -- Module requests in shared: - - Are only provided when they are used. - - Will match all used equal module requests in your build. - - Will provide all matching modules. - - Will extract `requiredVersion` from package.json at this position in the graph. - - Could provide and consume multiple different versions when you have nested node_modules. -- Module requests with trailing `/` in shared will match all module requests with this prefix. - -## Use cases - -### Separate builds per page - -Each page of a Single Page Application is exposed from container build in a separate build. The application shell is also a separate build referencing all pages as remote modules. This way each page can be separately deployed. The application shell is deployed when routes are updated or new routes are added. The application shell defines commonly used libraries as shared modules to avoid duplication of them in the page builds. - -### Components library as container - -Many applications share a common components library which could be built as a container with each component exposed. Each application consumes components from the components library container. Changes to the components library can be separately deployed without the need to re-deploy all applications. The application automatically uses the up-to-date version of the components library. - -## Dynamic Remote Containers - -The container interface supports `get` and `init` methods. -`init` is an `async` compatible method that is called with one argument: the shared scope object. This object is used as a shared scope in the remote container and is filled with the provided modules from a host. -It can be leveraged to connect remote containers to a host container dynamically at runtime. - -**init.js** - -```js -(async () => { - // Initializes the shared scope. Fills it with known provided modules from this build and all remotes - await __webpack_init_sharing__("default"); - const container = globalThis.someContainer; // or get the container somewhere else - // Initialize the container, it may provide shared modules - await container.init(__webpack_share_scopes__.default); - const module = await container.get("./module"); -})(); -``` - -The container tries to provide shared modules, but if the shared module has already been used, a warning and the provided shared module will be ignored. The container might still use it as a fallback. - -This way you could dynamically load an A/B test which provides a different version of a shared module. - -T> Ensure you have loaded the container before attempting to dynamically connect a remote container. - -Example: - -**init.js** - -```js -function loadComponent(scope, module) { - return async () => { - // Initializes the shared scope. Fills it with known provided modules from this build and all remotes - await __webpack_init_sharing__("default"); - const container = window[scope]; // or get the container somewhere else - // Initialize the container, it may provide shared modules - await container.init(__webpack_share_scopes__.default); - const factory = await window[scope].get(module); - const Module = factory(); - return Module; - }; -} - -loadComponent("abtests", "test123"); -``` - -[See full implementation](https://github.com/module-federation/module-federation-examples/tree/master/advanced-api/dynamic-remotes) - -## Promise Based Dynamic Remotes - -Generally, remotes are configured using URL's like in this example: - -```js -export default { - plugins: [ - new ModuleFederationPlugin({ - name: "host", - remotes: { - app1: "app1@http://localhost:3001/remoteEntry.js", - }, - }), - ], -}; -``` - -But you can also pass in a promise to this remote, which will be resolved at runtime. You should resolve this promise with any module that fits the `get/init` interface described above. For example, if you wanted to pass in which version of a federated module you should use, via a query parameter you could do something like the following: - -```js -export default { - plugins: [ - new ModuleFederationPlugin({ - name: "host", - remotes: { - app1: `promise new Promise(resolve => { - const urlParams = new URLSearchParams(window.location.search) - const version = urlParams.get('app1VersionParam') - // This part depends on how you plan on hosting and versioning your federated modules - const remoteUrlWithVersion = 'http://localhost:3001/' + version + '/remoteEntry.js' - const script = document.createElement('script') - script.src = remoteUrlWithVersion - script.onload = () => { - // the injected script has loaded and is available on window - // we can now resolve this Promise - const proxy = { - get: (request) => window.app1.get(request), - init: (...arg) => { - try { - return window.app1.init(...arg) - } catch(e) { - console.log('remote container already initialized') - } - } - } - resolve(proxy) - } - // inject this script with the src set to the versioned remoteEntry.js - document.head.appendChild(script); - }) - `, - }, - // ... - }), - ], -}; -``` - -Note that when using this API you _have_ to resolve an object which contains the get/init API. - -## Dynamic Public Path - -### Offer a host API to set the publicPath - -One could allow the host to set the publicPath of a remote module at runtime by exposing a method from that remote module. - -This approach is particularly helpful when you mount independently deployed child applications on the sub path of the host domain. - -Scenario: - -You have a host app hosted on `https://my-host.com/app/*` and a child app hosted on `https://foo-app.com`. The child app is also mounted on the host domain, hence, -`https://foo-app.com` is expected to be accessible via `https://my-host.com/app/foo-app` and `https://my-host.com/app/foo-app/*` requests are redirected to `https://foo-app.com/*` via a proxy. - -Example: - -**webpack.config.js (remote)** - -```js -export default { - entry: { - remote: "./public-path", - }, - plugins: [ - new ModuleFederationPlugin({ - name: "remote", // this name needs to match with the entry name - exposes: ["./public-path"], - // ... - }), - ], -}; -``` - -**public-path.js (remote)** - -```js -export function set(value) { - __webpack_public_path__ = value; -} -``` - -**src/index.js (host)** - -```ts -const publicPath = await import("remote/public-path"); -publicPath.set("/your-public-path"); - -//bootstrap app e.g. import('./bootstrap.js') -``` - -### Infer publicPath from script - -One could infer the publicPath from the script tag from `document.currentScript.src` and set it with the [`__webpack_public_path__`](/api/module-variables/#__webpack_public_path__-webpack-specific) module variable at runtime. - -Example: - -**webpack.config.js (remote)** - -```js -export default { - entry: { - remote: "./setup-public-path", - }, - plugins: [ - new ModuleFederationPlugin({ - name: "remote", // this name needs to match with the entry name - // ... - }), - ], -}; -``` - -**setup-public-path.js (remote)** - -```js -// derive the publicPath with your own logic and set it with the __webpack_public_path__ API -__webpack_public_path__ = `${document.currentScript.src}/../`; -``` - -T> There is also an `'auto'` value available to [`output.publicPath`](/configuration/output/#outputpublicpath) which automatically determines the publicPath for you. - -## Troubleshooting - -### `Uncaught Error: Shared module is not available for eager consumption` - -The application is eagerly executing an application that is operating as an omnidirectional host. There are options to choose from: - -You can set the dependency as eager inside the advanced API of Module Federation, which doesn’t put the modules in an async chunk, but provides them synchronously. This allows us to use these shared modules in the initial chunk. But be careful as all provided and fallback modules will always be downloaded. It’s recommended to provide it only at one point of your application, e.g. the shell. - -We strongly recommend using an asynchronous boundary. It will split out the initialization code of a larger chunk to avoid any additional round trips and improve performance in general. - -For example, your entry looked like this: - -**index.js** - -```jsx -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")); -root.render(); -``` - -Let's create `bootstrap.js` file and move contents of the entry into it, and import that bootstrap into the entry: - -**index.js** - -```diff -+ import('./bootstrap'); -- import { createRoot } from 'react-dom/client'; -- import App from './App'; - -- const root = createRoot(document.getElementById('root')); -- root.render(); -``` - -**bootstrap.js** - -```diff -+ import { createRoot } from 'react-dom/client'; -+ import App from './App'; -+ const root = createRoot(document.getElementById('root')); -+ root.render(); -``` - -This method works but can have limitations or drawbacks. - -Setting `eager: true` for dependency via the `ModuleFederationPlugin` - -**webpack.config.js** - -```js -// ... -new ModuleFederationPlugin({ - shared: { - ...deps, - react: { - eager: true, - }, - }, -}); -``` - -### `Uncaught Error: Module "./Button" does not exist in container.` - -It likely does not say `"./Button"`, but the error message will look similar. This issue is typically seen if you are upgrading from webpack beta.16 to webpack beta.17. - -Within ModuleFederationPlugin. Change the exposes from: - -```diff -new ModuleFederationPlugin({ - exposes: { -- 'Button': './src/Button' -+ './Button':'./src/Button' - } -}); -``` - -### `Uncaught TypeError: fn is not a function` - -You are likely missing the remote container, make sure it's added. -If you have the container loaded for the remote you are trying to consume, but still see this error, add the host container's remote container file to the HTML as well. - -### Setting `output.uniqueName` - -In a Module Federation setup, both the host and each remote must have a globally unique `output.uniqueName`. Webpack derives this value from the `name` field in `package.json` by default. This means two builds that share the same `package.json` `name` (a common pattern when splitting a remote out of an existing project) can silently collide at runtime. - -One solution is to use a separate `package.json` with a distinct name for each configuration. - -Alternatively, you can set `output.uniqueName` explicitly in each webpack config: - -**webpack.config.js (host)** - -```js -export default { - output: { - uniqueName: "my-host-app", - }, - plugins: [ - new ModuleFederationPlugin({ - // ... - }), - ], -}; -``` - -**webpack.config.js (remote)** - -```js -export default { - output: { - uniqueName: "my-remote-app", // must differ from host and all other remotes - }, - plugins: [ - new ModuleFederationPlugin({ - // ... - }), - ], -}; -``` - -The value can be any string, as long as it is unique across every federated build loaded on a given page. +This helps in building scalable and modular applications. \ No newline at end of file