- Defining microfrontends based on business domains rather than technical elements, citing examples like headers or footers as typically being components, not microfrontends
Module fedration
Webpack’s Module Federation is essentially a mini runtime framework injected into our bundles that lets independently compiled builds dynamically share and consume modules at runtime.
Under the hood
At build time, when we configure Webpack like this:
new ModuleFederationPlugin({
name: "hostApp",
remotes: {
authApp: "authApp@http://localhost:3001/remoteEntry.js",
},
exposes: {
"./Button": "./src/Button.js",
},
shared: ["react"],
});Webpack:
-
Analyzes Exposes For
./Button, Webpack creates a container manifest so others can dynamically load it. -
Analyzes Remotes
- Emits a loader in your bundle that will load the remote’s
remoteEntry.js.
- Emits a loader in your bundle that will load the remote’s
-
Emits
remoteEntry.js- This is a manifest + module factory registry + bootstrap, like a mini Webpack runtime, which remote apps expose.
-
Injects Runtime Hooks Adds special Webpack functions into your host bundle for:
-
Loading remote modules
-
Initializing shared scopes
-
Negotiating versions
-
At runtime, when we host app runs and you say:
import("authApp/Button").then((factory) => {
const Button = factory();
});Webpack does all this under the hood:
- Initializes share scope
- Calls
__webpack_init_sharing__('default').
- Calls
- Dynamically Loads Remote
- Inserts a
<script src="http://.../remoteEntry.js">, resolves when remote is ready.
- Inserts a
- Initializes Remote Container
- Calls
container.init(shareScope)to merge share scopes (like React) and check versions.
- Calls
- Fetches Exposed Module
- Calls
container.get("./Button")to get a factory function forButton.
- Calls
- Runs the Factory
factory()gives you the actual module code.
After the exposed will look like
var moduleMap = {
"./src/Button.jsx": () => {
return __webpack_require__.e(507).then(() => (() => ((__webpack_require__(507)))));
}
};
var get = (module, getScope) => {
__webpack_require__.R = getScope;
getScope = (
__webpack_require__.o(moduleMap, module)
? moduleMap[module]()
: Promise.resolve().then(() => {
throw new Error('Module "' + module + '" does not exist in container.');
})
);
__webpack_require__.R = undefined;
return getScope;
};
var init = (shareScope, initScope) => {
if (!__webpack_require__.S) return;
var oldScope = __webpack_require__.S["default"];
var name = "default"
if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope");
__webpack_require__.S[name] = shareScope;
return __webpack_require__.I(name, initScope);
};
__webpack_require__.d(exports, {
get: () => (get),
init: () => (init)
});| Component | Purpose |
|---|---|
moduleMap | Holds all exposed modules with dynamic loader functions |
get() | Loads and returns the requested module if it’s in the map |
init() | Initializes the shared scope (like React or other singleton libraries) |
__webpack_require__.e | Loads chunks dynamically (split code) |
__webpack_require__.S | Stores shared scopes |
__webpack_require__.I | Handles initializing modules with shared dependencies |
Shadow DOM?
Shadow DOM is part of the Web Components standard. It allows you to attach a “shadow tree” (a hidden DOM subtree) to an element. This means:
- Styles and scripts inside this subtree won’t leak out.
- Likewise, outside styles and scripts won’t affect it.
<user-card></user-card>
<script>
class UserCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' }); // 👈 attach Shadow DOM
shadow.innerHTML = `
<style>
p { color: red; }
</style>
<p>Hello from shadow DOM</p>
`;
}
}
customElements.define('user-card', UserCard);
</script>
mistake
- BIdirectional flow where we dont have central state tool we simply emiting the event from for key and there will be listener for that that will consume ect which is hard to debug and easy to break
EventBus.on('emailChanged', (newEmail) => {
console.log('UserProfile received updated email:', newEmail);
setEmail(newEmail); // direct update from NotificationSettings
});
EventBus.emit('emailChanged', newName);- So use unidirectional flow where have central state mangement where all state update need to be tell here and the subscriber will get notify of it where we have source of truth and control to us
import { useState, useEffect } from 'react';
let state = {
username: 'boopathi',
email: 'boopathi@example.com'
};
let listeners = [];
export function useStore() {
const [localState, setLocalState] = useState(state);
useEffect(() => {
const listener = () => setLocalState({ ...state });
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
};
}, []);
return [localState, dispatch];
}
function dispatch(action) {
switch (action.type) {
case 'UPDATE_USERNAME':
state.username = action.payload;
break;
case 'UPDATE_EMAIL':
state.email = action.payload;
break;
default:
return;
}
// Notify all listeners (re-render components)
listeners.forEach(listener => listener());
}
///
dispatch({ type: 'UPDATE_USERNAME', payload: e.target.value });
- (Duplicated API Calls): Having multiple microfrontends in the same view make identical calls to the same backend API is inefficient and puts unnecessary load on backend services (API Gateway, authorization, downstream services)
- Teams working in silos can unintentionally duplicate code or make redundant API requests for the same data, which significantly degrades application performance
Microservices Principles
- Modeled Around Business Domains : It starts from the assumption that each pieceof software should reflect what the organization does and that we should design our architectures based on domains and subdomains, leveraging ubiquitous languages shared across the business.
- Culture of Automation
- Hide Implementation Details
- Decentralize Governance
- Deploy Independently
- Isolate Failure
- Highly Observable
communication patterns
- Parent to child
- Using custom attributes in html tags
- connected callback
- attribute change callback
- child to parent
- Custom events
- event listener
- sibling
- Broadcast API
- Event bus
- Global
- URL
- Global state management (redux)
- local storage
Company using microfront end
- Spotify
- walmart
- paypal
- netflix (they own framework)
Frameworks
Cache
Resources
- https://module-federation.io/guide/start/index.html
- https://github.com/module-federation/module-federation-examples
- https://itnext.io/11-micro-frontends-frameworks-you-should-know-b66913b9cd20
- https://medium.com/paypal-tech/how-micro-frontend-has-changed-our-team-dynamic-ba2f01597f48
Micro frontend dev
news letter
monorepo
- apps
- library
Risks and Challenges of using a Monorepo:
- Broken Main Branch:
- A bug merged into the main branch can break apps or block continuous delivery. This is worse in a monorepo as it can take down “all your stuff”.
- A specific incident involved a pipeline migration bug that caused environment variables to be missing, breaking all apps. This led to a 50-minute outage of the main branch and broken apps.
- Mitigation measures included adding end-to-end tests, making rollback easier (e.g., nightly builds as checkpoints), and investing in a “good solid and rugged basket” with a “repair kit”.
- Slow Builds:
- This can be a concern, but monorepo build tools like NX help mitigate this. Locally, you only build projects needed for your app. In the pipeline, NX builds only affected projects, and supports distribution to multiple machines and remote caching. Caching can reduce build times significantly (e.g., 3 minutes with caching vs. 35 minutes worst case).
- Large Repository Size / Slow Git Operations:
- Git operations like cloning or blame can become slow as the repository gets very large. Some companies use custom Version Control systems. At their current scale, this hasn’t been an issue, and they believe there are ways to prevent it for some time.
- Bugs Rolling Out to Unknowing App Owners:
- Changes to core libraries can trigger builds and releases of affected projects even if the public interface isn’t changed. If the change is a bug, it can roll out automatically.
- The upside is teams work autonomously and get bug fixes for free. The risk can be reduced by writing good tests.
- Less Freedom:
- Monorepos likely mean less freedom for individual teams. Teams may be forced to use certain tools (e.g., NX, TypeScript, React in their case).
- Deployment can be coupled; changing one service may trigger builds/deploys for all dependent services.
- App owners have to update on someone else’s schedule, as frequent, small updates are needed. They can’t delay upgrades indefinitely.
- This less freedom is a trade-off for standardization and scale.
- Messy, Unmaintainable Code:
- The risk of code becoming complicated (“monolith ghost”) is always present when code sharing is easy.
- There’s a trade-off between avoiding repetition and increased coupling that requires constant monitoring and care.
Rspack’s Implementation of Module Federation for Microfrontend Architectures
1. Introduction: Module Federation and Rspack
Module Federation represents a significant paradigm shift in front-end architecture, offering a solution for the decentralization of JavaScript applications. This architectural pattern allows for the decomposition of large web applications into smaller, independently deployable units, often referred to as microfrontends.1 Much like microservices operate on the server-side, Module Federation enables these independent applications to share code and resources at runtime without the need for a complete rebuild and redeployment of the entire system.1 The core principle is to allow different teams to work on separate parts of a larger application, fostering autonomy and potentially accelerating development cycles.1 The adoption of Module Federation can lead to several key advantages, including a reduction in code duplication across different applications, improved maintainability due to the separation of concerns, a decrease in the overall size of individual applications by sharing common dependencies, and ultimately, an enhancement in application performance through optimized resource loading.1
Rspack emerges as a modern, high-performance JavaScript bundler engineered to address the growing demands of complex web applications.4 Built with Rust, Rspack boasts remarkable speed in bundling assets and offers strong compatibility with the expansive webpack ecosystem, facilitating a smooth transition for projects looking to leverage its performance benefits.4 Recognizing the increasing importance of microfrontend architectures, the Rspack team has established a close working relationship with the developers of Module Federation. This collaboration ensures that Rspack provides robust, first-class support for implementing Module Federation, making it a compelling choice for building scalable and maintainable web applications.1 To cater to diverse project requirements and to facilitate migration from existing systems, Rspack supports multiple major versions of Module Federation, offering developers the flexibility to select the version that best aligns with their specific needs and the complexity of their architectural goals.1
A notable aspect of Rspack’s design is its deep integration with the Module Federation concept. The explicit partnership between the Rspack and Module Federation teams suggests that Rspack’s implementation benefits from an intimate understanding of the underlying mechanisms of module sharing. This close collaboration likely results in a more optimized and efficient module federation experience compared to bundlers where the integration might be less direct. Furthermore, the consistent emphasis on performance improvements associated with both Module Federation and Rspack hints at a potential synergy. Rspack’s inherent speed in handling build processes could amplify the inherent performance advantages of Module Federation, such as reduced redundancy and optimized resource loading. This combination could lead to substantial gains in both development and runtime performance for microfrontend applications.
2. Rspack’s Core Implementation of Module Federation
Rspack provides support for the three primary versions of Module Federation, each offering distinct features and catering to different development scenarios.1 Understanding these versions is crucial for developers to effectively leverage Rspack’s module federation capabilities.
Module Federation version 1.0 was initially implemented to ensure compatibility with webpack’s widely adopted webpack.container.ModuleFederationPlugin.1 This version serves primarily as a bridge for projects migrating from webpack to Rspack that wish to maintain a high degree of consistency with their existing module federation logic. However, it is important to note that version 1.0 is no longer under active development, and the Rspack documentation recommends utilizing either version 1.5 or 2.0 for new projects or when considering upgrades.1 In Rspack, version 1.0 can be accessed through the Rspack.container.ModuleFederationPluginV1 API.2
Module Federation version 1.5 represents a significant advancement, as it is built directly into the core of Rspack.1 This native integration provides inherent performance benefits and streamlined configuration. Version 1.5 supports the fundamental features of module federation, including the ability to export modules for consumption by other applications, load modules exposed by remote applications, and share common dependencies to minimize redundancy.1 A key enhancement in version 1.5 is the introduction of runtime plugin functionality. This powerful feature allows developers to extend and customize the behavior of module federation at runtime, enabling more sophisticated scenarios and tailored solutions.1 To utilize Module Federation 1.5 in Rspack, developers can employ the rspack.container.ModuleFederationPlugin without the need to install any additional plugins.1 It is often necessary to explicitly set the uniqueName property within the output configuration in rspack.config.mjs to ensure that Hot Module Replacement (HMR) functions correctly in a federated environment.1
Module Federation version 2.0 is an enhanced iteration built upon the foundation of version 1.5.1 It incorporates several additional out-of-the-box features designed to further streamline the development of large-scale microfrontend architectures.1 These advanced features include dynamic TypeScript type hints, which improve the developer experience when working with shared modules in TypeScript projects, dedicated Chrome DevTools integration for enhanced debugging capabilities, preloading functionalities to optimize the loading of federated modules, and continued support for runtime plugins.1 To leverage the capabilities of Module Federation 2.0 with Rspack, developers need to install the @module-federation/enhanced plugin.1 Subsequently, the ModuleFederationPlugin should be imported from this enhanced package and used within the rspack.config.mjs file.1
Rspack’s implementation of Module Federation v1.5 benefits from a direct integration with the core Module Federation runtime logic. This tight coupling likely contributes to the performance advantages often associated with Rspack. In contrast, Module Federation v2.0 relies on the @module-federation/enhanced package, which represents a more decoupled approach, offering an independent version of the runtime with additional features built upon the principles of v1.5.1 This decoupling allows for independent updates and potentially broader compatibility across different bundlers, as evidenced by its use with webpack as well.6 The existence of core packages like @module-federation/runtime and @module-federation/sdk, which are dependencies for both webpack and Rspack 14, suggests a shared underlying architecture for module federation across different build tools, promoting a degree of standardization and interoperability. Furthermore, the @module-federation/runtime-tools package provides an avenue for more granular control over the module federation runtime within Rspack. It allows developers to potentially override default runtime packages and even perform partial upgrades of module federation without requiring a full Rspack core update.14
Configuration of Module Federation in Rspack is primarily done through the ModuleFederationPlugin within the rspack.config.mjs file. For both v1.5 and v2.0, the plugin offers a set of fundamental options that define the behavior of module federation for the current application. The name option provides a unique identifier for the application within the federated ecosystem.7 The filename option specifies the name of the entry file for the remote container, typically remoteEntry.js, which serves as the gateway for other applications to access exposed modules.6 The remotes option is crucial for consuming applications, as it defines the remote applications from which modules will be loaded, specifying their names and the URLs to their respective remoteEntry.js files.6 Conversely, the exposes option is used by applications that intend to share their modules, defining which internal modules should be made available to other containers.6 The shared option plays a vital role in managing dependencies that should be shared across different microfrontends, preventing duplication and ensuring consistency.6 To ensure proper functionality of Hot Module Replacement (HMR) in a module federation context, it is essential to configure the output.uniqueName property in the rspack.config.mjs file with a unique value for each application.1 While the runtime option (Type: string | false) exists, its primary function appears to be related to specifying a custom path for the Module Federation 1.5 runtime, with the shareStrategy option offering more direct control over the loading of shared dependencies.15 For Module Federation 1.5, the runtimePlugins option (Type: string) provides a powerful mechanism to extend the default behavior by allowing developers to specify paths to custom plugin implementations that hook into the module federation lifecycle.1
The availability of these distinct Module Federation versions within Rspack offers developers a flexible toolkit to address a wide range of project needs. The progression from a compatibility-focused v1.0 to a natively integrated and extensible v1.5, and then to an enhanced v2.0 with advanced features, demonstrates a clear evolution aimed at improving the module federation experience. Developers should carefully consider their project’s scale, complexity, and specific requirements when selecting the appropriate version. The decoupling of advanced features in v2.0 into the @module-federation/enhanced package signifies a trend towards modularity and potentially broader compatibility across build tools. Furthermore, the introduction of runtime plugins in v1.5 marks a significant step towards greater runtime extensibility, empowering developers to customize module federation behavior beyond static build-time configurations.
3. Handling Multiple React Versions in a Federated Ecosystem
Managing dependencies, especially a widely used library like React, becomes a critical consideration when implementing a microfrontend architecture using module federation. The presence of multiple teams and independently deployable units can sometimes lead to a scenario where different microfrontends might have dependencies on different versions of React. This can introduce several challenges and complexities within the federated ecosystem.17 While module federation’s core goal is to facilitate code sharing and reduce redundancy, running different major versions of React concurrently within the same rendering context is generally discouraged due to potential breaking changes in the API and inconsistencies in internal state management.18
Rspack’s shared configuration within the ModuleFederationPlugin provides the primary means to manage shared dependencies like React across federated modules.15 Several sub-options within this configuration are particularly relevant when addressing the complexities of managing React versions:
The singleton option (Type: boolean) is of paramount importance for React. When set to true, it ensures that only one instance of React (and its associated library react-dom) is loaded at runtime across all federated modules that declare it as shared, regardless of the specific versions requested (within the allowed range).6 This is highly recommended for React because it relies on internal state and context that are managed on a per-instance basis. Running multiple independent instances of React within the same application can lead to unpredictable behavior, especially when using features like hooks and context API.
The requiredVersion option (Type: false | string) allows developers to specify a semantic version number or a version range (e.g., "^18.0.0") that the current module federation container requires for the shared React dependency.15 If a consuming application offers a version that does not fall within this specified range, the module might not be shared, or an error could occur. This option enables developers to define the compatible React version range for their microfrontend.
The strictVersion option (Type: boolean), when set to true, enforces that the shared React dependency provided by a consuming application must exactly match the version specified in requiredVersion.11 If set to false (the default), it allows for more flexible matching within the requiredVersion range. While strictVersion: true might seem appealing for ensuring precise version control, it can be overly restrictive for React, as even minor version mismatches might not necessarily cause issues. Therefore, using strictVersion: false in conjunction with a well-defined requiredVersion range is often a more practical approach.
The shareKey option (Type: string) enables developers to specify a custom key to use when searching for the requested React module in the share scope.14 While the default shareKey for React is typically just 'react', this option could theoretically be used to manage different React versions under different keys. However, for a singleton library like React, this complicates the sharing model and is generally not the recommended approach for handling different minor versions. It might be more relevant in very advanced scenarios involving different implementations of a similar library.
The shareScope option (Type: string) defines a namespace for shared dependencies.15 Different builds can use their own share scopes independently to avoid conflicts. While this could be used to isolate different major versions of React in completely separate microfrontend ecosystems, it would prevent any sharing of React between them, effectively creating isolated applications rather than a truly federated system for that particular library. The default shareScope is "default".
The eager option (Type: boolean) determines whether the shared React module is loaded in the initial chunk.8 Setting eager: true can ensure that React is available very early in the application lifecycle, which might be necessary if remote modules rely on it immediately. However, this can increase the initial bundle size of the application. For React, it’s often recommended to keep eager as false to leverage code splitting and load React on demand, although setting it to true in the host application can sometimes be beneficial to ensure its availability for remotes.
The most effective strategy for handling React versions in a module federation environment is to prioritize consistency across all microfrontends.19 Aiming for the same major and minor versions of React minimizes the risk of runtime conflicts and maximizes the benefits of code sharing. To achieve this, it is crucial to configure React and react-dom as shared dependencies with singleton: true in both the host and remote applications.6 Additionally, developers should carefully define compatible requiredVersion ranges to allow for sharing while also setting appropriate version constraints.
In situations where there is a compelling need to run different major versions of React (e.g., during a phased migration), a more advanced technique might be necessary. This often involves isolating the microfrontend with the different React version within its own rendering context, potentially by mounting it within a specific DOM element managed by its own React instance.30 This approach might require custom logic, possibly involving techniques similar to the inject function mentioned in the research material 30, or even leveraging runtime plugins to manage the lifecycle and rendering of the isolated React application. However, it’s important to understand that this level of isolation typically means that components from different major React versions cannot directly interact as JSX within the same shared scope. Using different shareScope values to isolate React versions is another possibility, but this essentially creates separate, non-sharing ecosystems for React, which might not be ideal for a cohesive application experience.
The strong recommendation across various sources to use singleton: true for React highlights the fundamental challenge of running multiple React instances in the same context. While module federation provides the mechanism for sharing, React’s internal design necessitates a single, consistent instance for proper functionality, especially when utilizing modern features like hooks and context. The fact that discussions and examples exist around isolating different React versions, despite the preference for singleton sharing, indicates that this is a real-world problem often encountered during migration or when integrating with legacy systems. However, these solutions typically involve more complex runtime logic and a deliberate separation of rendering contexts rather than relying solely on the module federation configuration. Furthermore, reports of potential compatibility issues with very recent React versions in some Rspack setups underscore the importance of staying informed about the compatibility status of the bundler and its plugins when adopting new versions of core libraries.
4. Microfrontend Loading Mechanisms in Rspack
In Rspack’s module federation implementation, the process of loading and initializing microfrontends involves a well-defined sequence of steps facilitated by the ModuleFederationPlugin configuration. A host application, acting as the main container, consumes remote microfrontends by declaring them within the remotes configuration of its ModuleFederationPlugin in the rspack.config.mjs file.6 This configuration establishes a mapping between a user-defined remote name (which will be used in imports) and the URL of the remote microfrontend’s remoteEntry.js file.
The remoteEntry.js file, the name of which is specified by the filename option in the remote microfrontend’s ModuleFederationPlugin configuration, serves as the crucial entry point exposed by the remote container.6 When the host application needs a module from a remote microfrontend, it initiates a request to fetch and execute this remoteEntry.js file. Upon execution, this file registers the modules that the remote microfrontend intends to share, making them available for consumption by the host and potentially other remote applications. Subsequently, the consuming applications can import these exposed modules using the remote’s name, as defined in the remotes configuration, followed by the name under which the module was exposed in the remote’s exposes configuration (e.g., import Header from 'remoteApp/Header').11
The loading of dependencies, both within a single microfrontend and across federated modules, can be controlled through different strategies. The eager option within the shared configuration 8 dictates whether a shared dependency is loaded eagerly in the initial chunk or lazily on demand when it is first encountered during the application’s execution. Setting eager: true for critical shared dependencies, such as React in the host application, can ensure their early availability, potentially preventing runtime errors if remote modules rely on them immediately. However, this comes at the cost of potentially increasing the initial bundle size. In contrast, remote modules themselves are typically loaded lazily. This means that the code for a remote microfrontend is only fetched and executed by the host application when a module from that remote is explicitly imported and used. This lazy loading strategy is essential for optimizing the initial load time of the host application, as it avoids loading code that might not be immediately necessary.
Asynchronous bootstrapping plays a significant role in the initialization process of module federation setups.11 Often, the host application needs to asynchronously initialize the module federation runtime before it can reliably load and interact with remote microfrontends. This asynchronous initialization might involve the use of dynamic imports, such as import('./bootstrap') in the main entry point of the host application, to ensure that the module federation runtime is fully prepared before the application’s core logic begins to execute. Additionally, the @module-federation/enhanced/runtime package provides an init function 6 that can be used for more explicit runtime initialization, particularly in advanced scenarios or when developers need finer-grained control over the initialization process beyond what the build plugin handles automatically.
Rspack also supports dynamic module loading at runtime using the standard import() syntax.31 This feature can be particularly useful in module federation for loading remote modules based on specific conditions, user actions, or routing logic. The import() syntax is treated as a split point by Rspack, meaning that the requested module and its dependencies can be fetched and executed on demand, further enhancing the performance and flexibility of microfrontend loading. Furthermore, Module Federation version 2.0 introduces the concept of a manifest file (mf-manifest.json).9 This file can serve as a centralized registry of remote modules and their locations, offering a more dynamic and potentially versioned approach to managing remote endpoints. Instead of directly specifying the URL of remoteEntry.js in the host’s remotes configuration, the host can point to the manifest file, which then provides the necessary information to locate and load the required remote modules.
Finally, it’s important to note that Rspack’s module federation capabilities can be seamlessly integrated with higher-level microfrontend frameworks such as Modern.js 32 and potentially single-spa.34 These frameworks often provide additional features, conventions, and lifecycle management tools that can simplify the development and orchestration of complex microfrontend applications built upon the foundation of Rspack’s module federation.
The two-step process of first fetching remoteEntry.js and then requesting exposed modules introduces a potential point of failure. If remoteEntry.js cannot be loaded or if its execution results in an error, the host application will be unable to access the remote microfrontend’s functionalities. This underscores the importance of implementing robust error handling mechanisms and potentially employing preloading strategies for critical remote modules to mitigate these risks. The evolution towards runtime-driven module loading, exemplified by the introduction of manifest files in version 2.0, signifies a broader trend towards more dynamic and configurable microfrontend architectures. This approach offers increased flexibility in deploying and managing microfrontends, as the host application is not tied to hardcoded URLs at build time. The integration of Rspack’s module federation with higher-level microfrontend frameworks demonstrates a layered approach to building complex applications. While Rspack provides the core bundling and module sharing capabilities, these frameworks offer additional abstractions and tooling that can significantly simplify the development and management of large-scale microfrontend systems.
5. In-Depth Analysis of Shared Keys in Module Federation Plugin
The shareKey option within the shared configuration of Rspack’s ModuleFederationPlugin provides a powerful mechanism for fine-grained control over shared dependencies within a federated architecture.15 At its core, shareKey allows developers to specify a custom identifier for a shared module within the global share scope. By default, if no shareKey is provided, the name of the shared module (which corresponds to the key in the shared object) is used as the identifier.
The primary function of the shareKey is to act as the lookup key when the module federation runtime attempts to resolve a shared dependency. When a federated module requests a dependency that has been marked as shared, the runtime searches the configured shareScope (which defaults to "default") for a module registered with the requested shareKey. This indirection offered by shareKey becomes particularly valuable in several scenarios.
One common use case is to handle situations where different packages might offer functionally equivalent capabilities but have distinct package names.15 For instance, in a large organization, different teams might have independently chosen different UI component libraries that both provide a Button component. By configuring both of these packages in the respective microfrontends’ shared configurations with the same shareKey (e.g., 'ui-button'), the module federation runtime can potentially share a single compatible implementation of the button across both microfrontends, reducing redundancy and ensuring a consistent user experience.
Another important application of shareKey is in abstracting internal package names, especially during refactoring or when dealing with evolving dependencies.15 If a core library within a microfrontend architecture undergoes a name change due to refactoring, maintaining the same shareKey for both the old and new package names in the shared configurations of consuming applications can prevent breaking changes. This allows other microfrontends to continue requesting the shared functionality using the consistent shareKey, while the underlying package dependency in the providing microfrontend can be updated seamlessly.
In more advanced scenarios, shareKey could theoretically be employed to manage different implementations or even different versions of a library under distinct keys within the share scope.14 However, for singleton libraries like React, this approach needs careful consideration as it can complicate the sharing model and might not align with the intended behavior of such libraries. For instance, while you could potentially share React v17 under the shareKey 'react-17' and React v18 under 'react-18', this would necessitate more complex logic in the consuming applications to request the specific version they need and might not be ideal for seamless integration within the same rendering context.
When a federated module requires a shared dependency, the Rspack module federation runtime uses the requested shareKey to search within the specified shareScope.15 The runtime then evaluates the version requirements (defined by the requiredVersion option) and other sharing configurations associated with that shareKey to determine if a compatible shared instance is available from another container. If a suitable module is found, the runtime will reuse that instance. If not, it might attempt to load a new instance based on the import configuration or fetch it from a remote location, depending on the overall module federation setup.
The shareKey essentially provides a level of abstraction, allowing different microfrontends to communicate about shared dependencies using a logical name rather than being tightly coupled to specific package identifiers. This promotes greater flexibility and interoperability within the federated ecosystem. However, it is crucial to use shareKey thoughtfully to avoid introducing unnecessary complexity or unintended sharing behaviors. The interaction between shareKey and shareScope further enhances the control over dependency management, allowing for the creation of isolated namespaces for shared modules when required.
6. Code-Level Insights and Configuration Examples
To illustrate the practical implementation of module federation with Rspack, let’s consider example configurations for both a host and a remote microfrontend.
Host Application (rspack.config.mjs):
JavaScript
import { rspack } from '@rspack/core';
export default {
output: {
uniqueName: 'hostApp',
},
plugins: [
new rspack.container.ModuleFederationPlugin({
name: 'hostContainer',
remotes: {
remoteApp: 'remoteContainer@http://localhost:3001/remoteEntry.js',
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
eager: true, // Consider eager loading in the host
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
eager: true, // Consider eager loading in the host
},
'ui-library': { // Example using shareKey
shareKey: 'ui-components',
requiredVersion: '^1.0.0',
},
},
}),
],
};
In this host application configuration, remoteApp is defined as a remote, pointing to its remoteEntry.js file. React and react-dom are configured as singletons with a required version, and eager loading is enabled. Additionally, a shared dependency with the package name 'ui-library' is mapped to the shareKey 'ui-components'.
Remote Microfrontend (rspack.config.mjs):
JavaScript
import { rspack } from '@rspack/core';
export default {
output: {
uniqueName: 'remoteApp',
},
plugins: [
new rspack.container.ModuleFederationPlugin({
name: 'remoteContainer',
exposes: {
'./Header': './src/components/Header',
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
},
'another-ui-lib': { // Providing a different package for the same shareKey
shareKey: 'ui-components',
requiredVersion: '^2.0.0',
import: 'another-ui-lib',
},
},
}),
],
};
Here, the remote microfrontend exposes a Header component. It also declares React and react-dom as shared singletons with a matching required version. Notably, it shares the 'another-ui-lib' package under the same shareKey 'ui-components' as the host, demonstrating how different packages can be mapped to a common shared identity.
Conceptual Runtime Plugin Example (for v1.5):
Create a file named my-runtime-plugin.js:
JavaScript
module.exports = function() {
return {
name: 'MyCustomPlugin',
beforeInit(args) {
console.log(' beforeInit:', args);
return args;
},
beforeLoadShare(args) {
console.log(' beforeLoadShare:', args);
return args;
},
};
};
Then, in your rspack.config.mjs for v1.5:
JavaScript
import { rspack } from '@rspack/core';
const myRuntimePlugin = require.resolve('./my-runtime-plugin');
export default {
output: {
uniqueName: 'appWithPlugin',
},
plugins:,
}),
],
};
This demonstrates how to define a simple runtime plugin that logs messages during the beforeInit and beforeLoadShare lifecycle hooks and how to register it in the ModuleFederationPlugin configuration.
The following table summarizes the key SharedConfig options relevant to managing React versions:
| Option | Description | Impact on React Version Management |
singleton | Ensures only one instance of the shared module is loaded. | Crucial for React to prevent conflicts from multiple instances. Should generally be set to true. |
requiredVersion | Specifies the acceptable version range for the shared module. | Allows defining the compatible React version range. Host and remotes should have compatible ranges. |
strictVersion | Enforces an exact version match. | Use with caution for React, as minor version mismatches might still cause issues. Generally, false is recommended with a well-defined requiredVersion. |
shareKey | Custom key to search for the shared module. | Can be used in advanced scenarios to differentiate between different React implementations or versions, but generally the default (‘react’, ‘react-dom’) is sufficient when used with singleton. |
shareScope | Namespace for shared dependencies. | Useful for isolating React versions between completely independent sets of microfrontends if absolutely necessary, but complicates sharing across the main application ecosystem. |
eager | Loads the shared module in the initial chunk. | Might be used in specific edge cases, but generally false is preferred for better code splitting. Setting to true for React in the host can sometimes be beneficial to ensure its availability for remotes. |
import | Module to use as a fallback if the shared module isn’t found or has an invalid version. | Can be used to provide a local fallback React version if a compatible shared version isn’t available from remotes, but should be used judiciously with singleton: true. |
These configuration examples and the table highlight the importance of consistency in the shared dependency configuration, especially for singleton and requiredVersion, across all participating microfrontends. The runtime plugin example illustrates the extensibility offered by Module Federation 1.5. The table provides a quick reference for key configuration options when managing React in a federated environment.
7. Conclusion and Best Practices
Rspack offers robust and versatile support for Module Federation through its native integration of v1.5 and the enhanced capabilities of v2.0. This architectural pattern enables the efficient sharing of code and resources between independent microfrontends, contributing to reduced redundancy, improved maintainability, and enhanced application performance. The management of shared dependencies, particularly for a foundational library like React, is crucial in a federated ecosystem. Rspack’s shared configuration within the ModuleFederationPlugin provides the necessary tools to control the sharing and versioning of these dependencies. The singleton option stands out as essential for React, ensuring a single instance across the federated application to prevent conflicts. Defining appropriate requiredVersion ranges is also critical for maintaining compatibility between different microfrontends. The process of loading microfrontends in Rspack involves the host application consuming remote modules via the remotes configuration, which points to the remote’s remoteEntry.js file. Understanding the implications of loading strategies, such as eager versus lazy loading, is important for optimizing application performance. The shareKey option offers a mechanism for more advanced dependency management, allowing for the mapping of different package names to a common shared identity.
To effectively leverage Rspack’s module federation capabilities, several best practices should be considered:
It is strongly recommended to maintain consistent major and minor versions of React across all microfrontends within the federated architecture. This practice minimizes the risk of runtime errors and ensures seamless integration between different parts of the application. For React and react-dom, always configure them as shared dependencies with the singleton: true option. This enforces a single instance at runtime, which is crucial for React’s internal workings and prevents potential conflicts arising from multiple independent instances. Define clear and compatible requiredVersion ranges for shared dependencies in both the host and remote applications. This allows for effective sharing while also setting necessary boundaries for version compatibility. Utilize asynchronous bootstrapping in your host application to ensure that the module federation runtime is fully initialized before attempting to load remote microfrontends. This helps to avoid potential race conditions and initialization errors. Employ lazy loading for remote microfrontends to improve the initial load performance of the host application by only fetching and executing the code when it is actually needed. Understand the critical role of the remoteEntry.js file as the contract between a remote microfrontend and its consumers, and ensure it is correctly configured to expose the intended modules. Use the shareKey option strategically in scenarios where you need to handle naming conflicts between packages that provide the same functionality or to abstract internal package names during refactoring. For more advanced customization of module federation behavior, particularly in version 1.5, explore the possibilities offered by runtime plugins. Finally, stay informed about the latest features, best practices, and potential issues by regularly consulting the official Rspack and Module Federation documentation and engaging with the community.