--- url: /ai/index.md --- # AI Quick Start :::tip Prefer to read the docs yourself? If you want to go through Module Federation concepts, configuration, and integration in order, head to the [Getting Started guide](/guide/start/index.md). ::: ## A Better Way To Start If you are already using an AI coding tool, the better starting point is not to read all of the docs yourself first. The faster approach is: **give the latest Module Federation docs to your agent first, then let it answer questions, debug problems, update config, and write code.** Many models still have incomplete or outdated knowledge of Module Federation 2.0. So the point of this page is not to make you study first. It is to help your agent get the right source material first. ## Recommended Setup If you use Claude Code, Cursor, Windsurf, or another editor with Skills support, install: ```bash npx skills add module-federation/agent-skills --skill mf -y ``` Then ask the agent to read the docs before answering: ```text /mf docs What's the difference between singleton and requiredVersion in shared? ``` The idea is simple: - you do not need to hunt through the docs first - your agent reads the latest docs before it answers - the result is much more likely to be accurate ## If You Want Your Agent To Do More The `mf` skill does more than answer questions. It can also help your agent: - add Module Federation to an existing project - inspect config problems - analyze type issues - check shared dependency conflicts - inspect remote module information - use the Divebell MF Extension to diagnose runtime failures, loading chains, and performance bottlenecks - read application observability reports from local, SSR, or production environments ๐Ÿ‘‰ [View Agent Skills documentation](/ai/skill.md) ## Observe and Debug MF Loading Remote resolution, Shared selection, Bridge lifecycle, and module performance problems often exist only in the live page runtime. For a one-off diagnosis, the `mf` skill can use the [Divebell](/guide/debug/divebell.md) MF Extension to obtain bounded, structured evidence without adding the Observability Plugin to the application. Page interaction is used only to trigger the loading path. Ask the agent directly: ```text /mf observability Open https://example.com with Divebell, reproduce the failing checkout flow, and tell me which Remote or Shared phase failed. ``` When needed, the skill installs Divebell globally on the agent machine, reads the CLI Skill returned by `divebell skill`, installs `@divebell/extension-mf`, and reads the command Skill returned by `divebell mf --skill`. Neither package is added to your application. For continuous collection, Node/SSR files, production uploads, or reports that must be retained, integrate the [Observability Plugin](/plugin/plugins/observability-plugin.md). After a loading failure, give the generated `traceId`, `read:` command, or uploaded report to the agent: ```text /mf observability I saw this Module Federation console error: [Module Federation] Observability report generated traceId: mf-... read: window.__FEDERATION__.__OBSERVABILITY__["runtime_host"].getReport("mf-...") Please read the report and fix the issue. ``` For Node or SSR, ask the agent to read `.mf/observability/latest.json`. For production, upload reports with the plugin's `onReport` callback and give the stored report to your agent. A trace ID from a closed browser session is not a substitute for the stored report. ## You Can Still Use This Without Installing Anything If you just want a lightweight version first, send this directly to your agent: ```text Before answering, please read https://module-federation.io/llms.txt to learn about the latest Module Federation 2.0 documentation, then answer my question. ``` This is the easiest way to stop the model from guessing when you have not installed the skill yet. ## Docs You Can Hand To Your Agent ### llms.txt [module-federation.io](https://module-federation.io) supports the [llms.txt](https://llmstxt.org) standard, which is a good entry point for AI systems: ```text https://module-federation.io/llms.txt ``` ### Markdown Docs Every page on the docs site is also available in Markdown. Replace `.html` at the end of a docs URL with `.md`: ```text # HTML version https://module-federation.io/guide/basic/runtime.html # Markdown version https://module-federation.io/guide/basic/runtime.md ``` ## Next If you want to start using this workflow right away: 1. Install `mf` 2. Ask the question you care about most 3. Use [Divebell](/guide/debug/divebell.md) for one-off MF runtime, loading-chain, or performance diagnosis 4. Enable the [Observability Plugin](/plugin/plugins/observability-plugin.md) when loading evidence must be retained or uploaded 5. Open [Skills documentation](/ai/skill.md) to see which sub-commands it supports --- url: /ai/skill.md --- # Skills Module Federation now exposes one unified **`mf` skill**. You do not need to pick from a long list of separate skills, and you do not need to read the docs first and then translate them back to your agent. The simpler approach is to install this one skill and let the agent decide whether it should read docs, inspect config, or troubleshoot a problem. When a problem exists only in the live page runtime, the skill uses the Divebell MF Extension for structured Remote, Shared, Bridge, and module performance evidence. The browser is only the entry point and loading trigger. Divebell is installed on the agent machine, not in the application. If you have not read it yet, start with [AI Quick Start](/ai/index.md). ## Installation ```bash npx skills add module-federation/agent-skills --skill mf -y ``` After that, the main entry is: ```text /mf ``` If you cannot use the CLI, you can also copy the directory manually from GitHub: ```text https://github.com/module-federation/agent-skills/tree/main/skills/mf ``` ## How to use it `mf` has one entry point, but it contains multiple capabilities. You can either provide an explicit sub-command or just ask in plain language. With explicit sub-commands: ```text /mf docs How do I use Module Federation in Modern.js? /mf integrate /mf type-check /mf runtime-error RUNTIME-008 /mf observability ``` With natural language: ```text /mf What's the difference between singleton and requiredVersion in shared? /mf Help me figure out why this project is not pulling remote types /mf Add Module Federation to the current project /mf I saw a Module Federation console error with traceId mf-... ``` ## What `mf` supports | Sub-command | Use it for | | --------------- | ------------------------------------------------------------------------------------------------------------------------- | | `docs` | Read the latest docs first, then answer configuration, API, and concept questions | | `context` | Inspect the current project's MF configuration | | `module-info` | Inspect remote module URLs, exposed modules, and related metadata | | `integrate` | Add Module Federation to an existing project | | `type-check` | Troubleshoot type-related issues | | `shared-deps` | Troubleshoot shared dependency conflicts and duplicate bundling | | `perf` | Troubleshoot slow local development and slow HMR | | `config-check` | Troubleshoot config mistakes, missing files, and plugin mismatches | | `bridge-check` | Troubleshoot Bridge integration issues | | `runtime-error` | Troubleshoot explicit runtime error codes, especially `RUNTIME-001` and `RUNTIME-008` | | `observability` | Diagnose MF runtime failures, loading chains, and performance bottlenecks through Divebell, or read observability reports | ## The most common workflows ### 1. Read the docs before answering This is the most common use case, and the best place to start: ```text /mf docs What's the difference between singleton and requiredVersion in shared? ``` That makes the agent read the latest docs first instead of guessing from outdated memory. ### 2. Let the agent integrate it for you If you want to add Module Federation to an existing project: ```text /mf integrate ``` The agent will inspect your current project and decide what to do next. ### 3. Let the agent troubleshoot directly If you already have a concrete problem: ```text /mf type-check /mf shared-deps /mf config-check /mf runtime-error RUNTIME-008 /mf observability ``` At that point, the goal is not for you to manually investigate first. The goal is to let the agent start diagnosing immediately. ### 4. Let the agent debug a live MF page For a one-off investigation, give the target URL and user path to the skill: ```text /mf observability Open https://example.com with Divebell, reproduce the failing checkout flow, and inspect the Remote and Shared loading chain. ``` The skill discovers the installed Divebell CLI and MF Extension Skills, opens the page with `divebell open --mf`, and selects the smallest relevant `divebell mf` command. Page interaction is used only to trigger the relevant lazy load; conclusions should come from structured Remote, Shared, Bridge, or module performance evidence. See [Troubleshoot Runtime Issues](/guide/debug/divebell.md) for the installation commands, command map, Remote proxy workflow, and evidence boundaries. ### 5. Let the agent read retained observability Install the [Observability Plugin](/plugin/plugins/observability-plugin.md) in your app when reports must be collected continuously, uploaded from production, or written by Node/SSR. When a browser console error prints a `traceId` and `read:` command, pass the accessible report source to the skill: ```text /mf observability I saw this Module Federation console error: [Module Federation] Observability report generated traceId: mf-... read: window.__FEDERATION__.__OBSERVABILITY__["runtime_host"].getReport("mf-...") Please read the report and fix the issue. ``` For Node, SSR, or production uploads: ```text /mf observability Read .mf/observability/latest.json and explain the likely owner and fix. ``` ## The one thing you really need to remember You do not need to read the docs first and then tell the agent what to do. Install `mf`, then let the agent read, inspect, decide, and act. --- url: /guide/start/index.md --- # Introduction ### ๐Ÿ’ก What is Module Federation? Module Federation is an architectural pattern for the decentralization of JavaScript applications (similar to microservices on the server-side). It allows you to share code and resources among multiple JavaScript applications (or micro-frontends). This can help you: - Reduce code duplication - Improve code maintainability - Lower the overall size of your applications - Enhance the performance of your applications ### โœจ What is Module Federation 2.0? `Module Federation 2.0` differs from the `Module Federation` built into `Webpack5` by providing not only the core features of module export, loading, and dependency sharing but also additional dynamic type hinting, `Manifest`, `Federation Runtime`, and `Runtime Plugin System`. These features make `Module Federation` more suitable for use as a micro-frontend architecture in large-scale `Web` applications. ### ๐Ÿ”ฅ Features Module Federation has the following features: - โšก Code sharing, Dependency reuse - ๐Ÿ“ Manifest - ๐ŸŽจ [Module Federation Runtime](/guide/runtime/index.md) - ๐Ÿงฉ [Runtime Plugins System](/plugin/dev/index.md) - ๐Ÿš€ [Dynamic type prompt](/guide/basic/type-prompt.md) - ๐Ÿ› ๏ธ [Chrome Devtool](/guide/debug/chrome-devtool.md) - ๐Ÿฆ€ [Rspack](/integrations/bundler/rspack.md) and [Webpack](/integrations/bundler/webpack.md) Support ### ๐ŸŽฏ Use Cases Module Federation is suitable for the following scenarios: - **Large Applications**: For large applications, you can break the application into multiple micro-frontends and use Module Federation to share code and resources between them. - **Microfrontend Architecture**: Module Federation is an ideal tool for building microfrontend architectures. - **Multi-team Development**: Module Federation can assist multiple teams in collaboratively developing large applications. ### ๐Ÿ•  History of Module Federation Module Federation is a new feature introduced in Webpack 5, but its history dates back to 2017. At that time, the Webpack team began exploring a way to share code between multiple applications. - In 2018, Webpack 4.20 was released, introducing module hooks, which laid the foundation for the development of Module Federation. - In 2019, Webpack 5 was released, officially introducing the Module Federation feature. Module Federation has become a powerful tool for building modern web applications. ### ๐Ÿ•ฐ๏ธ The Future of Module Federation Module Federation aims to become an architectural method for building large web applications, similar to microservices in the backend. Module Federation will provide more capabilities to meet the foundational needs of large web application decentralization, currently including these parts: - Providing comprehensive Devtool tools - Offering more high-level framework capabilities like Router, Sandbox, SSR - Providing best practices for large web applications based on Module Federation ## Follow Us - [GitHub - Star us on GitHub](https://github.com/module-federation/core) - [Discord](https://discord.com/channels/1055442562959290389/1055442563718467637) - [Lark Group (Chinese Community)](https://applink.larkoffice.com/client/chat/chatter/add_by_link?link_token=a41s8f79-741f-41ba-8349-395d9a0e9662) ## โœจ Next Steps You might want to: [Get Started Quickly Learn how to use Module Federation ](/guide/start/quick-start)[Integrations Choose the package for your project ](/integrations)[Consult Configuration Understand how to configure Module Federation ](/configure/index) --- url: /guide/start/quick-start.md --- # Quick Start Guide This guide will lead you step by step to learn how to use Module Federation. We will build two independent Single Page Applications (SPAs) that will share components using Module Federation. If you encounter unfamiliar terms in the following text, please refer to the [Glossary](/guide/start/glossary.md). ## Requirements When using Module Federation build plugins, make sure your Node.js version is 20 or higher. ```bash node -v ``` If you are adding Module Federation to an existing project, start with [Integrations](/integrations/index.md) and choose the package for your framework or bundler. ## Creating a New Project Module Federation provides the `create-module-federation` tool to create projects. You don't need to install it globally; you can directly use `npx` to run it on-demand. You can use `create-module-federation` to create a Module Federation project by running the following command: ```sh [npm] npm create module-federation@latest ``` ```sh [yarn] yarn create module-federation ``` ```sh [pnpm] pnpm create module-federation@latest ``` Just follow the prompts step by step. During the creation process, you can select the project type, role type, etc. ### Templates When creating a project, you can choose from the following templates provided by `create-module-federation`: | Template | Description | | ------------------------ | ------------------------------------------------------------------------------------- | | provider-modern | A provider using [Modern.js](https://modernjs.dev/) | | provider-rsbuild | A provider using [Rsbuild](https://rsbuild.dev/) | | provider-rslib | A provider using [Rslib](https://lib.rsbuild.dev/) | | provider-rslib-storybook | A provider using [Rslib](https://lib.rsbuild.dev/) with the storybook feature enabled | | consumer-modern | A consumer using [Modern.js](https://modernjs.dev/) | | consumer-rsbuild | A consumer using [Rsbuild](https://rsbuild.dev/) | ### Quick Creation [create-module-federation](https://www.npmjs.com/package/create-module-federation) provides some CLI options. By setting these CLI options, you can skip the interactive selection steps and create a project with one click. For example, to create a Modern.js provider project named `provider` in the `my-project` directory with one click: ```bash npx create-module-federation --dir my-project --template provider-modern --name provider # Use abbreviations npx create-module-federation -d my-project -t provider-modern -n provider ``` The complete CLI options for `create-module-federation` are as follows: ```text Usage: create-module-federation [options] Options: -h, --help display help for command -d, --dir create project in specified directory -t, --template specify the template to use -n, --name specify the mf name -r, --role specify the mf role type: provider or consumer --override override files in target directory Templates: provider-modern, provider-rsbuild, provider-rslib, provider-rslib-storybook, consumer-modern, consumer-rsbuild ``` ### Create a provider Execute the `create-module-federation` command, select the required framework and type according to your needs, and select the role type as provider to create a project. Here we take creating an rsbuild provider project as an example: ```bash โžœ ~ โœ— npm create module-federation@latest > npx > create-module-federation โ—† Create Module Federation Project โ”‚ โ—‡ Please input Module Federation name: โ”‚ mf_provider โ”‚ โ—‡ Please select the type of project you want to create: โ”‚ Application โ”‚ โ—‡ Select template โ”‚ Rsbuild โ”‚ โ—‡ Please select the role of project you want to create: โ”‚ Provider โ”‚ โ—‡ Next steps โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ cd mf_provider โ”‚ โ”‚ npm install โ”‚ โ”‚ npm run dev โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ”” Done. ``` ### Create a consumer Execute the `create-module-federation` command, select the required framework and type according to your needs, and select the role type as consumer to create a project. Here is an example of creating an rsbuild consumer project: ```bash โžœ ~ โœ— npm create module-federation@latest > npx > create-module-federation โ—† Create Module Federation Project โ”‚ โ—‡ Please input Module Federation name: โ”‚ mf_consumer โ”‚ โ—‡ Please select the type of project you want to create: โ”‚ Application โ”‚ โ—‡ Select template โ”‚ Rsbuild โ”‚ โ—‡ Please select the role of project you want to create: โ”‚ Consumer โ”‚ โ—‡ Next steps โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ cd mf_consumer โ”‚ โ”‚ npm install โ”‚ โ”‚ npm run dev โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ”” Done. ``` #### Replace provider The default consumer project created will consume a published provider. If you want to replace the provider, you need to modify the `remotes` configuration in `module-federation.config.ts`. ```diff title="module-federation.config.ts" import { createModuleFederationConfig } from '@module-federation/rsbuild-plugin'; export default createModuleFederationConfig({ name: 'mf_consumer', remotes: { - 'provider': 'rslib_provider@https://unpkg.com/module-federation-rslib-provider@latest/dist/mf/mf-manifest.json', + 'provider': 'rslib_provider@http://localhost:3000/mf-manifest.json', }, shared: { react: { singleton: true }, 'react-dom': { singleton: true }, }, }); ``` ## Existing project integration If you want to integrate Module Federation into an existing project, start with [Integrations](/integrations/index.md). It explains which package to install for Rsbuild, Rspack, Webpack, Modern.js, Next.js, Vite, and other project types. ## Summary Through the above process, you have completed the export of a component from a 'provider' for use by a 'consumer' based on Module Federation. Along the way, you have preliminarily used and understood the configurations of remotes, exposes, and shared in the Module Federation plugin. If you want to add Module Federation to an existing project, start from [Integrations](/integrations/index.md) and choose the matching path. ## Next Steps You may want to: [Integrations Choose the package for your project ](/integrations)[Review Configuration Learn how to configure Module Federation ](/configure)[Glossary of Terms Understand the concepts related to Module Federation ](/guide/start/glossary) ## Follow Us - [GitHub - Star us on GitHub](https://github.com/module-federation/core) - [Discord](https://discord.com/channels/1055442562959290389/1055442563718467637) - [Feishu Group (Chinese Community)](https://applink.larkoffice.com/client/chat/chatter/add_by_link?link_token=a41s8f79-741f-41ba-8349-395d9a0e9662) --- url: /guide/start/glossary.md --- # Glossary of Terms ## Module Federation Module Federation (MF) is an architectural pattern for partitioning JavaScript applications, similar to microservices on the server side. It allows you to share code and resources between multiple JavaScript applications (or micro frontends). If an application using federated modules lacks the dependencies required by the federated code, the missing dependencies will be downloaded from the build source or a peer that is able to share it. This enables the creation of micro-frontend style applications where multiple systems can share code and update dynamically without the need to rebuild the entire application. This also enabled a wider set of use cases on the server side, as federation operates universally, it has several dynamic backend use cases. ## Producer (Remote) An application that exposes other modules to be consumed by other JavaScript applications through the Module Federation build plugin with the `exposes` configuration is referred to as a Provider (Producer) in Module Federation. A Producer can also act as a Consumer. ## Consumer (Host) An application that consumes modules from other Producers through the Module Federation build plugin with the `remotes` configuration is referred to as a Consumer (Consumer). A Consumer can also act as a Producer. ## Micro-frontend Micro-frontend (MFE) is an architectural style similar to microservices, where a cohesive single product is composed of multiple independently delivered frontend applications. It breaks down a frontend application into smaller, simpler applications that can be independently developed, tested, and deployed, while still appearing as a single product to the user. It primarily addresses two issues: - The increasing size and maintenance difficulty of applications as they evolve through iterations. - The low efficiency of cross-team or cross-department collaboration in project development. ## Bundler Refers to module bundling tools such as [Webpack](https://webpack.js.org/), [Rspack](https://rspack.dev/), Vite, and Metro. The main goal of a bundler is to package JavaScript, CSS, and other files together. The packaged files can be used in browsers, Node.js, and other environments. When a Bundler processes a web application, it constructs a dependency graph that includes all the modules required by the application and then packages all modules into one or more bundles. To choose the right tool and plugin for your project, see [Integrations](/integrations/index.md). ## Manifest Manifest is a runtime manifest generated by a producer during build. It describes the remote entry, exposed modules, assets, shared dependencies, and type information. A consumer can read `mf-manifest.json` and use that information to load remote modules. For more details, see [Manifest and Snapshot](/guide/basic/manifest-snapshot.md), [manifest configuration](/configure/manifest.md), and [mf-manifest.json fields](/guide/advanced/manifest-fields.md). ## Snapshot Snapshot is a condensed and pre-resolved result of Manifest information. The runtime can generate a Snapshot from Manifest. If you have a deployment service, it can also generate and deliver Snapshot ahead of time so the consumer can get the remote entry and preloadable assets directly. Snapshot is used by remote loading, asset preloading, DevTools, proxying, and runtime diagnostics. --- url: /guide/basic/cli.md --- # Command Line Tool Module Federation provides a lightweight command-line tool `mf`, shipped by `@module-federation/enhanced`. :::note Once you have completed integration by following an [integration guide](/integrations/index.md) , `@module-federation/enhanced` is typically already in your dependency tree and this command is available out of the box. Otherwise, install it manually in your project before running the commands below. ::: ## View All Commands If you need to view all available CLI commands, please run the following command in the project directory: ```bash npx mf -h ``` Output: ```bash Usage: mf [options] Options: -V, --version output the version number -h, --help display help for command Commands: dts [options] generate or fetch the mf types help [command] display help for command ``` ## Common Options Module Federation CLI provides some common options that can be used for all commands: | Option | Description | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `-c, --config ` | Specify the configuration file path, which can be a relative or absolute path. The default value is {props.configName ? props.configName : 'module-federation.config.ts' } | | `-m, --mode ` | Specify the running environment, you can choose "dev" or "prod". The default value is "dev". After setting, "development" or "production" will be automatically injected into the `process.env.NODE_ENV` environment variable according to the value. | | `-h, --help` | Show command help | ##mf dts `mf dts ` is used to pull or generate TypeScript type declaration files. ```bash Usage: mf dts [options] generate or fetch the mf types Options: --root specify the project root directory --output specify the generated dts output directory --fetch fetch types from remote, default is true (default: true) --generate generate types, default is true (default: true) -c --config specify the configuration file, can be a relative or absolute path -m --mode Specify the runtime environment. You can choose "dev" or "prod". The default value is "dev". After setting, the process.env.NODE_ENV environment variable will be automatically injected with "development" or "production" according to the value. (default: "dev") -h, --help display help for command ``` :::info ๆณจๆ„ The `mf dts ` command will automatically generate or pull type declaration files based on the configuration in `module-federation.config.ts`. This means you must provide a valid configuration file, otherwise the command will not run correctly. If you are only using the runtime API, you need to create a temporary `module-federation.config.ts` file, configure [dts.consumeTypes.remoteTypeUrls](/configure/dts.md#remotetypeurls) , and then run the `mf dts` command. ::: --- url: /guide/basic/css-isolate.md --- # Style Isolation In Module Federation, the host and remote applications run on the same page and share a single global CSS scope, so styles from one side can easily leak into another and cause unintended "style pollution". Module Federation does not ship built-in style isolation (see the [FAQ](#why-doesnt-module-federation-directly-handle-css-style-isolation) below for the reasoning), so producers should pick the strategy that fits their scenario. ## BEM (Block Element Modifier) BEM is a naming convention that achieves style isolation by adding prefixes and suffixes to class names. The structure of BEM is as follows: - **Block**: Represents an independent functional block, e.g., `.button` - **Element**: Represents a part of the block, e.g., `.button__text` - **Modifier**: Represents different states or versions of the block or element, e.g., `.button--primary` ```html
Button
``` ```css .button { background-color: blue; color: white; } .button__text { font-size: 16px; } .button--primary { background-color: green; } ``` ## CSS Modules CSS Modules achieve style isolation by treating each CSS file as a module, where each class name is transformed into a unique identifier during compilation. [Using CSS Modules in Rsbuild](https://v2.rsbuild.rs/guide/styling/css-modules) 1. Create a CSS file named `styles.module.css`: ```css /* styles.module.css */ .button { background-color: blue; color: white; } ``` 2. Import and use it in a React component: ```javascript import React from 'react'; import styles from './styles.module.css'; function App() { return ; } export default App; ``` ## CSS-in-JS CSS-in-JS writes styles directly in JavaScript files. Common libraries include [styled-components](https://styled-components.com/) and [emotion](https://github.com/emotion-js/emotion). [Using CSS-in-JS in Rsbuild](https://rsbuild.rs/guide/styling/css-in-js) 1. Install styled-components: ```bash npm install styled-components ``` 2. Use it in a React component: ```javascript import React from 'react'; import styled from 'styled-components'; const Button = styled.button` background: blue; color: white; `; function App() { return ; } export default App; ``` ## Shadow DOM Shadow DOM is part of the Web Components technology, creating an isolated DOM tree to achieve style isolation. React can use Shadow DOM via [react-shadow](https://www.npmjs.com/package/react-shadow). 1. Create an HTML template: ```html ``` 2. Define a custom element and attach Shadow DOM: ```javascript class MyComponent extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: 'open' }); const template = document.getElementById('my-component').content; shadow.appendChild(template.cloneNode(true)); } } customElements.define('my-component', MyComponent); ``` 3. Use the custom element in HTML: ```html ``` ## Vue Scoped Styles In Vue, the `scoped` attribute can be used to achieve component-level style isolation. 1. Define styles in a Vue component: ```vue ``` Each method has its own applicable scenarios and limitations. Developers can choose the appropriate style isolation solution based on project requirements. ## FAQ ### Why doesn't Module Federation directly handle CSS style isolation? The main reasons for not directly incorporating CSS isolation into Module Federation are: - CSS isolation can conflict significantly with shared dependencies. Shared dependencies aim to reuse common dependencies as much as possible, which can lead to some shared dependencies escaping the sandbox, making isolation uncontrollable and potentially affected by load order. - Runtime handling of CSS isolation can have many edge cases and troubleshooting issues can be very difficult, leading to decreased business stability. Common issues include: - Shadow DOM: This method can cause compatibility issues with various component libraries, and troubleshooting online issues can be long and complex, with no guarantee of resolution. - Collecting and clearing CSS: Due to the isolation and shared reuse issues mentioned above, CSS might be unintentionally cleared. - Upgrading the sandbox in the consumer: The impact on business is uncontrollable. > Suggested handling methods: - Process CSS at the module or sub-application producer level to ensure that the module or application runs as expected in any environment. - Use CSS modules, component library prefixes, and unified component library versions to solve the problem. - Directly export Shadow DOM components for use by other businesses. --- url: /guide/basic/type-prompt.md --- # Type Hinting Just like an NPM Package, Module Federation artifacts also generate types and enjoy hot-reloading of types, even though the artifacts are hosted on a remote CDN. `@module-federation/enhanced` has type hinting enabled by default. This article will introduce several common usage scenarios and their specific configurations. ## Usage Scenarios ### Generating Types When building with the build plugin provided by `@module-federation/enhanced`, type files are automatically generated. ### Loading Types 1. Consumer modifies `tsconfig.json` and adds `paths`: ```json { "compilerOptions": { "paths": { "*": ["./@mf-types/*"] } } } ``` 2. Start the applications After starting, the consumer will automatically fetch the producer's type files. :::tip You don't need to worry about the startup order of the producer and consumer. ::: ### Type Hot Reloading After modifying the producer's code, the consumer will automatically fetch the producer's types. ![hot-types-reload-static](/static/image/hot-types-reload-static.6f665135c3.gif) ### Runtime API Type Hinting Add `./@mf-types/*` to the `include` field to enjoy `Runtime` `loadRemote` type hinting and type hot reloading: ```json title="tsconfig.json" { "include": ["./@mf-types/*"] } ``` ![hot-types-reload](/static/image/hot-types-reload.c4031555f8.gif) :::info When the builder is `webpack`, add `**/@mf-types/**` to [watchOptions.ignored](https://webpack.js.org/configuration/watch/#watchoptionsignored) to avoid circular compilation issues caused by type updates. ::: ### Third-Party Package Type Extraction Module Federation automatically extracts third-party package types to ensure that `exposes` file types can be accessed normally. ![third-party](/static/image/third-party.5273d7e2a6.jpg) ### Nested Type Re-export Module Federation supports nested remotes and will automatically extract the types of nested remotes. ![nested-remote](/static/image/nested-remote.49110ea0dc.jpg) ### Dynamic Type Hinting Module Federation supports loading dynamic types and also supports hot updates. ![dynamic-remote-hot-types-reload](/static/image/dynamic-remote-hot-types-reload-static.f804fbd1d9.gif) ## Configuration - [dts](/configure/dts.md): Type generation/loading - [dev](/configure/dev.md): Type hot reloading ## FAQ ### Project has circular compilation and cannot be terminated This is likely caused by the compiler watching changes in the type folder or the `dist` directory. Add `@mf-types` to the ignored files. For example, with webpack/rspack: ```ts config.watchOptions = { ignored: ['**/node_modules/**', '**/@mf-types/**'], }; ``` ### No type files are generated, how to find the reason? 1. Add the `FEDERATION_DEBUG=true` environment variable before the project startup command. 2. Set [dts.displayErrorInTerminal](/configure/dts.md#displayerrorinterminal) to `true`. 3. Start the project and check the console output. 4. (Optional) If there is no error in the console, check the `.mf/typesGenerate.log` log file. --- url: /guide/basic/manifest-snapshot.md --- # Manifest and Snapshot `mf-manifest.json` is a runtime manifest generated by a producer after build. It describes which modules the producer exposes, where the remote entry is, which assets those modules need, which shared dependencies are available, and where the type files are. Snapshot is a condensed and pre-resolved result of Manifest information; the runtime can request Manifest and generate Snapshot on demand. If you have a deployment service, it can also generate Snapshot ahead of time and deliver it to the consumer, so the consumer can get the remote entry and preloadable assets directly. This document only explains the concepts and where they are used. For configuration, see [manifest configuration](/configure/manifest.md). For complete fields, see [mf-manifest.json fields](/guide/advanced/manifest-fields.md). ## Why Manifest With only `remoteEntry.js`, the consumer can load remote modules, but it cannot easily know the assets, types, and shared dependency information behind those remote modules ahead of time. With `mf-manifest.json`, the consumer can get more information before loading the remote module: - Remote entry URL and loading type - Exposed modules and asset lists - JavaScript and CSS that can be preloaded - Shared dependency names, versions, and assets - Type file URLs So Manifest is not just another entry URL. It is closer to a runtime instruction file that the producer provides to the consumer. ## Manifest and Stats After enabling [manifest configuration](/configure/manifest.md), the build plugin generates both `mf-manifest.json` and `mf-stats.json`. | File | Purpose | | ------------------ | ------------------------------------------------------------------------------- | | `mf-manifest.json` | Read by the consumer runtime. Its fields are more stable and compact | | `mf-stats.json` | Used for build analysis, diagnostics, and tooling. Its fields are more complete | If you only need to consume remote modules, you usually only need `mf-manifest.json`. If you are building analysis tools, diagnostics, or platform integrations, then inspect `mf-stats.json`. ## What Snapshot Is Snapshot can be understood as the module information that the runtime actually consumes. Manifest is a build artifact from a single producer and is closer to a source manifest. Snapshot reorganizes that information into a structure that is easier for the runtime to consume, such as: - The final `remoteEntry` for the producer - Assets for exposed modules - Assets that can be preloaded - Shared dependency information - Other remote modules that the producer depends on Without a deployment service, the consumer requests `mf-manifest.json` first, and the runtime generates Snapshot from it. ```txt Host configures remotes -> request mf-manifest.json -> parse Manifest -> generate Snapshot -> load remoteEntry, expose, shared, and preload assets ``` With a deployment service, the service can consume Manifest ahead of time, generate Snapshot, and deliver it to the consumer. ```txt Producer builds Manifest -> deployment service parses Manifest ahead of time -> generate Snapshot -> Host gets remoteEntry and preload assets directly ``` This can avoid one extra Manifest request at runtime and move asset resolution to the server side. ## Where We Use It ### Remote Loading When `remotes` points to `mf-manifest.json`, the runtime reads Manifest, generates Snapshot, and then resolves the final `remoteEntry` URL from Snapshot. See [remotes](/configure/remotes.md) for configuration. ### Asset Preloading Manifest contains asset information for exposes and shared dependencies. After Snapshot is generated, the runtime can use that information to preload the JavaScript and CSS required by remote modules. ### Chrome DevTools [Chrome DevTools](/guide/debug/chrome-devtool.md) uses Manifest / Snapshot to understand which federated modules exist on the current page, how modules depend on each other, how shared dependencies are reused, and which remote entry is needed for proxying. ### Local Proxying When debugging a production page, proxy tools can use Snapshot to identify the target producer and replace it with a locally running Manifest or remote entry. ### Runtime Diagnostics When remote loading fails, Manifest / Snapshot can help identify where the failure happens: - Whether the Manifest URL is reachable - Whether Manifest fields are complete - Whether the target producer exists in Snapshot - Whether Snapshot can resolve `remoteEntry` - Whether `remoteEntry` loads successfully - Whether the expose exists This helps distinguish Manifest request failures, Snapshot misses, and later remote entry loading failures. ## What You Can Build With It Manifest / Snapshot is suitable as input for runtime plugins, debugging tools, and deployment platforms. You can build: - **Preload optimization**: get the JS and CSS associated with remote modules earlier and reduce waiting time after user interaction. - **Module graph visualization**: show which producers, consumers, and shared dependencies are loaded on the current page. - **Local proxy debugging**: replace a producer on a production page with a locally running producer. - **Loading diagnostics**: tell whether a failure happens at the Manifest, Snapshot, remoteEntry, or expose stage. - **Shared analysis**: check whether shared dependencies are reused, duplicated, or matched against the expected version. Business code should not modify global Snapshot directly. Prefer consuming this information through Runtime Plugin, DevTools, or a deployment service. ## Impact of disableSnapshot If [`optimization.disableSnapshot`](/configure/experiments.md#disablesnapshot) is enabled, Snapshot and preload-related runtime capabilities are removed to reduce runtime size. This affects capabilities that depend on Manifest / Snapshot, such as remote type hints, preloading, DevTools visualization, and proxying. --- url: /guide/runtime/index.md --- # Runtime Access :::info Prerequisite reading - The [features and capabilities](/guide/start/index.md) of Module Federation - The [glossary](/guide/start/glossary.md) of Module Federation - How to [consume and export modules](/guide/start/quick-start.md) ::: Currently, `Module Federation` provides two ways to register and load modules: - One is to declare it in the build plugin (usually in the `module-federation.config.ts` file). - The other way is to directly register and load modules through the `runtime` API. The two modes are not conflicting and can be used together. You can flexibly choose the module registration method and timing according to your actual scenario. ## Differences between the two approaches | Registering modules at runtime | Registering modules in the plugin | | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | Can be used without the build plugin, and pure runtime can be used directly for module registration and loading in projects like `webpack4` | The build plugin needs to be webpack5 or above | | Supports dynamic module registration | Does not support dynamic module registration | | Does not support loading modules with `import` syntax | Supports loading modules with `import` synchronous syntax | | Supports loading modules with `loadRemote` | Supports loading modules with `loadRemote` | | Setting `shared` must provide specific version and instance information | Setting `shared` only requires configuring rules, without providing specific version and instance information | | `shared` dependencies can only be used externally, and external `shared` dependencies cannot be used | `shared` dependencies are shared bidirectionally according to specific rules | | The loading process can be affected through the `runtime`'s `plugin` mechanism | Currently does not support providing `plugin` to affect the loading process | | Does not support remote type hints | Supports remote type hints | If the build plugin is used, an MF instance will be automatically created and stored in memory when the project starts. You can directly call methods of the MF instance via the . ```ts import { loadRemote } from '@module-federation/enhanced/runtime'; loadRemote('remote1'); ``` If the build plugin is not used, you need to manually create an MF instance before calling the corresponding API. ```ts import { createInstance } from '@module-federation/enhanced/runtime'; const mf = createInstance({ name: 'host', remotes: [ { name: 'remote1', entry: 'http://localhost:2001/vmok-manifest.json', }, ], }); mf.loadRemote('remote1'); ``` :::tip ModuleFederation instance An object created by the `ModuleFederation` class that contains all runtime functionality. You can enter `__FEDERATION__.__INSTANCES__` in the console to view the full instance information. ::: ## Installation Different project types should install and import Runtime from different entries. Most projects should install `@module-federation/enhanced` and import Runtime APIs from `@module-federation/enhanced/runtime`. If your Modern.js project already uses a Module Federation plugin, import Runtime APIs from the plugin's `/runtime` entry so the framework plugin and manual Runtime calls use the same Runtime instance. ### @module-federation/enhanced ```sh [npm] npm install @module-federation/enhanced --save ``` ```sh [yarn] yarn add @module-federation/enhanced --save ``` ```sh [pnpm] pnpm add @module-federation/enhanced --save ``` ```sh [bun] bun add @module-federation/enhanced --save ``` ```ts import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; ``` ### Modern.js ```sh [npm] npm install @module-federation/modern-js-v3 --save ``` ```sh [yarn] yarn add @module-federation/modern-js-v3 --save ``` ```sh [pnpm] pnpm add @module-federation/modern-js-v3 --save ``` ```sh [bun] bun add @module-federation/modern-js-v3 --save ``` ```ts import { createInstance, loadRemote } from '@module-federation/modern-js-v3/runtime'; ``` For Modern.js v2 projects, install `@module-federation/modern-js` and import Runtime APIs from `@module-federation/modern-js/runtime`. ## Module Registration **Build Plugin(Use build plugin)** ```tsx // If use build plugin, you can use `registerRemotes` directly. import { registerRemotes } from '@module-federation/enhanced/runtime'; registerRemotes([ { name: 'remote1', alias: 'remote-1', entry: 'http://localhost:3001/mf-manifest.json', } ]); ``` **Pure Runtime(Not use build plugin)** ```ts // If not use build plugin, you can create new instance and register remote. import { createInstance } from '@module-federation/enhanced/runtime'; const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'remote1', alias: 'remote-1', entry: 'http://localhost:3001/mf-manifest.json', } ] }); mf.registerRemotes([ { name: 'remote1', alias: 'remote-1', entry: 'http://localhost:3001/mf-manifest.json', } ]); ``` ## Module Loading **Build Plugin(Use build plugin)** ```tsx // If use build plugin, you can use `loadRemote` directly. import { loadRemote } from '@module-federation/enhanced/runtime'; import React from 'react'; export default () => { const MyButton = React.lazy(() => loadRemote('remote1').then(({ MyButton }) => { return { default: MyButton }; }), ); return ( ); } ``` **Pure Runtime(Not use build plugin)** ```ts // If not use build plugin, you can create new instance and load remote. import { createInstance } from '@module-federation/enhanced/runtime'; import React from 'react'; const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'remote1', alias: 'remote-1', entry: 'http://localhost:3001/mf-manifest.json', } ] }); export default () => { const MyButton = React.lazy(() => mf.loadRemote('remote1').then(({ MyButton }) => { return { default: MyButton }; }), ); return ( ); } ``` ### Loading Anonymous Modules **Build Plugin(Use build plugin)** ```tsx // If use build plugin, you can use `loadRemote` directly. import React from 'react'; import { loadRemote } from '@module-federation/enhanced/runtime'; const RemoteButton = React.lazy(() => loadRemote('provider/button')); // ไนŸๅฏ้€š่ฟ‡ๆจกๅ—ๅˆซๅๅŠ ่ฝฝ๏ผš // const RemoteButton = React.lazy(() => loadRemote('remotes-1/button')); export default () => { return ( ); } ``` **Pure Runtime(Not use build plugin)** ```tsx // If not use build plugin, you can create new instance and load remote. import { createInstance } from '@module-federation/enhanced/runtime'; import React from 'react'; // ๅˆ›ๅปบๅฎžไพ‹ const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'remote1', alias: 'remote-1', entry: 'http://localhost:3001/mf-manifest.json', } ] }); // Use instance api load remote. const RemoteButton = React.lazy(() => mf.loadRemote('provider/button')); // Also support use alias to load // const RemoteButton = React.lazy(() => mf.loadRemote('remotes-1/button')); export default () => { return ( ); } ``` ### Loading Named Modules **Build Plugin(Use build plugin)** ```tsx // If use build plugin, you can use `loadRemote` directly. import React from 'react'; import { loadRemote } from '@module-federation/enhanced/runtime'; export default () => { const RemoteButton = React.lazy(() => loadRemote('remote1/button').then(({ RemoteButton }) => { return { default: RemoteButton }; }), ); return ( ); } ``` **Pure Runtime(Not use build plugin)** ```tsx // If not use build plugin, you can create new instance and load remote. import { createInstance } from '@module-federation/enhanced/runtime'; import React from 'react'; // ๅˆ›ๅปบๅฎžไพ‹ const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'remote1', alias: 'remote-1', entry: 'http://localhost:3001/mf-manifest.json', } ] }); export default () => { const RemoteButton = React.lazy(() => // Use instance api load remote. mf.loadRemote('remote1/button').then(({ RemoteButton }) => { return { default: RemoteButton }; }), ); return ( ); } ``` ### Loading Utility Functions **Build Plugin(Use build plugin)** ```tsx // If use build plugin, you can use `loadRemote` directly. import React from 'react'; import { loadRemote } from '@module-federation/enhanced/runtime'; // ๅŠ ่ฝฝ remote1 expose ็š„ util loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ md.add(1,2); }); ``` **Pure Runtime(Not use build plugin)** ```tsx // If not use build plugin, you can create new instance and load remote. import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; import React from 'react'; // create instance const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'remote1', alias: 'remote-1', entry: 'http://localhost:3001/mf-manifest.json', } ] }); mf.loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ md.add(1,2); }); ``` ## Read Next - [Runtime API](/guide/runtime/runtime-api.md): `createInstance`, `loadRemote`, `registerRemotes`, and other runtime APIs. - [Runtime Plugins](/guide/runtime/runtime-plugins.md): extend the runtime loading flow. - [Runtime Hooks](/guide/runtime/runtime-hooks.md): lifecycle hooks available to runtime plugins. --- url: /guide/runtime/runtime-api.md --- # Runtime API MF runtime APIs are built around the `ModuleFederation` instance. The default instance is usually created automatically by the build plugin, so you do not need to pass an instance explicitly when calling runtime APIs โ€” they automatically apply to the default instance in the runtime. ## createInstance Used to create a new `ModuleFederation` instance. Unlike other runtime APIs, `createInstance` does not act on the default instance; it returns a brand-new instance that is isolated from the default one. **When to use** The default instance pattern already covers most scenarios. You only need `createInstance` in the following cases: - No build plugin is used (pure runtime scenario) - You need to create multiple `ModuleFederation` instances with different configurations in the same application - You want to leverage Module Federation's partitioning feature to encapsulate a set of APIs for use by other projects **Behavior** - Every call creates a brand-new instance and adds it to the global instance list - It does not look up existing instances by `name` / `version`, nor does it merge the new options into an existing instance - The new instance does not replace the default instance; if you need to retrieve it elsewhere, use [getInstance](#getinstance) **Example** ```ts import { createInstance } from '@module-federation/enhanced/runtime'; const mf = createInstance({ name: 'host', remotes: [ { name: 'sub1', entry: 'http://localhost:8080/mf-manifest.json' } ] }); mf.loadRemote('sub1/util').then((m) => m.add(1, 2, 3)); ``` ## init Use with caution Used to initialize or reuse a `ModuleFederation` runtime instance. `init` does not always create a new instance: when called, the runtime first looks for an existing instance by `name` and `version`. If a matching one is found, it is reused and the new options are merged via `instance.initOptions(options)`. Only when no match is found will a new instance be created. Therefore, `init` is better suited for scenarios where the same host is initialized multiple times and you want to reuse and extend the same runtime instance. :::warning - If you need to create a completely independent new instance, use [createInstance](#createinstance). - If you only want to access the instance created by the build plugin, use [getInstance](#getinstance). ::: - Type: `init(options: InitOptions): ModuleFederation` ย  InitOptions Type ```ts type InitOptions { // Name of the current host name: string; // List of dependent remote modules // tip: The remotes configured at runtime are not completely consistent in type and data with those passed in by the build plugin. remotes: Array; // List of dependencies that the current host needs to share // When using the build plugin, users can configure the dependencies to be shared in the build plugin, and the build plugin will inject the dependencies to be shared into the shared configuration at runtime. // When shared is passed in at runtime, the version instance reference must be passed in manually, because it cannot be directly obtained at runtime. shared?: { [pkgName: string]: ShareArgs | ShareArgs[]; }; }; type ShareArgs = | (SharedBaseArgs & { get: SharedGetter }) | (SharedBaseArgs & { lib: () => Module }); type SharedBaseArgs = { version: string; shareConfig?: SharedConfig; scope?: string | Array; deps?: Array; strategy?: 'version-first' | 'loaded-first'; }; type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); type RemoteInfo = { alias?: string; }; interface RemotesWithEntry { name: string; entry: string; } type ShareInfos = { // Package name and basic information of the dependency, sharing strategy [pkgName: string]: Share; }; type Share = { // Version of the shared dependency version: string; // Which modules consume the current shared dependency useIn?: Array; // Which module the shared dependency comes from from?: string; // Factory function to get the instance of the shared dependency. When the cached shared instance cannot be loaded, it will load its own shared dependency. lib: () => Module; // Sharing strategy, which strategy will be used to determine the reuse of shared dependencies shareConfig?: SharedConfig; // Dependencies between shares deps?: Array; // Under which scope the current shared dependency is placed, the default is default scope?: string | Array; }; ``` ย  Example ```ts import { init, loadRemote } from '@module-federation/enhanced/runtime'; init({ name: "mf_host", remotes: [ { name: "remote", // After configuring an alias, it can be loaded directly through the alias alias: "app1", // Decide which module to load by specifying the address of the module's manifest.json file entry: "http://localhost:2001/mf-manifest.json" } ], }); ``` :::tip Recommended migration If your code currently uses `init`, you can migrate to the recommended approach based on your scenario. ::: ### Migration option - using the build plugin Remove the `init` call, use [getInstance](#getinstance) to retrieve the instance, and use [registerRemotes](#registerremotes) / [registerShared](#registershared) / [registerPlugins](#registerplugins) to register the options that were previously passed to `init`. ```diff - import { init } from '@module-federation/enhanced/runtime'; + import { registerShared, registerRemotes, registerPlugins, getInstance } from '@module-federation/enhanced/runtime'; import React from 'react'; import mfRuntimePlugin from 'mf-runtime-plugin'; - const instance = init({ - name: 'mf_host', - remotes: [ - { - name: 'remote', - entry: 'http://localhost:2001/mf-manifest.json', - }, - ], - shared: { - react: { - version: '18.0.0', - scope: 'default', - lib: () => React, - shareConfig: { - singleton: true, - requiredVersion: '^18.0.0', - }, - }, - }, - plugins: [mfRuntimePlugin()], - }); + const instance = getInstance(); + registerRemotes([ + { + name: 'remote', + entry: 'http://localhost:2001/mf-manifest.json', + }, + ]); + registerShared({ + react: { + version: '18.0.0', + scope: 'default', + lib: () => React, + shareConfig: { + singleton: true, + requiredVersion: '^18.0.0', + }, + }, + }); + registerPlugins([mfRuntimePlugin()]); ``` ### Migration option - pure runtime Switch directly to [createInstance](#createinstance); the option shape stays the same. But note the different semantics: `createInstance` always creates a new instance, while `init` may reuse an existing instance with the same `name` / `version` and merge the new options into it. ```diff - import { init } from '@module-federation/enhanced/runtime'; + import { createInstance } from '@module-federation/enhanced/runtime'; - const instance = init({ + const instance = createInstance({ name: 'mf_host', remotes: [ { name: 'remote', entry: 'http://localhost:2001/mf-manifest.json', }, ], shared: { react: { version: '18.0.0', scope: 'default', lib: () => React, shareConfig: { singleton: true, requiredVersion: '^18.0.0', }, }, }, plugins: [mfRuntimePlugin()], }); ``` ## getInstance - Type: `getInstance(): ModuleFederation | null` - Type: `getInstance(finder: (instance: ModuleFederation) => boolean): ModuleFederation | null` - Retrieves the default `ModuleFederation` instance, or the first registered instance that matches a finder callback Once the default instance has been created by the build plugin or by [init](#init), you can call `getInstance()` to retrieve it. ```ts import { getInstance } from '@module-federation/enhanced/runtime'; const mfInstance = getInstance(); if (!mfInstance) { throw new Error('Module Federation instance is not initialized'); } mfInstance.loadRemote('remote/util'); ``` If the build plugin is not used, calling `getInstance` will throw an exception. In that case you need to use [createInstance](#createinstance) to create a new instance. Instances created via [createInstance](#createinstance) do not replace the default instance, but they are still registered in the global instance list. So even if you did not keep the returned reference, you can still find them later by passing a finder callback to `getInstance`. The finder callback behaves like `Array.prototype.find`: the runtime iterates over the currently registered instances and returns the first match. If no matching instance is found, `getInstance` returns `null`. ```ts const targetInstance = getInstance( (instance) => instance.name === 'remote-host', ); if (targetInstance) { targetInstance.loadRemote('remote/util'); } ``` ## registerRemotes ย  Type declaration ```typescript function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {} type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon; interface RemoteInfoCommon { alias?: string; shareScope?: string; type?: RemoteEntryType; entryGlobalName?: string; } interface RemoteWithEntry { name: string; entry: string; } interface RemoteWithVersion { name: string; version: string; } ``` :::warning When `force: true` is set, the newly registered modules will overwrite already-registered and loaded modules, and the cache of the loaded modules will be automatically deleted. A warning will also be printed to the console to inform you that this operation is risky. ::: **Build Plugin (Use build plugin)** ```tsx import { registerRemotes } from '@module-federation/enhanced/runtime'; // register new remote sub2 registerRemotes([ { name: 'sub2', entry: 'http://localhost:2002/mf-manifest.json', } ]); // override remote sub1 registerRemotes([ { name: 'sub1', entry: 'http://localhost:2003/mf-manifest.json', } ], { force: true }); ``` **Pure Runtime (Not use build plugin)** ```ts import { createInstance } from '@module-federation/enhanced/runtime'; const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'sub1', entry: 'http://localhost:2001/mf-manifest.json', } ] }); // register new remote sub2 mf.registerRemotes([ { name: 'sub2', entry: 'http://localhost:2002/mf-manifest.json', } ]); // override remote sub1 mf.registerRemotes([ { name: 'sub1', entry: 'http://localhost:2003/mf-manifest.json', } ], { force: true }); ``` ## registerPlugins ```typescript function registerPlugins(plugins: ModuleFederationRuntimePlugin[]) {} ``` **Build Plugin (Use build plugin)** ```tsx import { registerPlugins } from '@module-federation/enhanced/runtime'; import runtimePlugin from './custom-runtime-plugin'; // add new runtime plugin registerPlugins([runtimePlugin()]); registerPlugins([ { name: 'custom-plugin-runtime', beforeInit(args) { const { userOptions, origin } = args; if (origin.options.name && origin.options.name !== userOptions.name) { userOptions.name = origin.options.name; } console.log('[build time inject] beforeInit: ', args); return args; }, beforeLoadShare(args) { console.log('[build time inject] beforeLoadShare: ', args); return args; }, createLink({ url }) { const link = document.createElement('link'); link.setAttribute('href', url); link.setAttribute('rel', 'preload'); link.setAttribute('as', 'script'); link.setAttribute('crossorigin', 'anonymous'); return link; }, } ]); ``` **Pure Runtime (Not use build plugin)** ```ts import { createInstance } from '@module-federation/enhanced/runtime'; import runtimePlugin from './custom-runtime-plugin'; const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'sub1', entry: 'http://localhost:2001/mf-manifest.json', } ] }); // add new runtime plugin mf.registerPlugins([runtimePlugin()]); mf.registerPlugins([ { name: 'custom-plugin-runtime', beforeInit(args) { const { userOptions, origin } = args; if (origin.options.name && origin.options.name !== userOptions.name) { userOptions.name = origin.options.name; } console.log('[build time inject] beforeInit: ', args); return args; }, beforeLoadShare(args) { console.log('[build time inject] beforeLoadShare: ', args); return args; }, createLink({ url }) { const link = document.createElement('link'); link.setAttribute('href', url); link.setAttribute('rel', 'preload'); link.setAttribute('as', 'script'); link.setAttribute('crossorigin', 'anonymous'); return link; }, } ]); ``` ## registerGlobalPlugins ```typescript function registerGlobalPlugins(plugins: ModuleFederationRuntimePlugin[]): void {} ``` Registers plugins on the global federation state instead of on a single current instance. This is suitable for scenarios such as: - shared instrumentation - environment-wide policy - host-wide default plugins Global plugins are deduplicated by `plugin.name`. ```ts import { registerGlobalPlugins, createInstance } from '@module-federation/enhanced/runtime'; import runtimePlugin from './runtime-plugin'; registerGlobalPlugins([runtimePlugin()]); const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'sub1', entry: 'http://localhost:2001/mf-manifest.json', }, ], }); ``` For predictable behavior, register global plugins before creating or using runtime instances. ## registerShared Registers shared dependencies for the host. The runtime will prefer to reuse a shared dependency that already exists globally and satisfies the conditions; otherwise it falls back to the dependency registered here. ```typescript function registerShared(shared: Shared): void; type Shared = { // Mapping of shared dependency package names; the same package can be registered with multiple versions (array form) [pkgName: string]: ShareArgs | ShareArgs[]; }; /** * ShareArgs must provide either lib or get * * lib: synchronous factory function that returns the module immediately when called; should not return a Promise. * Suitable when the module is already available at the time `registerShared` is called, e.g. `import React from 'react'` * * get: used for async / lazy loading scenarios * */ type ShareArgs = | (SharedBaseArgs & { lib: () => Module }) | (SharedBaseArgs & { get: SharedGetter }) | SharedBaseArgs; type SharedBaseArgs = { // Version number being registered; recommended for version differentiation version?: string; // Sharing strategy, controlling singleton, requiredVersion, etc. shareConfig?: SharedConfig; // Scope it belongs to, defaults to 'default'; can be placed under multiple scopes simultaneously scope?: string | Array; // Names of other shared dependencies that this one depends on deps?: Array; // Version selection strategy: prefer by version number / prefer already loaded instance strategy?: 'version-first' | 'loaded-first'; loaded?: boolean; }; interface SharedConfig { singleton?: boolean; requiredVersion: false | string; eager?: boolean; strictVersion?: boolean; layer?: string | null; } type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); ``` **Build Plugin (Use build plugin)** ```tsx import { registerShared } from '@module-federation/enhanced/runtime'; import React from 'react'; import ReactDom from 'react-dom'; registerShared({ react: { version: '18.0.0', scope: 'default', lib: () => React, shareConfig: { singleton: true, requiredVersion: '^18.0.0', }, }, 'react-dom': { version: '18.0.0', scope: 'default', lib: () => ReactDom, shareConfig: { singleton: true, requiredVersion: '^18.0.0', }, }, antd: { version: '1.0.0', scope: 'default', get: () => import('antd').then((m) => () => m), }, }); ``` **Pure Runtime (Not use build plugin)** ```ts import { createInstance } from '@module-federation/enhanced/runtime'; import React from 'react'; import ReactDom from 'react-dom'; const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'sub1', entry: 'http://localhost:2001/mf-manifest.json', } ] }); mf.registerShared({ react: { version: '18.0.0', scope: 'default', lib: () => React, shareConfig: { singleton: true, requiredVersion: '^18.0.0', }, }, 'react-dom': { version: '18.0.0', scope: 'default', lib: () => ReactDom, shareConfig: { singleton: true, requiredVersion: '^18.0.0', }, }, antd: { version: '1.0.0', scope: 'default', get: () => import('antd').then((m) => () => m), }, }); ``` ## loadShare ```ts type loadShare = ( pkgName: string, extraOptions?: { customShareInfo?: Partial; resolver?: (sharedOptions: ShareInfos[string]) => Shared; } ) => Promise<() => ShareModule>; ``` Gets a `share` dependency. When there is a `share` dependency in the global environment that meets the requirements of the current `host`, the existing dependency that meets the `share` conditions will be reused first. Otherwise, its own dependency will be loaded and stored in the global cache for later reuse. This `API` is **generally not called directly by the user, but is used by the build plugin when transforming its own dependencies**. **Build Plugin (Use build plugin)** ```tsx import { registerShared, loadShare } from '@module-federation/enhanced/runtime'; import React from 'react'; import ReactDom from 'react-dom'; registerShared({ react: { version: '17.0.0', scope: 'default', lib: () => React, shareConfig: { singleton: true, requiredVersion: '^17.0.0', }, }, 'react-dom': { version: '17.0.0', scope: 'default', lib: () => ReactDom, shareConfig: { singleton: true, requiredVersion: '^17.0.0', }, }, }); loadShare('react').then((reactFactory) => { console.log(reactFactory()); }); ``` **Pure Runtime (Not use build plugin)** ```ts import { createInstance } from '@module-federation/enhanced/runtime'; import React from 'react'; import ReactDom from 'react-dom'; const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'remote', entry: 'http://localhost:2001/mf-manifest.json', alias: 'app1', } ] }); mf.registerShared({ react: { version: '17.0.0', scope: 'default', lib: () => React, shareConfig: { singleton: true, requiredVersion: '^17.0.0', }, }, 'react-dom': { version: '17.0.0', scope: 'default', lib: () => ReactDom, shareConfig: { singleton: true, requiredVersion: '^17.0.0', }, }, }); mf.loadShare('react').then((reactFactory) => { console.log(reactFactory()); }); ``` If multiple versions of shared are set, by default the loaded one with the highest version is returned. This behavior can be changed by setting `extraOptions.resolver`: ```ts loadShare('react', { resolver: (sharedOptions) => { return ( sharedOptions.find((i) => i.version === '17.0.0') ?? sharedOptions[0] ); }, }).then((reactFactory) => { console.log(reactFactory()); // { version: '17.0.0' } }); ``` ## loadRemote This API is used to load a remote module at runtime. ```ts type loadRemote = (remoteNameOrAlias: string) => Promise; ``` **Build Plugin (Use build plugin)** ```tsx import { loadRemote } from '@module-federation/enhanced/runtime'; // remoteName + expose loadRemote('remote/util').then((m) => m.add(1, 2, 3)); // alias + expose loadRemote('app1/util').then((m) => m.add(1, 2, 3)); ``` **Pure Runtime (Not use build plugin)** ```ts import { createInstance } from '@module-federation/enhanced/runtime'; const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'remote', entry: 'http://localhost:2001/mf-manifest.json', alias: 'app1', } ] }); // remoteName + expose mf.loadRemote('remote/util').then((m) => m.add(1, 2, 3)); // alias + expose mf.loadRemote('app1/util').then((m) => m.add(1, 2, 3)); ``` ## preloadRemote ย  Type declaration ```typescript async function preloadRemote(preloadOptions: Array){} type depsPreloadArg = Omit; type PreloadRemoteArgs = { // Name or alias of the remote to be preloaded nameOrAlias: string; // The exposes to be preloaded // By default, all exposes are preloaded // When exposes are provided, only the required exposes will be loaded exposes?: Array; // Default request // The default is sync, which only loads the synchronous code referenced in expose // When set to all, both synchronous and asynchronous references will be loaded resourceCategory?: 'all' | 'sync'; // When no value is configured, all dependencies are loaded by default // After configuring dependencies, only the configuration options will be loaded depsRemote?: boolean | Array; // When not configured, resources are not filtered // After configuration, unnecessary resources will be filtered filter?: (assetUrl: string) => boolean; // Whether to record preloaded JS and CSS URLs and skip duplicate loading // Defaults to true recordPreloadedAssets?: boolean; }; ``` With `preloadRemote`, you can start preloading module resources at an earlier stage to avoid waterfall requests. `preloadRemote` can preload: - the `remote`'s `remoteEntry` - the `remote`'s `expose` - the `remote`'s synchronous or asynchronous resources - the `remote`'s dependent `remote` resources `preloadRemote` waits for all resources involved in the current preload call to finish. If every resource is loaded successfully or hits the cache, the Promise resolves; if any resource fails or times out, the Promise rejects, and the error object carries the resource results of this preload call. If you only care about the final result, you can use `await` or `.then/.catch` directly: ```ts import { preloadRemote } from '@module-federation/enhanced/runtime'; try { await preloadRemote([ { nameOrAlias: 'sub1', exposes: ['add'], resourceCategory: 'all', }, ]); console.log('sub1/add preload success'); } catch (error) { console.error('sub1/add preload failed', error); } ``` If you need to track which specific resources succeeded, failed, timed out, or came from cache, read `results` from the error object: ```ts type PreloadRemoteError = Error & { results?: Array<{ id: string; results: Array<{ url: string; status: 'success' | 'error' | 'timeout' | 'cached'; resourceType: 'manifest' | 'remoteEntry' | 'js' | 'css'; error?: unknown; }>; }>; }; preloadRemote([ { nameOrAlias: 'sub1', exposes: ['add'], resourceCategory: 'all', }, ]).catch((error: PreloadRemoteError) => { const failedResources = error.results ?.flatMap((remoteResult) => remoteResult.results.map((resource) => ({ id: remoteResult.id, ...resource, })), ) .filter( (resource) => resource.status === 'error' || resource.status === 'timeout', ) ?? []; failedResources.forEach((resource) => { console.error( `[preloadRemote] ${resource.id} ${resource.resourceType} failed`, resource.url, resource.error, ); }); }); ``` When `exposes` is not specified, the resource id for this preload is `remoteName/*`. When `exposes` is specified, the runtime generates resources per expose, and the resource id is `remoteName/expose`. By default, the runtime records each JS and CSS URL before starting its preload. Later attempts to preload the same loading or loaded URL are skipped and reported as `cached`. This URL tracking does not change the existing lifecycle of the generated link or script element. Up to 2,000 URLs are retained; when the limit is exceeded, the oldest URL is removed. Set `recordPreloadedAssets: false` to disable both URL lookup and recording for a preload operation. **Build Plugin (Use build plugin)** ```tsx import { registerRemotes, preloadRemote } from '@module-federation/enhanced/runtime'; registerRemotes([ { name: 'sub1', entry: 'http://localhost:2001/mf-manifest.json', }, { name: 'sub2', entry: 'http://localhost:2002/mf-manifest.json', }, { name: 'sub3', entry: 'http://localhost:2003/mf-manifest.json', }, ]); // preload sub1 module // filter the resource information that carries ignore in the resource name // only preload sub-dependent sub1-button module preloadRemote([ { nameOrAlias: 'sub1', filter(assetUrl) { return assetUrl.indexOf('ignore') === -1; }, depsRemote: [{ nameOrAlias: 'sub1-button' }], }, ]); // preload sub2 module // preload all exposes under sub2 // preload synchronous and asynchronous resources of sub2 preloadRemote([ { nameOrAlias: 'sub2', resourceCategory: 'all', }, ]); // preload add expose of sub3 module preloadRemote([ { nameOrAlias: 'sub3', resourceCategory: 'all', exposes: ['add'], }, ]); ``` **Pure Runtime (Not use build plugin)** ```ts import { createInstance } from '@module-federation/enhanced/runtime'; const mf = createInstance({ name: 'mf_host', remotes: [], }); mf.registerRemotes([ { name: 'sub1', entry: 'http://localhost:2001/mf-manifest.json', }, { name: 'sub2', entry: 'http://localhost:2002/mf-manifest.json', }, { name: 'sub3', entry: 'http://localhost:2003/mf-manifest.json', }, ]); // preload sub1 module // filter the resource information that carries ignore in the resource name // only preload sub-dependent sub1-button module mf.preloadRemote([ { nameOrAlias: 'sub1', filter(assetUrl) { return assetUrl.indexOf('ignore') === -1; }, depsRemote: [{ nameOrAlias: 'sub1-button' }], }, ]); // preload sub2 module // preload all exposes under sub2 // preload synchronous and asynchronous resources of sub2 mf.preloadRemote([ { nameOrAlias: 'sub2', resourceCategory: 'all', }, ]); // preload add expose of sub3 module mf.preloadRemote([ { nameOrAlias: 'sub3', resourceCategory: 'all', exposes: ['add'], }, ]); ``` --- url: /guide/runtime/runtime-plugins.md --- # Runtime Plugins Runtime plugins let you change **runtime behavior** without changing the core runtime itself. Use them when you need to: - rewrite remote URLs at runtime - customize manifest requests - patch script loading - override shared resolution - add recovery or fallback behavior - introduce new remote loading behavior If you only need to **register** a plugin path in build config, see [`runtimePlugins`](/configure/runtimeplugins.md). If you need the full hook list, see [Runtime Hooks](/guide/runtime/runtime-hooks.md). ## Mental model A runtime plugin is a function that returns a `ModuleFederationRuntimePlugin`. New plugins should return their hooks from `apply(instance)`: ```ts import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; export default function myRuntimePlugin(): ModuleFederationRuntimePlugin { return { name: 'my-runtime-plugin', apply(instance) { return { // Declare runtime hooks here. }; }, }; } ``` The function form matters because it lets you: - accept options - keep state in a closure - reuse the same plugin logic across environments ## Register a plugin You can register runtime plugins in three places: 1. Build time, through `runtimePlugins` 2. Runtime, on a specific instance, through `registerPlugins(...)` or `instance.registerPlugins(...)` 3. Runtime, globally, through `registerGlobalPlugins(...)` ### Build-time registration Use build-time registration when the plugin should always be present for that host. ```ts title="module-federation.config.ts" const path = require('path'); export default { name: 'host', remotes: { catalog: 'catalog@https://registry.example.com/mf-manifest.json', }, runtimePlugins: [ [ path.resolve(__dirname, './plugins/rewrite-remote-entry.ts'), { fromHost: 'registry.example.com', toHost: 'cdn.example.com', }, ], ], }; ``` ### Runtime registration Use runtime registration when the plugin depends on user state, environment, feature flags, or data fetched after startup. ```ts import { createInstance } from '@module-federation/enhanced/runtime'; import rewriteRemoteEntryPlugin from './plugins/rewrite-remote-entry'; const mf = createInstance({ name: 'host', remotes: [ { name: 'catalog', entry: 'https://registry.example.com/mf-manifest.json', }, ], }); mf.registerPlugins([ rewriteRemoteEntryPlugin({ fromHost: 'registry.example.com', toHost: 'cdn.example.com', }), ]); ``` ### Global runtime registration Use `registerGlobalPlugins(...)` when you want the plugin to be available to all future runtime instances, instead of only one current instance. This is best suited for shared instrumentation, cross-app policy, or host-wide defaults. ```ts import { registerGlobalPlugins, createInstance, } from '@module-federation/enhanced/runtime'; import runtimePlugin from './plugins/runtime-plugin'; registerGlobalPlugins([runtimePlugin()]); const mf = createInstance({ name: 'host', remotes: [ { name: 'catalog', entry: 'https://registry.example.com/mf-manifest.json', }, ], }); ``` `registerGlobalPlugins(...)` deduplicates plugins by `name`. In practice, call it before creating or using runtime instances so the global plugins are incorporated consistently. ## Access the runtime instance in a plugin Previously, only a few hooks provided the runtime instance as an argument. If a plugin needs to access its runtime instance from any hook, implement `apply(instance)`. It is called once per plugin name on each instance, where the plugin can access the current instance and return the hooks to register for it. ```ts import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; const instanceTracePlugin: ModuleFederationRuntimePlugin = { name: 'instance-trace-plugin', apply(instance) { let completedLoads = 0; return { afterLoadRemote(args) { completedLoads += 1; console.log(instance.name, args.id, completedLoads); }, }; }, }; ``` The `instance` passed to `apply(instance)` is the current runtime instance. Use it to read instance information or call instance methods. Because each instance calls `apply` separately, counters, caches, and subscriptions declared there also remain isolated. The returned object is the hook set registered for that instance. ### Legacy syntax and compatibility Previously, hooks could be declared directly on the plugin object: ```ts const legacyPlugin: ModuleFederationRuntimePlugin = { name: 'legacy-plugin', beforeRequest(args) { return args; }, }; ``` This syntax is still supported, but will be deprecated. To migrate, move the existing hooks into the object returned by `apply(instance)`. When `apply` returns nothing, the current version still falls back to hooks declared on the plugin object. ## Choose the right hook | Job | Hook | Use it when | | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Change remote lookup input | `beforeRequest` | You need to rewrite the request before remote resolution starts | | Rewrite a resolved entry URL | `afterResolve` | You want to change `remoteInfo.entry` after the runtime has resolved the remote | | Customize manifest network requests | `fetch` | You need credentials, headers, retries, or alternate fetch behavior for manifest loading | | Customize injected scripts and links | `createScript`, `createLink` | You need to add attributes like `crossorigin`, timeouts, or custom script/link elements. When available, `remoteInfo` is provided so you can apply per-remote policy. | | Align or rewrite share scopes before remote init | `beforeInitContainer`, `initContainerShareScopeMap` | You need to change which share scope a remote initializes against | | Override the shared winner | `resolveShare` | You want to force a different shared implementation than the runtime would normally pick | | Observe remote load status | `afterMatchRemote`, `afterLoadRemote` | You need request tracing or final success/failure status for `loadRemote` | | Observe shared dependency status | `afterRegisterShare`, `afterLoadShare`, `errorLoadShare` | You need to diagnose provider registration, missing shared dependencies, version mismatch, or eager configuration errors | | Observe manifest loading | `beforeLoadManifest`, `afterLoadManifest` | You need manifest timing, cache, validation, or recovery diagnostics | | Observe Bridge operations | `beforeBridgeRender`, `afterBridgeRender`, `beforeBridgeDestroy`, `afterBridgeDestroy`, `afterBridgeRouteSync` | You need framework, producer/consumer, render, teardown, or route synchronization context | | Recover from load failures | `errorLoadRemote` | You need fallback modules, offline behavior, or layered recovery | | Implement a new remote loading strategy | `loadEntry` | You want to fully customize how a remote entry is loaded or support a new remote type | ## Recipe: rewrite the resolved remote entry `afterResolve` is the right hook when the remote has already been resolved and you want to change the final URL before loading continues. This pattern is useful for: - CDN indirection - environment switching - registry-backed routing - domain normalization ```ts import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; interface RewriteRemoteEntryOptions { fromHost: string; toHost: string; } export default function rewriteRemoteEntryPlugin( options: RewriteRemoteEntryOptions, ): ModuleFederationRuntimePlugin { return { name: 'rewrite-remote-entry', apply() { return { async afterResolve(args) { const entry = args.remoteInfo?.entry; if (!entry) { return args; } try { const currentUrl = new URL(entry); if (currentUrl.hostname !== options.fromHost) { return args; } const nextUrl = new URL(entry); nextUrl.hostname = options.toHost; args.remoteInfo.entry = nextUrl.toString(); } catch { // Ignore non-URL entries and keep the original resolution. } return args; }, }; }, }; } ``` Why this hook: - `beforeRequest` is earlier; the remote is not resolved yet - `afterResolve` already gives you `remoteInfo.entry` - changing `args.remoteInfo.entry` here keeps the original path, query, and hash intact :::tip Runtime-only caveat An `afterResolve` rewrite changes runtime loading behavior. It does not automatically rewrite the remote URL used by type generation or other build-time tooling. If you rely on generated remote types, keep that path aligned separately. ::: ## Recipe: customize manifest fetch Use `fetch` when the manifest request itself needs custom behavior. ```ts import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; export default function fetchManifestWithCredentials(): ModuleFederationRuntimePlugin { return { name: 'fetch-manifest-with-credentials', apply() { return { fetch(manifestUrl, requestInit, remoteInfo) { const headers = new Headers(requestInit?.headers); headers.set('x-mf-host', 'host'); if (remoteInfo?.name) { headers.set('x-mf-remote', remoteInfo.name); } return fetch(manifestUrl, { ...requestInit, credentials: 'include', headers, }); }, }; }, }; } ``` Use `fetch` for: - credentials - auth headers - manifest retries - custom proxy logic If you need a ready-made transport policy, see the built-in retry plugin. ## Recipe: prefer a host-owned shared dependency `resolveShare` is the hook for overriding the final shared module selection. The important part: changing `args.scope` or `args.version` alone is not enough. To change the actual winner, replace `args.resolver`. ```ts import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; export default function preferHostReact(): ModuleFederationRuntimePlugin { return { name: 'prefer-host-react', apply() { return { resolveShare(args) { if (args.pkgName !== 'react') { return args; } const hostVersionMap = args.shareScopeMap.default?.react; if (!hostVersionMap) { return args; } const preferredShared = hostVersionMap[args.version] ?? Object.values(hostVersionMap)[0]; if (!preferredShared) { return args; } args.resolver = () => ({ shared: preferredShared, useTreesShaking: false, }); return args; }, }; }, }; } ``` ## Failure handling and `shareStrategy` `errorLoadRemote` is the right place for runtime fallbacks. One important detail: the behavior changes with `shareStrategy`. - `version-first` eagerly loads remote entries during startup to initialize shared dependencies - `loaded-first` defers remote loading until the remote is actually used That means: - with `version-first`, an offline remote can fail early with `lifecycle: 'beforeLoadShare'` - with `loaded-first`, the same remote usually fails later, when code actually tries to use it If you expect remotes to be intermittently unavailable, pair `errorLoadRemote` with a deliberate `shareStrategy`. ## Version-sensitive hooks The advanced hooks evolve over time. For common hooks like `afterResolve`, `fetch`, `createScript`, and `loadEntry`, prefer checking the installed runtime types when you depend on newer arguments or behaviors. That is especially relevant when you rely on: - extra script attributes in `createScript` - loader hook details passed into `fetch` or `loadEntry` - less-common lifecycle hooks used by built-in plugins ## Related docs - [`runtimePlugins` config](/configure/runtimeplugins.md) - [Runtime API](/guide/runtime/runtime-api.md) - [Runtime Hooks](/guide/runtime/runtime-hooks.md) - [Plugin System](/plugin/dev/index.md) - [Retry Plugin](/plugin/plugins/retry-plugin.md) - [Error Load Remote Solutions](/blog/error-load-remote.md) --- url: /guide/runtime/runtime-hooks.md --- # Runtime Hooks The Module Federation runtime exposes hooks at key stages โ€” loading remote modules, initializing containers, registering and resolving shared dependencies, and more. They are useful for diagnostics and tracing, error fallbacks, rewriting shared dependencies, customizing resource loading, and similar scenarios. ## Hook return values For `SyncHook` and `AsyncHook`, returning `undefined` means the plugin only observes the event. It does not clear a value returned by an earlier plugin. A later plugin can still replace that value by returning another non-`undefined` value. For `SyncWaterfallHook` and `AsyncWaterfallHook`, return the full updated args object when you need to change the payload. Returning `undefined` keeps the current args and passes them to the next plugin. ## beforeInit `SyncWaterfallHook` Updates the corresponding init configuration before the MF instance is initialized. ```ts function beforeInit(args: BeforeInitOptions): BeforeInitOptions; type BeforeInitOptions = { userOptions: UserOptions; options: ModuleFederationRuntimeOptions; origin: ModuleFederation; shareInfo: ShareInfos; }; interface ModuleFederationRuntimeOptions { id?: string; name: string; version?: string; remotes: Array; shared: ShareInfos; plugins: Array; inBrowser: boolean; } ``` ## init `SyncHook` Called after the MF instance is initialized. ```ts function init(args: InitOptions): void; type InitOptions = { options: ModuleFederationRuntimeOptions; origin: ModuleFederation; }; ``` ## beforeRequest `AsyncWaterfallHook` Called before resolving the remote path, useful for updating something before lookup. ```ts async function beforeRequest( args: BeforeRequestOptions, ): Promise; type BeforeRequestOptions = { id: string; options: ModuleFederationRuntimeOptions; origin: ModuleFederation; }; ``` ## afterMatchRemote `AsyncHook` Called after the runtime matches a `loadRemote` request to a configured remote. If matching fails, the hook is still called with `error`. It is useful for diagnostics, request tracing, and checking whether a failure happened before manifest or remoteEntry loading. ```ts async function afterMatchRemote(args: AfterMatchRemoteOptions): Promise; type AfterMatchRemoteOptions = { id: string; options: ModuleFederationRuntimeOptions; remote?: Remote; expose?: string; remoteInfo?: RemoteInfo; error?: unknown; origin: ModuleFederation; }; ``` ## afterResolve `AsyncWaterfallHook` Called after resolving the remote path, allowing modification of the resolved content. ```ts async function afterResolve( args: AfterResolveOptions, ): Promise; type AfterResolveOptions = { id: string; pkgNameOrAlias: string; expose: string; remote: Remote; options: ModuleFederationRuntimeOptions; origin: ModuleFederation; remoteInfo: RemoteInfo; remoteSnapshot?: ModuleInfo; }; ``` ## onLoad `AsyncHook` Triggered after a remote is loaded, allowing access to and modification of the loaded file's exposed exports. ```ts async function onLoad(args: OnLoadOptions): Promise; type OnLoadOptions = { id: string; expose: string; pkgNameOrAlias: string; remote: Remote; options: ModuleOptions; origin: ModuleFederation; exposeModule: any; exposeModuleFactory: any; moduleInstance: Module; }; type ModuleOptions = { remoteInfo: RemoteInfo; host: ModuleFederation; }; interface RemoteInfo { name: string; version?: string; buildVersion?: string; entry: string; type: RemoteEntryType; entryGlobalName: string; shareScope: string; } ``` ## afterLoadRemote `AsyncHook` Called after a `loadRemote` request finishes. It runs for successful loads, failed loads, and loads recovered by `errorLoadRemote`. Use it to record the final remote loading status. `onLoad` runs before `afterLoadRemote` on a successful load. If a load fails and `errorLoadRemote` returns a fallback, `afterLoadRemote` receives the original `error` and `recovered: true`. ```ts async function afterLoadRemote(args: AfterLoadRemoteOptions): Promise; type AfterLoadRemoteOptions = { id: string; expose?: string; remote?: RemoteInfo; options?: { loadFactory?: boolean; from?: 'build' | 'runtime'; }; error?: unknown; recovered?: boolean; origin: ModuleFederation; }; ``` ## beforeInitContainer `AsyncWaterfallHook` Triggered before the host calls `remoteEntry.init(...)`. Useful for dynamically rewriting the `shareScope` / `initScope` used in this initialization and the `remoteEntryInitOptions` passed to the remote. ```ts async function beforeInitContainer( args: BeforeInitContainerOptions, ): Promise; type BeforeInitContainerOptions = { shareScope: ShareScopeMap[string]; initScope: InitScope; remoteEntryInitOptions: RemoteEntryInitOptions; remoteInfo: RemoteInfo; origin: ModuleFederation; }; ``` ## initContainer `AsyncWaterfallHook` Triggered after `remoteEntry.init(...)` completes successfully. Useful for metrics, observability, or post-initialization customization. ```ts async function initContainer( args: InitContainerOptions, ): Promise; type InitContainerOptions = { shareScope: ShareScopeMap[string]; initScope: InitScope; remoteEntryInitOptions: RemoteEntryInitOptions; remoteInfo: RemoteInfo; remoteEntryExports: RemoteEntryExports; origin: ModuleFederation; id?: string; remoteSnapshot?: ModuleInfo; }; ``` ## handlePreloadModule `SyncHook` Handles the preloading logic for remotes. ```ts function handlePreloadModule(args: HandlePreloadModuleOptions): void; type HandlePreloadModuleOptions = { id: string; name: string; remote: Remote; remoteSnapshot: ModuleInfo; preloadConfig: PreloadRemoteArgs; origin: ModuleFederation; }; ``` ## errorLoadRemote `AsyncHook` Called if loading remotes fails, enabling custom error handling. Can return a custom fallback logic. ```ts async function errorLoadRemote( args: ErrorLoadRemoteOptions, ): Promise; type ErrorLoadRemoteOptions = { id: string; error: unknown; options?: any; from: 'build' | 'runtime'; lifecycle: 'beforeRequest' | 'beforeLoadShare' | 'afterResolve' | 'onLoad'; remote?: RemoteInfo; expose?: string; origin: ModuleFederation; }; ``` The `lifecycle` parameter indicates the stage where the error occurred: - `beforeRequest`: Error during initial request processing - `afterResolve`: Error during manifest loading (most common for network failures) - `onLoad`: Error during module loading and execution - `beforeLoadShare`: Error while loading a remoteEntry during shared dependency initialization If this hook returns a fallback module, the runtime uses that value to continue. If a diagnostics or logging plugin returns `undefined`, it does not clear a fallback returned by another plugin. ```ts import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; const fallbackPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'fallback-plugin', errorLoadRemote(args) { const { lifecycle, id, error } = args; if (error) { console.warn(\`Failed to load remote \${id} at \${lifecycle}:\`, error?.message || error); } switch (lifecycle) { case 'afterResolve': return { id: id || 'fallback', name: id || 'fallback', metaData: { /* fallback manifest */ }, shared: [], remotes: [], exposes: [] }; case 'beforeRequest': console.warn(\`Request processing failed for \${id}\`); return void 0; case 'onLoad': return () => ({ __esModule: true, default: () => 'Fallback Component' }); case 'beforeLoadShare': console.warn(\`Shared dependency loading failed for \${id}\`); return () => ({ __esModule: true, default: {} }); default: console.warn(\`Unknown lifecycle \${lifecycle} for \${id}\`); return void 0; } }, }; }; const mf = createInstance({ name: 'mf_host', remotes: [ { name: "remote", alias: "app1", entry: "http://localhost:2001/mf-manifest.json" } ], plugins: [fallbackPlugin()] }); mf.loadRemote('app1/un-existed-module').then(mod=>{ expect(mod).toEqual('fallback'); }); ``` ## afterRegisterShare `SyncHook` Called after the runtime attempts to register one version of a shared dependency in a share scope. This hook reports both the candidate and the record that actually remains registered, so it can also observe conflicts where an existing provider wins. ```ts function afterRegisterShare(args: AfterRegisterShareOptions): void; type AfterRegisterShareOptions = { pkgName: string; scope: string; shared: Shared; previousShared?: Shared; registeredShared?: Shared; shareScopeMap: ShareScopeMap; trigger: 'build' | 'runtime' | 'container-init'; origin: ModuleFederation; }; ``` - `shared` is the candidate the runtime tried to register. - `previousShared` is the record at the same scope, package, and version before this attempt. - `registeredShared` is the record that remains after conflict resolution. - `trigger` identifies whether registration came from build-generated runtime code, a runtime API call, or remote container initialization. ## beforeLoadShare `AsyncWaterfallHook` Called before loading shared, can be used to modify the corresponding shared configuration. ```ts async function beforeLoadShare( args: BeforeLoadShareOptions, ): Promise; type BeforeLoadShareOptions = { pkgName: string; shareInfo?: Shared; shared: Options['shared']; origin: ModuleFederation; loadContext?: SharedLoadContext; }; ``` `loadContext` carries the consumer that triggered shared resolution when that information is available: ```ts type SharedLoadContext = { trigger?: 'build' | 'runtime' | 'container-init'; moduleId?: string | number; chunkId?: string | number; }; ``` Because `beforeLoadShare` is a waterfall hook, a plugin can return an updated `loadContext`; the same value is then forwarded to `resolveShare`, `afterLoadShare`, and `errorLoadShare`. ## afterLoadShare `SyncHook` Called after a shared dependency is successfully resolved by `loadShare` or `loadShareSync`. Use it to observe which provider and version were selected. ```ts function afterLoadShare(args: AfterLoadShareOptions): void; type AfterLoadShareOptions = { pkgName: string; shareInfo?: Partial; selectedShared?: Partial; shared: Options['shared']; shareScopeMap: ShareScopeMap; lifecycle: 'loadShare' | 'loadShareSync'; loadContext?: SharedLoadContext; origin: ModuleFederation; }; ``` ## errorLoadShare `SyncHook` Called when shared dependency resolution fails or cannot select a valid shared dependency. It is useful for diagnostics around missing shared dependencies, version mismatch, and eager configuration errors. ```ts function errorLoadShare(args: ErrorLoadShareOptions): void; type ErrorLoadShareOptions = { pkgName: string; shareInfo?: Partial; shared: Options['shared']; shareScopeMap: ShareScopeMap; lifecycle: 'loadShare' | 'loadShareSync'; origin: ModuleFederation; error?: unknown; recovered?: boolean; loadContext?: SharedLoadContext; }; ``` ## initContainerShareScopeMap `SyncWaterfallHook` Triggered when the host aligns a remoteโ€™s share-scope mapping. Useful for redirecting one scope to another (for example, aliasing `scope1` to `default` so they share the same pool). ```ts function initContainerShareScopeMap( args: InitContainerShareScopeMapOptions, ): InitContainerShareScopeMapOptions; type InitContainerShareScopeMapOptions = { shareScope: ShareScopeMap[string]; options: Options; origin: ModuleFederation; scopeName: string; hostShareScopeMap?: ShareScopeMap; }; ``` ## resolveShare `SyncWaterfallHook` Allows overriding the final shared module selection result. `resolveShare` runs after the runtime has already picked the candidate scope and version. At this point, changing fields like `scope` or `version` on `args` does not change the final result by itself. To switch to a different shared module, replace `args.resolver` and return the shared record you want to use. ```ts function resolveShare(args: ResolveShareOptions): ResolveShareOptions; type ResolveShareOptions = { shareScopeMap: ShareScopeMap; scope: string; pkgName: string; version: string; shareInfo: Shared; GlobalFederation: Federation; resolver: () => | { shared: Shared; useTreesShaking: boolean; } | undefined; loadContext?: SharedLoadContext; }; ``` ```ts import { createInstance, loadRemote, } from '@module-federation/enhanced/runtime'; import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; const customSharedPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'custom-shared-plugin', resolveShare(args) { const { pkgName, shareScopeMap } = args; if (pkgName !== 'react') { return args; } const fallbackShared = shareScopeMap.default?.react?.['17.0.0']; if (!fallbackShared) { return args; } args.resolver = function () { return { shared: fallbackShared, useTreesShaking: false, }; }; return args; }, }; }; const mf = createInstance({ name: 'mf_host', shared: { react: { version: '17.0.0', scope: 'default', lib: () => React, shareConfig: { singleton: true, requiredVersion: '^17.0.0', }, }, }, plugins: [customSharedPlugin()], }); mf.loadShare('react').then((reactFactory) => { expect(reactFactory()).toEqual(React); }); ``` ## beforePreloadRemote `AsyncHook` Called before the preload handler executes any preload logic. It runs for both explicit `preloadRemote` calls and snapshot-driven automatic preloading during `loadRemote`. Mutate `args.preloadOps` in place to adjust the configuration used to generate preload assets. ```ts async function beforePreloadRemote( args: BeforePreloadRemoteOptions, ): Promise; type BeforePreloadRemoteOptions = { preloadOps: Array; options: Options; origin: ModuleFederation; }; ``` ## generatePreloadAssets `AsyncHook` Used to control the generation of resources that need to be preloaded. ```ts async function generatePreloadAssets( args: GeneratePreloadAssetsOptions, ): Promise; type GeneratePreloadAssetsOptions = { origin: ModuleFederation; preloadOptions: PreloadOptions[number]; remote: Remote; remoteInfo: RemoteInfo; remoteSnapshot: ModuleInfo; globalSnapshot: GlobalModuleInfo; }; interface PreloadAssets { cssAssets: Array; jsAssetsWithoutEntry: Array; entryAssets: Array; } ``` ## beforeLoadManifest `AsyncHook` Called immediately before the runtime requests and parses a remote manifest. It is not called when the manifest is already in the runtime cache. ```ts async function beforeLoadManifest( args: BeforeLoadManifestOptions, ): Promise; type BeforeLoadManifestOptions = { manifestUrl: string; moduleInfo: Remote; resourceOptions?: { initiator: 'loadRemote' | 'preloadRemote' | 'loadShare'; id: string; }; origin: ModuleFederation; }; ``` ## afterLoadManifest `AsyncHook` Called after manifest loading finishes. It runs for network success, cache hits, validation failures, and values recovered through `errorLoadRemote`. ```ts async function afterLoadManifest( args: AfterLoadManifestOptions, ): Promise; type AfterLoadManifestOptions = { manifestUrl: string; moduleInfo: Remote; resourceOptions?: { initiator: 'loadRemote' | 'preloadRemote' | 'loadShare'; id: string; }; manifestJson?: Manifest; response?: Response; error?: unknown; cached?: boolean; recovered?: boolean; origin: ModuleFederation; }; ``` - `cached` is `true` when no network request was needed. - `error` preserves the original loading or validation error. - `recovered` is `true` when `errorLoadRemote` supplied a usable manifest. ## loaderHook `loaderHook` is used to intercept resource loading and module-factory resolution. ## beforeInitRemote `AsyncHook` Called before initializing the remote container with `remoteEntry.init(...)`. ```ts async function beforeInitRemote(args: BeforeInitRemoteOptions): Promise; type BeforeInitRemoteOptions = { id?: string; remoteInfo: RemoteInfo; remoteSnapshot?: ModuleInfo; origin: ModuleFederation; }; ``` ## afterInitRemote `AsyncHook` Called after remote container initialization succeeds or fails. When the remote was already initialized, `cached` is set to `true`. ```ts async function afterInitRemote(args: AfterInitRemoteOptions): Promise; type AfterInitRemoteOptions = { id?: string; remoteInfo: RemoteInfo; remoteSnapshot?: ModuleInfo; remoteEntryExports?: RemoteEntryExports; error?: unknown; cached?: boolean; origin: ModuleFederation; }; ``` ## beforeGetExpose `AsyncHook` Called before `remoteEntry.get(expose)`. ```ts async function beforeGetExpose(args: BeforeGetExposeOptions): Promise; type BeforeGetExposeOptions = { id: string; expose: string; moduleInfo: RemoteInfo; remoteEntryExports: RemoteEntryExports; origin: ModuleFederation; }; ``` ## afterGetExpose `AsyncHook` Called after `remoteEntry.get(expose)` succeeds or fails. ```ts async function afterGetExpose(args: AfterGetExposeOptions): Promise; type AfterGetExposeOptions = { id: string; expose: string; moduleInfo: RemoteInfo; remoteEntryExports: RemoteEntryExports; moduleFactory?: () => unknown | Promise; error?: unknown; origin: ModuleFederation; }; ``` ## beforeExecuteFactory `AsyncHook` Called before executing the exposed module factory. It is skipped when `loadRemote` is called with `loadFactory: false`. ```ts async function beforeExecuteFactory( args: BeforeExecuteFactoryOptions, ): Promise; type BeforeExecuteFactoryOptions = { id: string; expose: string; moduleInfo: RemoteInfo; loadFactory: boolean; origin: ModuleFederation; }; ``` ## afterExecuteFactory `AsyncHook` Called after the exposed module factory succeeds or fails. It is skipped when `loadRemote` is called with `loadFactory: false`. ```ts async function afterExecuteFactory( args: AfterExecuteFactoryOptions, ): Promise; type AfterExecuteFactoryOptions = { id: string; expose: string; moduleInfo: RemoteInfo; loadFactory: boolean; exposeModule?: unknown; error?: unknown; origin: ModuleFederation; }; ``` ## createScript `SyncHook` Used to modify the script when loading resources. ```ts function createScript(args: CreateScriptOptions): CreateScriptHookReturn; type CreateScriptOptions = { url: string; attrs?: Record; remoteInfo?: RemoteInfo; resourceContext?: ResourceLoadContext; }; type CreateScriptHookReturn = | HTMLScriptElement | { script?: HTMLScriptElement; timeout?: number } | void; type ResourceLoadContext = { initiator: 'loadRemote' | 'preloadRemote' | 'loadShare'; id: string; resourceType: 'manifest' | 'remoteEntry' | 'js' | 'css'; url?: string; expose?: string; }; ``` `timeout` is measured in milliseconds and controls the script loading timeout. The default value is `20000`. `resourceContext` tells whether this resource load came from `loadRemote`, `preloadRemote`, or shared initialization, plus the resource type, id, and exposed module when applicable. You can combine `timeout` and `resourceContext` to set different timeouts for remoteEntry loads and preload loads. ```ts import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; const changeScriptAttributePlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'change-script-attribute', createScript({ url }) { if (url === 'http://localhost:3001/remoteEntry.js') { let script = document.createElement('script'); script.src = url; script.setAttribute('loader-hooks', 'isTrue'); script.setAttribute('crossorigin', 'anonymous'); return { script, timeout: 30000, }; } }, }; }; ``` ## fetch `AsyncHook` The `fetch` function allows customizing the request that fetches the manifest JSON. A successful `Response` must yield a valid JSON. ```typescript function fetch( manifestUrl: string, requestInit: RequestInit, remoteInfo?: RemoteInfo, resourceContext?: ResourceLoadContext, ): Promise | void | false; ``` `resourceContext` can tell whether this manifest request came from `loadRemote`, `preloadRemote`, or shared initialization. - Example for including the credentials when fetching the manifest JSON: ```ts import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; // fetch-manifest-with-credentials-plugin.ts export default function (): FederationRuntimePlugin { return { name: 'fetch-manifest-with-credentials-plugin', fetch(manifestUrl, requestInit) { return fetch(manifestUrl, { ...requestInit, credentials: 'include', }); }, }; } ``` ## createLink `SyncHook` Used to customize the link element created for preload/style loading. ```ts function createLink(args: CreateLinkOptions): HTMLLinkElement | void; type CreateLinkOptions = { url: string; attrs?: Record; remoteInfo?: RemoteInfo; resourceContext?: ResourceLoadContext; }; type CreateLinkHookReturn = | HTMLLinkElement | { link?: HTMLLinkElement; timeout?: number } | void; ``` `resourceContext` has the same shape as in `createScript`. It can distinguish preloaded JS/CSS, actual remoteEntry loading, and the related resource id. `timeout` is measured in milliseconds and controls the link loading timeout. The default value is `20000`. Example: use a shorter timeout for low-priority preload resources, while keeping normal remote loading more tolerant. ```ts import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; const resourceTimeoutPlugin = (): ModuleFederationRuntimePlugin => ({ name: 'resource-timeout-plugin', createScript({ resourceContext }) { if ( resourceContext?.initiator === 'loadRemote' && resourceContext.resourceType === 'remoteEntry' ) { return { timeout: 30000, }; } }, createLink({ resourceContext }) { if (resourceContext?.initiator === 'preloadRemote') { return { timeout: 5000, }; } }, }); ``` ## loadEntryError `AsyncHook` Triggered when loading remoteEntry fails (typically script loading errors). Useful for retries or custom fallback strategies. ```ts async function loadEntryError( args: LoadEntryErrorOptions, ): Promise | undefined>; type LoadEntryErrorOptions = { getRemoteEntry: typeof getRemoteEntry; origin: ModuleFederation; remoteInfo: RemoteInfo; remoteEntryExports?: RemoteEntryExports; globalLoading: Record | undefined>; uniqueKey: string; }; ``` ## afterLoadEntry `AsyncHook` Called after loading a remoteEntry succeeds, fails, or is recovered by `loadEntryError`. ```ts async function afterLoadEntry(args: AfterLoadEntryOptions): Promise; type AfterLoadEntryOptions = { origin: ModuleFederation; remoteInfo: RemoteInfo; remoteEntryExports?: RemoteEntryExports | false | void; resourceContext?: ResourceLoadContext; cached?: boolean; error?: unknown; recovered?: boolean; }; ``` `resourceContext` identifies the request that needed the remoteEntry. `cached` is `true` when the exports were already available, while `recovered` means `loadEntryError` returned replacement exports after a loading error. ## getModuleFactory `AsyncHook` Triggered before calling `remoteEntry.get(expose)`, allowing custom module-factory resolution. ```ts async function getModuleFactory( args: GetModuleFactoryOptions, ): Promise<(() => Promise) | undefined>; type GetModuleFactoryOptions = { remoteEntryExports: RemoteEntryExports; expose: string; moduleInfo: RemoteInfo; }; ``` ## loadEntry `asyncHook` The `loadEntry` function allows for full customization of remotes, enabling you to extend and create new remote types. The following two simple examples demonstrate loading JSON data and module delegation. ```typescript function loadEntry( args: LoadEntryOptions, ): Promise | RemoteEntryExports | void; type LoadEntryOptions = { origin: ModuleFederation; loaderHook: ModuleFederation['loaderHook']; remoteEntryExports?: RemoteEntryExports; remoteInfo: RemoteInfo; resourceContext?: ResourceLoadContext; }; interface RemoteInfo { name: string; version?: string; buildVersion?: string; entry: string; type: RemoteEntryType; entryGlobalName: string; shareScope: string; } export type RemoteEntryExports = { get: (id: string) => () => Promise; init: ( shareScope: ShareScopeMap[string], initScope?: InitScope, remoteEntryInitOPtions?: RemoteEntryInitOptions, ) => void | Promise; }; ``` - Example Loading JSON Data ```ts import { init } from '@module-federation/enhanced/runtime'; import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; // load-json-data-plugin.ts const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { return { name: 'load-json-data-plugin', loadEntry({ remoteInfo }) { if (remoteInfo.jsonA === 'jsonA') { return { init(shareScope, initScope, remoteEntryInitOPtions) {}, async get(path) { const json = await fetch(remoteInfo.entry + '.json').then((res) => res.json(), ); return () => ({ path, json, }); }, }; } }, }; }; ``` ```ts // module-federation-config { remotes: { jsonA: "jsonA@https://cdn.jsdelivr.net/npm/@module-federation/runtime/package"; } } ``` ```ts // src/bootstrap.js import jsonA from 'jsonA'; jsonA; // {...json data} ``` - Example Delegate Modules ```ts import { init } from '@module-federation/enhanced/runtime'; import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; // delegate-modules-plugin.ts const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { return { name: 'delegate-modules-plugin', loadEntry({ remoteInfo }) { if (remoteInfo.name === 'delegateModulesA') { return { init(shareScope, initScope, remoteEntryInitOPtions) {}, async get(path) { path = path.replace('./', ''); const { [path]: factory } = await import('./delegateModulesA.js'); const result = await factory(); return () => result; }, }; } }, }; }; ``` ```ts // ./src/delegateModulesA.js export async function test1() { return new Promise((resolve) => { setTimeout(() => { resolve('test1 value'); }, 3000); }); } export async function test2() { return new Promise((resolve) => { setTimeout(() => { resolve('test2 value'); }, 3000); }); } ``` ```ts // module-federation-config { remotes: { delegateModulesA: 'delegateModulesA@https://delegateModulesA.js'; } } ``` ```ts // src/bootstrap.js import test1 from 'delegateModulesA/test1'; import test2 from 'delegateModulesA/test2'; test1; // "test1 value" test2; // "test2 value" ``` ## bridgeHook `bridgeHook` observes React and Vue Bridge render, destroy, and route synchronization operations. Bridge packages pass a semantic operation context as a second argument while keeping the first `info` argument compatible with existing plugins. ```ts import type { BridgeOperationContext, BridgeOperationResult, } from '@module-federation/bridge-shared'; type BridgeOperationContext = { side: 'consumer' | 'producer'; framework: 'react' | 'vue'; operation: 'render' | 'update' | 'destroy' | 'route-sync'; moduleName?: string; route?: { action: 'memory-route-init' | 'host-to-remote'; from?: string; to?: string; basename?: string; mechanism?: 'popstate'; }; reason?: 'unmount' | 'direct'; }; type BridgeOperationResult = { context: BridgeOperationContext; result?: unknown; }; ``` ## beforeBridgeRender `SyncHook` Triggered before a Bridge producer or consumer renders or updates. You can return an object to augment render arguments (for example, `extraProps`). ```ts function beforeBridgeRender( info: Record, context?: BridgeOperationContext, ): void | Record; ``` ## afterBridgeRender `SyncHook` Triggered after bridge rendering. ```ts function afterBridgeRender( info: Record, operation?: BridgeOperationResult, ): void | Record; ``` ## beforeBridgeDestroy `SyncHook` Triggered before bridge teardown. ```ts function beforeBridgeDestroy( info: Record, context?: BridgeOperationContext, ): void | Record; ``` ## afterBridgeDestroy `SyncHook` Triggered after bridge teardown. ```ts function afterBridgeDestroy( info: Record, operation?: BridgeOperationResult, ): void | Record; ``` ## afterBridgeRouteSync `SyncHook` Triggered after Bridge synchronizes a route. React and Vue consumers report host-to-remote `popstate` synchronization, while a Vue producer also reports initial memory-route navigation. `operation.result` contains the router result when the framework provides one. ```ts function afterBridgeRouteSync(operation: BridgeOperationResult): void; ``` ```ts import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; const bridgeTracePlugin = (): ModuleFederationRuntimePlugin => ({ name: 'bridge-trace-plugin', afterBridgeRender(_info, operation) { console.log('bridge render', operation?.context); }, afterBridgeRouteSync(operation) { console.log('bridge route', operation.context.route); }, }); ``` --- url: /guide/bridge/overview.md --- # Bridge Overview `Bridge` is a cross-framework application-level module loading and integration solution provided by Module Federation. It's not just a component loader, but a complete lifecycle management system specifically designed for handling "application-level modules." The core problem Bridge solves is: how to enable complete applications developed with different frontend frameworks to be dynamically loaded, rendered, and integrated as modules by other applications, while maintaining their respective technology stack independence and business integrity. Unlike traditional component-level sharing, Bridge handles complete application modules that include routing systems and business logic. Through Bridge, you can: - **Cross-framework Integration**: Load Vue applications in React applications, load React applications in Vue applications, achieving true technology-agnostic integration - **Application-level Modularity**: Export and consume complete applications (including routing and business logic) as modules - **Component-level Lazy Loading**: Support fine-grained component-level loading for on-demand loading and performance optimization - **Routing System Collaboration**: Support complex nested routing scenarios, ensuring seamless collaboration between host and remote application routing - **Lifecycle Management**: Automatically handle mounting, unmounting, and updating of remote applications, ensuring proper resource management - **Application Isolation**: Ensure styles, scripts, and contexts between different applications are isolated from each other to avoid conflicts - **Progressive Migration**: Support gradual splitting of monolithic applications into micro-frontend architectures, reducing migration risks These capabilities make Bridge the core infrastructure for building modern, scalable micro-frontend architectures. ## Supported Frameworks ### React Bridge (`@module-federation/bridge-react`) A Bridge toolkit designed specifically for React applications, supporting all versions from React 16-19 and compatible with mainstream React ecosystem tools. #### Core Features - **Version Compatibility**: Supports all versions from React 16.x to 19.x, automatically handling compatibility issues between different versions - **Routing Integration**: Deep integration with React Router, supporting v5, v6, v7 versions, automatically handling basename and nested routing - **Framework Isolation**: Ensures React contexts and component trees between different applications are isolated from each other - **Performance Optimization**: Built-in lazy loading, code splitting, and resource preloading mechanisms #### Core APIs **`createBridgeComponent`** - Purpose: Wraps React applications as remotely loadable modules - Use case: When you want to export a React application as a module for use by other applications - Features: Automatically handles application lifecycle, routing configuration, and error boundaries **`createRemoteAppComponent`** - Purpose: Loads and renders remote React applications in host applications - Features: Automatically creates rendering context, handles routing integration and lifecycle management - Advantages: Supports error recovery, loading state management, and performance monitoring **`createLazyComponent`** - Purpose: Component-level lazy loading, requires use with runtime plugins - Advantages: More fine-grained loading control and performance optimization - Scenarios: Suitable for on-demand loading of large component libraries or complex business components #### Practice Guides - [React practice](/integrations/practice/react/index.md) - [Vue Bridge practice](/integrations/practice/vue.md) ### Vue Bridge (`@module-federation/bridge-vue3`) A Bridge toolkit designed specifically for Vue 3 applications, making full use of Vue 3's modern features. #### Core Features - **Vue 3 Deep Integration**: Full support for Composition API, reactivity system, Teleport and other new features - **Routing System**: Deep integration with Vue Router 4, supporting dynamic routes and nested routes - **Application Isolation**: Ensures Vue instances and reactivity systems between different applications are isolated from each other - **TypeScript Support**: Provides complete TypeScript type definitions and inference - **Development Experience**: Supports Vue DevTools and hot module replacement #### Core APIs **`createBridgeComponent`** - Purpose: Wraps Vue 3 applications as remotely loadable modules - Support: Complete Vue 3 ecosystem integration, including plugin system and custom directives - Features: Automatically handles application instance creation, mounting, and destruction **`createRemoteAppComponent`** - Purpose: Loads application modules from other frameworks in Vue applications - Features: Automatically handles Vue 3's Composition API and application instance management - Advantages: Supports cross-framework DOM rendering and event handling ## Common Questions ### Why Bridge? Bridge addresses several key challenges in modern frontend development: 1. **Technology Stack Diversity Management** - Different teams in large organizations often use different frontend frameworks - Bridge enables these teams to develop independently while maintaining interoperability between applications - Avoids migration costs and development efficiency issues caused by forcing technology stack unification 2. **Application-level Modularity** - Traditional module federation mainly handles component-level sharing - Bridge supports application-level modularity, including complete business logic and user experience - Achieves coarser-grained but more manageable micro-frontend architecture 3. **Routing System Collaboration** - Supports loading application modules with complex routing - Ensures host and remote application routing can work together - Supports advanced features like nested routes, dynamic routes, and route guards 4. **Progressive Architecture Evolution** - Supports gradual splitting of existing monolithic applications into micro-frontend architectures - Reduces risks and costs of large-scale refactoring - Provides smooth migration paths ### Bridge vs Traditional Component Sharing | Feature | Traditional Component Sharing | Bridge Application Sharing | | ------------------------ | ----------------------------- | -------------------------- | | Granularity | Component level | Application level | | Routing | Not supported | Full support | | Application Isolation | Limited support | Complete isolation | | Business Integrity | Fragmented | Complete business units | | Development Independence | Medium | Highly independent | | Deployment Strategy | Dependent on host | Independent deployment | ### Component-level Loading Deep Dive In addition to application-level modularity, Bridge also supports component-level lazy loading through the `createLazyComponent` API, providing more fine-grained loading control. #### Use Cases **1. Large Component Libraries** - Load complex business components on demand, reducing initial bundle size - Support component-level version management and independent updates - Suitable for design systems and shared component library scenarios **2. Conditional Feature Modules** - Dynamically load feature modules based on user permissions or business scenarios - Support A/B testing and feature flags - Reduce core application complexity **3. Performance Optimization** - Defer loading of non-critical path components - Support preloading and intelligent caching strategies - Work with Intersection Observer for viewport-based loading #### Core Features - **Data Prefetching**: Support component-level data prefetching, ensuring data is ready when components load - **Error Boundaries**: Built-in error handling mechanisms, component loading failures don't affect the main application - **Loading States**: Rich loading state management with support for custom loading indicators - **Caching Mechanism**: Intelligent caching of loaded components, avoiding duplicate network requests - **Type Safety**: Complete TypeScript support, ensuring type safety of component interfaces #### Integration with Application-level Loading Component-level loading and application-level loading work perfectly together: ```tsx import { getInstance } from '@module-federation/runtime'; import { lazyLoadComponentPlugin } from '@module-federation/bridge-react/data-fetch'; // Application-level loading - Load complete remote application const RemoteApp = createRemoteAppComponent({ loader: () => import('remote/app'), fallback: , }); const instance = getInstance(); instance.registerPlugins([lazyLoadComponentPlugin()]); // Component-level loading - Load components on demand within remote applications const LazyComponent = instance.createLazyComponent({ loader: () => import('remote/heavy-component'), loading: , fallback: ({ error }) => , }); ``` This layered loading strategy allows you to: - Achieve decoupling and independent deployment between applications at a coarse granularity - Optimize performance and user experience at a fine granularity - Flexibly choose appropriate loading granularity based on business needs ### Performance Considerations **Loading Performance** - Bridge supports application-level lazy loading and preloading - Built-in resource caching and version management mechanisms - Supports progressive loading strategies **Runtime Performance** - Framework isolation ensures different applications don't affect each other - Supports application-level performance monitoring and optimization - Automated memory management and resource cleanup **Network Optimization** - Supports resource sharing and deduplication - Built-in CDN-friendly resource loading strategies - Supports offline caching and server-side rendering ### How to extend to other frameworks? Currently, Module Federation officially provides Bridge implementations for React and Vue 3. If you need support for other frameworks: 1. **Submit Requirements**: Tell us your needs through [GitHub Issues](https://github.com/module-federation/core/issues) 2. **Reference Implementation**: Check existing [Bridge implementations](https://github.com/module-federation/core/blob/main/packages/bridge/bridge-react) to understand how to develop 3. **Contribute Code**: Welcome to contribute new framework support to the community ### Bridge Implementation Principles The core idea of Bridge is simple: DOM-based rendering abstraction. Here's the simplified implementation principle: #### Export Side (Remote Application) ```tsx export default function createProvider() { const rootMap = new Map(); return { // Render application to specified DOM node render(info: { dom: HTMLElement; basename?: string; memoryRoute?: { entryPath: string }; }) { const root = ReactDOM.createRoot(info.dom); rootMap.set(info.dom, root); root.render( , ); }, // Clean up resources destroy(info: { dom: HTMLElement }) { const root = rootMap.get(info.dom); root?.unmount(); rootMap.delete(info.dom); }, }; } ``` #### Consumer Side (Host Application) ```tsx const RemoteApp = React.lazy(async () => { // Load remote module const module = await loadRemote('remote1/export-app'); const provider = module.default; return { default: () => { const containerRef = useRef(null); const providerRef = useRef(null); useEffect(() => { if (containerRef.current) { // Create provider instance const instance = provider(); providerRef.current = instance; // Render remote application instance.render({ dom: containerRef.current, basename: '/remote-app', }); } // Cleanup function return () => { if (providerRef.current && containerRef.current) { providerRef.current.destroy({ dom: containerRef.current, }); } }; }, []); return
; }, }; }); function App() { return ( Loading...
}> ); } ``` Through this pattern, Bridge achieves framework-agnostic application-level module loading, providing a solid technical foundation for micro-frontend architectures. --- url: /guide/bridge/react/getting-started.md --- # React Bridge Overview `@module-federation/bridge-react` is a powerful micro-frontend solution designed specifically for React applications. By providing cross-framework rendering and routing coordination capabilities, you can easily export and load React applications or modules as standalone applications. ## Core Features ### Basic Capabilities - **๐Ÿš€ Application-level Module Export**: Use `createBridgeComponent` to wrap complete React applications as remotely loadable modules - **๐Ÿ“ฆ Application-level Module Loading**: Seamlessly integrate remote React applications in host applications through `createRemoteAppComponent` - **๐ŸŽฏ Component-level Lazy Loading**: Support `createLazyComponent` for fine-grained component-level lazy loading ### Features - **๐Ÿ”„ Cross-framework Rendering**: Support cross-framework rendering between different frameworks (React, Vue, Angular, etc.) - **๐Ÿ›ฃ๏ธ Routing Coordination**: Automatically handle React Router basename injection and routing context passing - **๐Ÿ”— Version Compatibility**: Full support for React 16-19, automatically handling rendering differences and lifecycle across versions - **โšก Data Prefetch**: Built-in resource data prefetch mechanism, supporting application, component, and dependency prefetch optimization ### Why This Matters? - **Progressive Upgrade**: Allow different teams to upgrade React versions at their own pace - **Zero-disruption Integration**: New and legacy applications can work together seamlessly without synchronized upgrades - **Technical Debt Management**: Avoid integration issues caused by version inconsistencies - **Team Autonomy**: Each team can independently choose the most suitable React version ## Key API Overview ### Producer API `createBridgeComponent`: wraps a React application as a Bridge component (application component) so consumers can load it remotely. Choose the import path that matches the React version used by the producer project: | React Version | Import Path | | ------------- | --------------------------------------- | | React 19 | `@module-federation/bridge-react/v19` | | React 18 | `@module-federation/bridge-react/v18` | | React 16 / 17 | `@module-federation/bridge-react/react` | :::warning Important `@module-federation/bridge-react/v18` and `/v19` import `createRoot` from `react-dom/client`. If you only configure `"react-dom"` as a shared dependency, `react-dom/client` will not be shared and can cause React version mismatch errors. Make sure to use the trailing-slash prefix form: ```ts shared: { 'react-dom/': { singleton: true, }, } ``` ::: ### Consumer API | API | Description | Import Path | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------- | | `createRemoteAppComponent` | Loads and renders a remote Bridge component in the host, automatically handling rendering context and router integration | `@module-federation/bridge-react/react` | | `lazyLoadComponentPlugin` | Prefetch and lazy-loading plugin | `@module-federation/bridge-react/react` | ### ๐Ÿ’ก Examples [Complete Examples](https://github.com/module-federation/core/tree/main/apps/router-demo) - Complete micro-frontend project containing multiple React applications --- url: /guide/bridge/react/export-app.md --- # Export Applications This chapter will introduce how to use `createBridgeComponent` to export your React application as a module that can be loaded remotely. ## Installation ```sh [npm] npm install @module-federation/bridge-react@latest ``` ```sh [yarn] yarn add @module-federation/bridge-react@latest ``` ```sh [pnpm] pnpm add @module-federation/bridge-react@latest ``` ## Basic Usage ### Step 1: Create Export Entry To export a React application, you need to create a dedicated export file and use `createBridgeComponent` to wrap the application as a remote module. Assuming your application entry is `App.tsx`, create a new file `export-app.tsx`: ```tsx // ./src/export-app.tsx import App from './App'; import { createBridgeComponent } from '@module-federation/bridge-react'; // Use createBridgeComponent to wrap App as a remote module and export export default createBridgeComponent({ rootComponent: App }); ``` ### Step 2: Configure `exposes` Export Next, you need to configure Module Federation in your build tool to expose the created export file for use by other applications. :::tip Build Tool Support The following example uses Rsbuild configuration. Please adjust according to your build tool: - **Rsbuild**: `@module-federation/rsbuild-plugin` - **Rspack**: `@module-federation/enhanced/rspack` - **Webpack**: `@module-federation/enhanced/webpack` - **Vite**: `@module-federation/vite` ::: ```ts // rsbuild.config.ts import { pluginModuleFederation } from '@module-federation/rsbuild-plugin'; export default { plugins: [ pluginModuleFederation({ name: 'remote1', exposes: { './export-app': './src/export-app.tsx', // Export application-type remote module }, }), ], }; ``` ## Bridge Router Configuration React Bridge provides powerful routing coordination capabilities that can dynamically inject basename and route context passing based on the current path. :::info Router Framework Support - Bridge Router currently supports routing proxy functionality for React Router v5, v6, and v7 versions. - It does not support other routing frameworks such as `@tanstack/react-router`. For applications using non-React Router frameworks, you need to explicitly disable `enableBridgeRouter` and handle routing logic yourself. ::: ### Enable Bridge Router ```ts // rsbuild.config.ts import { pluginModuleFederation } from '@module-federation/rsbuild-plugin'; export default { plugins: [ pluginModuleFederation({ name: 'remote1', exposes: { './export-app': './src/export-app.tsx', }, bridge: { // Enable Bridge Router routing capabilities, default is true enableBridgeRouter: true, } }), ], }; ``` ### Configuration Description - **`enableBridgeRouter: true`** (default) - Automatically handles basename and routing coordination, supports React Router v5, v6, v7 - **`enableBridgeRouter: false`** - Disables Bridge's default routing proxy capabilities, users need to handle routing integration manually :::warning Important When enabling Bridge Router, do not configure `react-router-dom` as a shared dependency, otherwise it will cause routing functionality issues. ::: ## createBridgeComponent API Reference :::warning Important `@module-federation/bridge-react/v18` and `/v19` import `createRoot` from `react-dom/client`. If you only configure `"react-dom"` as a shared dependency, `react-dom/client` will not be shared and can cause React version mismatch errors. Make sure to use the trailing-slash prefix form: ```ts shared: { 'react-dom/': { singleton: true, }, } ``` ::: ### Function Signature ```tsx function createBridgeComponent( bridgeInfo: Omit, 'createRoot'> ): () => { render(info: RenderParams): Promise; destroy(info: DestroyParams): void; } ``` ### ProviderFnParams\ Bridge component configuration parameters: ```tsx interface ProviderFnParams { // Root component rootComponent: React.ComponentType; // Custom render function (optional) render?: (App: React.ReactElement, id?: HTMLElement | string) => RootType | Promise; // Custom createRoot function (optional, React 18+) createRoot?: (container: Element | DocumentFragment, options?: CreateRootOptions) => Root; // Default createRoot options (React 18+) defaultRootOptions?: CreateRootOptions; } ``` ### RenderParams Render parameters interface: ```tsx interface RenderParams { moduleName?: string; basename?: string; memoryRoute?: { entryPath: string; initialState?: Record; }; dom: HTMLElement; // Target DOM element for rendering rootOptions?: CreateRootOptions; // React 18+ createRoot options [key: string]: unknown; // Other custom properties } ``` ### DestroyParams Destroy parameters interface: ```tsx interface DestroyParams { moduleName: string; dom: HTMLElement; } ``` ### CreateRootOptions React 18+ createRoot options: ```tsx interface CreateRootOptions { identifierPrefix?: string; // Identifier prefix onRecoverableError?: (error: unknown) => void; // Recoverable error handling transitionCallbacks?: unknown; // Transition callbacks } ``` ### Advanced Usage Examples #### Custom Rendering Logic ```tsx // ./src/export-app.tsx import App from './App'; import { createBridgeComponent } from '@module-federation/bridge-react'; import { createRoot } from 'react-dom/client'; export default createBridgeComponent({ rootComponent: App, // Custom render function render: (App, container) => { const root = createRoot(container as HTMLElement, { identifierPrefix: 'my-app-' }); root.render(App); return root; }, // Default createRoot options defaultRootOptions: { identifierPrefix: 'remote-app-', onRecoverableError: (error) => { console.error('Remote app recoverable error:', error); } } }); ``` #### Supporting Multiple Exports ```tsx // ./src/export-app.tsx import App from './App'; import Dashboard from './Dashboard'; import { createBridgeComponent } from '@module-federation/bridge-react'; // Export main application export default createBridgeComponent({ rootComponent: App }); // Export dashboard component export const dashboard = createBridgeComponent({ rootComponent: Dashboard }); ``` Corresponding Module Federation configuration: ```ts // rsbuild.config.ts export default { plugins: [ pluginModuleFederation({ name: 'remote1', exposes: { './export-app': './src/export-app.tsx', // Main application './dashboard': './src/export-app.tsx', // Dashboard }, }), ], }; ``` ## Next Steps After completing the export application configuration, you can continue reading [Loading Remote Applications](/guide/bridge/react/load-app.md) to learn how to load this remote application in a host application. --- url: /guide/bridge/react/load-app.md --- # Loading Applications This chapter introduces how to use `createRemoteAppComponent` to load and integrate remote React applications in a host application. ## What is createRemoteAppComponent? `createRemoteAppComponent` is the core React Bridge API for loading remote React applications. It provides: - **Automatic Lazy Loading**: Loads remotes only when needed - **Lifecycle Management**: Handles mount/unmount automatically - **Router Integration**: Integrates with React Router and supports basename injection - **Error Handling**: Built-in loading/runtime error fallback support - **Styling**: Supports `className` / `style` on the wrapper component ## Installation ```sh [npm] npm install @module-federation/bridge-react@latest ``` ```sh [yarn] yarn add @module-federation/bridge-react@latest ``` ```sh [pnpm] pnpm add @module-federation/bridge-react@latest ``` ## Basic Usage ### Step 1: Configure Remote Modules Add remote configuration to the host application's Module Federation config. :::tip Build Tool Support The following example uses Rsbuild configuration. Please adjust according to your build tool: - **Rsbuild**: `@module-federation/rsbuild-plugin` - **Rspack**: `@module-federation/enhanced/rspack` - **Webpack**: `@module-federation/enhanced/webpack` - **Vite**: `@module-federation/vite` ::: ```ts // rsbuild.config.ts import { pluginModuleFederation } from '@module-federation/rsbuild-plugin'; import { defineConfig } from '@rsbuild/core'; export default defineConfig({ plugins: [ pluginModuleFederation({ name: 'host-app', remotes: { remote1: 'remote1@http://localhost:3001/remoteEntry.js', }, }), ], }); ``` :::tip If the remote uses React 18/19 bridge, make sure to also configure the `shared` section with the trailing-slash prefix form (`'react-dom/'`) so that `react-dom/client` is correctly shared. ::: ### Step 2: Create Remote Components #### Define Loading and Error Components ```tsx // ./src/components/RemoteComponents.tsx import React from 'react'; export const LoadingComponent = () => (
Loading remote application...
); export const ErrorFallback = ({ error }: { error: Error }) => (

Remote Application Load Failed

Error details: {error.message}

); ``` #### Create Remote Application Component ```tsx // ./src/remotes/Remote1App.tsx import { createRemoteAppComponent } from '@module-federation/bridge-react'; import { loadRemote } from '@module-federation/runtime'; import { LoadingComponent, ErrorFallback } from '../components/RemoteComponents'; export const Remote1App = createRemoteAppComponent({ loader: () => loadRemote('remote1/export-app'), loading: , fallback: ErrorFallback, }); ``` #### Main Application Router Configuration ```tsx // ./src/App.tsx import React, { useRef } from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { Remote1App } from './remotes/Remote1App'; const HomePage = () => (

