This project is a Host MFE that implements the Module Federation pattern described within the Quick Start Guide from the Module Federation documentation.
This project was developed to demonstrate the harmonious coexistence of Module Federation with the Rsbuild bundler using TypeScript, React, Testing Library, and Material UI which when combined offer a very diverse foundation for developing modern web applications. The motivation for this project stems from a demand to develop feature rich administrative dashboards within a distributed development environment.
pnpm i
pnpm dev
pnpm build
pnpm test
This project provides the source code for the host (consumer) application. Before starting the host development server you need to make sure that the project remotes are up and running. This project consumes two hosts; remote_one
and remote_two
. The remote servers MUST be up and available on the local system otherwise this application will fail to render.
Server | Port |
---|---|
host | localhost:3000 |
remote_one | localhost:3001 |
remote_two | localhost:3002 |
You may override MFE remotes defined within ./remotes.dev.json
by setting a REMOTES
environment variable.
export REMOTES="{\"remote_one\":\"http://localhost:3001\"}"
This project is Micro-Frontend host application that implements Module Federation 2.0 patterns. Rspack and Rsbuild provide native support for Module Federation but the setup requires a little work and has very distinct differences froma a vanilla Rsbuild project.
module federation with rspack/rsbuild automatically writes types from remotes to the ./@mf-types
directory. We need to update the tsconfig.jsion
file to recognize this for our dev/build environments.
{
"compilerOptions": {
...
"paths": {
"*": ["./@mf-types/*"]
}
}
}
Module Federation v2.0 requires the @module-federation/enhanced
plugin so we add it to the project as a development dependency with pnpm.
pnpm add -D @module-federation/enhanced
After experiencing several out-of-the-box failures with implementing Module Federation, I found that implementing a standard index.ts
file with React that loads the React library and the root <App />
into the root element of the DOM is not supported.
To resolve this issue, which appears to be some sort of race condition, we abstract the normal index.ts
implementation to bootstrap.ts
and replace the index.ts
implementation with an import statement.
./src/index.ts
// Move src/index.tsx to src/bootstrap.tsx file
// src/index.tsx SHOULD be renamed to src/index.ts
import('./bootstrap')
// src/bootstrap.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
if (container) {
const root = createRoot(container);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
}
To implement Module Federation v2.0 with rspack we modify the ./rsbuild.config.ts
file.
- import
ModuleFederationPlugin
from@module-federation/enhanced/rspack
- extend the
rsbuild
configuration
Our implementation is a Micro-Frontend consumer and will run on port 3000 in the development environment. We add this configuration to the rsbuild configuration that is exported by defining the server
object. Refer to the rsbuild server.host documentation.
To implement the @module-federation/enhanced
plugin we define the tools:rspack
object of the rsbuild configuration. Refer to the rsbuild tools.rspack documentation.
Our implementation includes two remotes and shares refact
and react-dom
. These are Module Federation concepts explained in detail within the rsbuild moduleFederation.options documentation.
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { ModuleFederationPlugin } from '@module-federation/enhanced';
export default defineConfig({
server: { port: 3000 },
tools: {
rspack: (_, { appendPlugins }) => {
appendPlugins([
new ModuleFederationPlugin({
name: 'host',
remotes: {
remote_one: 'remote_one@http://localhost:3001/mf-manifest.json',
remote_two: 'remote_two@http://localhost:3002/mf-manifest.json',
},
shared: ['react', 'react-dom'],
}),
]);
},
},
plugins: [pluginReact()],
});
This project is bootstrapped with Jest and testing-library/react.
pnpm test
To find documentation on command line options for Jest run the following:
pnpm test -- -h
- This project is a Module Federation consumer. READ THE DOCS.
- Run the application in localdev BEFORE trying to implement anything new provided by a remote MFE.
- Make sure the project remote dependencies are up and available before running the host MFE local development environment.
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.
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.
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 (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.
Refers to module bundling tools such as Rspack, Webpack.
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 requires by the application and then packages all modules into one or more bundles.
I have implemented a solution that supports moving the remotes
portion of the configuration out to either an environment variable or a .json file. When the environment variable is set the value takes precedence over the remotes.dev.json
file. I did this so that environment variables could be set within build pipelines allowing for a clean developer experience without the need to configure environment variables on the local host.
// remotes.dev.json
{
"remote_one": "http://localhost:3001",
"remote_two": "http://localhost:3002"
}
export REMOTES="{\"remote_one\":\"http://localhost:3001\",\"remote_two\":\"http://localhost:3002\"}"
Related code
// rsbuild.config.ts
// probably a better way to do this but it works and it is testable
const getRemotes = (envRemotes: string | undefined) => {
if (envRemotes !== undefined) {
console.warn('REMOTES FROM ENV');
return JSON.parse(envRemotes);
}
console.warn('REMOTES FROM DEV');
// rsbuild includes native support for json serialization
return require('./remotes.dev.json');
};