The module federation team cared deeply about the DevX in their latest release.
The purpose of this blog is to go through setting up the project, and explain a little on each decision being taken here.
Github Repo:
Wanna see the full code, check this app out:
https://github.com/IbrahimShamma99/microfrontend-rspack-module-federation-v2
Tech Stack
pnpm
This package manager recently became my favorite, for many reasons, including how to handles non flattened node_modules which helps in resolving security vulnerabilities effectively.
Another thing amazing about pnpm which how it simply you create monorepos, without the need to read much of docs like other solutions.
For more info about this amazing package manager I highly recommend this article published by refine.dev
React
Very obvious choice, though I will add vue into the mix soon.
Typescript
Consumer apps in MF V2 generates types out of remotes and being stored by default into @mf-types directory, we are using TS to demonstrate that.
Rspack
In previous blogs we were using webpack, but ever since rspack hit the V1 mark I have been migrating every app I work with into rspack, why? same webpack configuration but with faster and out of the box support for code splitting and module federation, what could you ask for more?
some members webpack core team are the ones building rspack which they have the experience and architecture knowledge transfered into the new bunder rspack should not be looked at as a V1 package, instead it is a continuation to what webpack is.
Setup project
Initialize dir:
First we are going to create the directory and setup package.json.
mkdir ./out-project-name
cd ./out-project-name
pnpm init
Add ESM Config:
We want to make sure our package is ESM based, not CommonJS, so we do the following modification to package.json
npm pkg set type="module"
For more info check this out
Setup Engine and Package manager:
Simply run the following two commands
npm pkg set engines.node=">=22.10.0"
npm pkg set packageManager="pnpm@9.15.1"
Add pnpm worksapce
Run this touch command:
touch pnpm-workspace.yaml
it will create pnpm-workspace.yaml
config file which we will add to it the following yaml
config:
packages:
- "apps/*"
- "packages/*"
In respect to each will mean:
- apps: Consumed and Consumer apps which run on specific port.
- packages: shared slices of code between different apps, it could be UI libraries or communication with backends etc.
Create apps
mkdir apps
cd ./apps
# lets create two react typescript app one named header and other named main
pnpm create rsbuild@latest
cd ../
Install module federation:
Command:
pnpm add @module-federation/enhanced --workspace-root
pnpm add @module-federation/rsbuild-plugin --save-dev --workspace-root
We are adding --workspace-root flag stating that this package is a global package that can be shared between apps, this is helpful for having single place to manage global dependency versioning
Setup Consumed app
going into apps/header/rsbuild.config.ts
we would find rsbuild
configuration
rsbuild is the build tool for rspack.
Let's add the following code:
// src/Header.tsx
export default function Header() {
return (
<div>
<h1>Main Header</h1>
</div>
);
}
// apps/header/rsbuild.config.ts
import { defineConfig } from "@rsbuild/core";
import { pluginReact } from "@rsbuild/plugin-react";
import { pluginModuleFederation } from "@module-federation/rsbuild-plugin";
export default defineConfig({
server: {
port: 3001,
},
plugins: [
pluginReact(),
pluginModuleFederation({
name: "header",
exposes: {
"./header": "./src/Header.tsx",
},
shared: ["react", "react-dom"],
}),
],
});
Explanation:
It is much like the configuration we explained before here
Configuration is pretty much similar to previous MF versions.
Setup Consumer app:
// src/FallbackHeader.tsx
export function FallbackHeader() {
return (
<div>
<h1>Fallback Header</h1>
</div>
);
}
// src/App.tsx
import { FallbackHeader } from "./FallbackHeader";
import { Suspense } from "react";
import Header from "header/header";
const App = () => {
return (
<div className="content">
<Suspense fallback={<FallbackHeader />}>
<Header />
</Suspense>
<h1>Main App</h1>
</div>
);
};
export default App;
Want to learn more about why we adding a fallback Header with the suspense? please checkout part 5 & 6 of this series:
Part 5
Part 6
running the app:
One of the true powers of pnpm is the filter
flag
which let root package.json exposes internal monorepo scripts
ROOT/package.json
{
"scripts": {
"header": "pnpm --filter ./apps/header",
"main": "pnpm --filter ./apps/main"
},
}
and going into root app and run the following commands each on separate terminal:
pnpm header dev
pnpm main dev
And your app is running!
Top comments (0)