Host Application Home

This is the home content of the host application

); const App = () => { const ref = useRef(null); return ( } /> ( )} /> ); }; export default App; ``` ## Remote Component Props ### Router Props - `basename`: Set the base path for the remote application - `memoryRoute`: Memory router configuration ### Style Props - `style`: `React.CSSProperties` - `className`: `string` ### Ref Support - `ref`: `React.Ref` (forwarded to the internal container) ### Data Passing - Pass props directly (e.g. `userId="123"`) or via the `props` object. ## createRemoteAppComponent API Reference ### Function Signature ```tsx function createRemoteAppComponent, E extends keyof T = keyof T>( config: RemoteComponentParams ): React.ForwardRefExoticComponent< Omit, "ref"> & React.RefAttributes > ``` ### RemoteComponentParams\ ```tsx interface RemoteComponentParams, E extends keyof T = keyof T> { loader: () => Promise; loading: React.ReactNode; fallback: React.ComponentType<{ error: Error }>; export?: E; props?: T; } ``` ### RemoteComponentProps\ ```tsx interface RemoteComponentProps> { props?: T; fallback?: React.ComponentType<{ error: Error }>; loading?: React.ReactNode; basename?: string; memoryRoute?: { entryPath: string; initialState?: Record; }; style?: React.CSSProperties; className?: string; [key: string]: unknown; } ``` ### Parameter Details ### loader - Type: `() => Promise` - Required: Yes - Description: Loads the remote module object - Examples: ```tsx loader: () => loadRemote('remote1/export-app') loader: () => import('remote1/export-app') ``` ### loading - Type: `React.ReactNode` - Required: Yes - Description: UI shown while the remote is loading - Examples: ```tsx loading:
Loading...
loading: 'Loading remote app...' loading: ``` ### fallback - Type: `React.ComponentType<{ error: Error }>` - Required: Yes - Description: UI shown when loading/rendering fails; receives `error` - Examples: ```tsx fallback: ({ error }) =>
Error: {error.message}
fallback: ErrorBoundaryComponent ``` ### export - Type: `E extends keyof T` - Required: No - Default: `'default'` - Description: Selects which export to render from the loaded remote module Example: if your remote module exports: ```tsx export default App; export const dashboard = Dashboard; ``` Host usage: ```tsx createRemoteAppComponent({ loader: () => loadRemote('remote1/export-app'), }) createRemoteAppComponent({ loader: () => loadRemote('remote1/export-app'), export: 'dashboard', }) ``` ## Bundle Size Optimization ### React Router Dependency Explanation By default, `@module-federation/bridge-react` includes `react-router-dom` in your bundle to provide: - Automatic basename injection - Router context passing - Nested routing support If you don't need React Router integration (or you use a different router), disable `enableBridgeRouter` to reduce bundle size (about \~3KB gzipped) and avoid unnecessary router integration. ### How to Disable Router Dependency ```ts title="rsbuild.config.ts" import { pluginModuleFederation } from '@module-federation/rsbuild-plugin'; export default { plugins: [ pluginModuleFederation({ name: 'host-app', remotes: { remote1: 'remote1@http://localhost:3001/mf-manifest.json', }, bridge: { enableBridgeRouter: false, }, }), ], }; ``` By default, Bridge enables router support to provide the best development experience and compatibility. When you set `enableBridgeRouter: false`, the plugin automatically switches to a build that does not include `react-router-dom`, reducing your bundle size. :::info How It Works The plugin automatically resolves imports of `@module-federation/bridge-react` to `@module-federation/bridge-react/base`. That entry does not bundle `react-router-dom`, so no code changes are required. ::: --- url: /guide/bridge/react/load-component.md --- # Lazy Load and Prefetch Components This chapter introduces how to use `createLazyComponent` to load remote React components on demand in a host application. `createLazyComponent` is a higher-level loading API for React component scenarios. Compared with directly loading a module and wiring up `React.lazy`, `Suspense`, and error boundaries yourself, it builds those pieces into the API and additionally provides: - **Declarative loading and fallback**: no need to manually wrap `React.Suspense` and error boundaries - **Data prefetching**: use `prefetch` to start dependency requests before the component loads, avoiding request waterfalls where data is requested only after the component is ready - **SSR control**: precisely control whether remote components render on the server, helping avoid CSS flickering issues ## Installation ```sh [npm] npm install @module-federation/bridge-react@latest ``` ```sh [yarn] yarn add @module-federation/bridge-react@latest ``` ```sh [pnpm] pnpm add @module-federation/bridge-react@latest ``` ## Basic Usage ### Step 1: Register lazyLoadComponentPlugin Register the lazyLoadComponentPlugin plugin at runtime to enable createLazyComponent and prefetch APIs. ```tsx import { getInstance } from '@module-federation/runtime'; import { lazyLoadComponentPlugin } from '@module-federation/bridge-react/data-fetch'; const instance = getInstance(); // Register lazyLoadComponentPlugin plugin instance.registerPlugins([lazyLoadComponentPlugin()]); ``` ### Step 2: Call createLazyComponent After registering the `lazyLoadComponentPlugin` plugin, you can create lazy-loaded components using the `instance.createLazyComponent` method. ```tsx import { getInstance } from '@module-federation/runtime'; import { lazyLoadComponentPlugin } from '@module-federation/bridge-react/data-fetch'; const instance = getInstance(); // After registering lazyLoadComponentPlugin plugin, you can use `createLazyComponent` or `prefetch` APIs instance.registerPlugins([lazyLoadComponentPlugin()]); // Use instance.prefetch for remote module data prefetching instance.prefetch({ id: 'dynamic_remote' }); // Use instance.createLazyComponent for lazy loading remote modules const LazyComponent = instance.createLazyComponent({ loader: () => loadRemote('dynamic_remote'), loading: 'loading...', fallback: ({ error }) => { if (error instanceof Error && error.message.includes('not exist')) { return
fallback - not existed id
; } return
fallback
; }, }); ``` ## createLazyComponent API Reference ### Function Signature ```tsx function createLazyComponent( options: CreateLazyComponentOptions ): React.ComponentType ``` ### createLazyComponent > This API requires [registering the lazyLoadComponentPlugin plugin](#step-1-register-lazyloadcomponentplugin) first before it can be called. ย  Type declaration ```ts declare function createLazyComponent( props: CreateLazyComponentOptions ): (props: ComponentType) => React.JSX.Element; type CreateLazyComponentOptions = { loader: () => Promise; loading: React.ReactNode; delayLoading?: number; fallback: ReactNode | ((errorInfo: ErrorInfo) => ReactNode); export?: E; dataFetchParams?: DataFetchParams; noSSR?: boolean; injectScript?: boolean; injectLink?: boolean; }; type ComponentType = T[E] extends (...args: any) => any ? Parameters[0] extends undefined ? Record : Parameters[0] : Record; type DataFetchParams = { isDowngrade: boolean; } & Record; type ErrorInfo = { error: Error; errorType: number; dataFetchMapKey?: string; }; ``` In addition to loading components, this function also supports the following capabilities: 1. In SSR mode, it will inject the corresponding producer's style tags/script resources, which can help avoid CSS flickering issues caused by streaming rendering and accelerate PID (First Paint Interactive Time). 2. If the producer has a data fetching function, it will automatically call this function and inject the data. ```tsx import React, { FC, memo, useEffect } from 'react'; import { getInstance } from '@module-federation/enhanced/runtime'; import { ERROR_TYPE } from '@module-federation/bridge-react/data-fetch'; const instance = getInstance(); const LazyComponent = instance.createLazyComponent({ loader: () => import('remote/Image'), loading:
loading...
, fallback: ({error,errorType,dataFetchMapKey}) => { console.error(error) if(errorType === ERROR_TYPE.LOAD_REMOTE){ return
load remote failed
} if(errorType === ERROR_TYPE.DATA_FETCH){ return
data fetch failed, the dataFetchMapKey key is: {dataFetchMapKey}
} return
error type is unknown
; }, }); const App: FC = () => { return <> ; }; export default App; ``` #### loader - Type: `() => Promise` - Required: Yes - Default: `undefined` Function to load remote components, typically `()=>loadRemote(id)` or `()=>import(id)`. #### loading - Type: `React.ReactNode` - Required: Yes - Default: `undefined` Sets the module loading state. #### delayLoading - Type: `number` - Required: No - Default: `undefined` Sets the delay time for displaying loading state in milliseconds. If the loading time is less than this time, the loading state will not be displayed. #### fallback - Type: `(({ error }: { error: ErrorInfo}) => React.ReactElement)` - Required: Yes - Default: `undefined` The error component rendered when component **loading** or **rendering** fails. #### export - Type: `string` - Required: No - Default: `'default'` If the remote component is a named export, you can specify the component name to export through this parameter. By default, it loads the default export. #### dataFetchParams - Type: `DataFetchParams` - Required: No - Default: `undefined` If the remote component has a data fetching function, this will be passed to the data fetching function when set. #### noSSR - Type: `boolean` - Required: No - Default: `false` When set to `true`, this component will not render in SSR scenarios. #### injectScript - Type: `boolean` - Required: No - Default: `false` In SSR environment, if set to `true`, the created component will inject the corresponding script resources. For example, if `remote/button` has `__federation_button.js`, then in the HTML returned by SSR, the corresponding script will be injected before the component to accelerate interaction speed. ```html