Skip to content

Commit 3aa3cf9

Browse files
authored
feat: make Module Federation on Node.js easy (#4314)
1 parent 813923c commit 3aa3cf9

File tree

25 files changed

+524
-114
lines changed

25 files changed

+524
-114
lines changed

.changeset/few-rules-own.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@module-federation/runtime-core': minor
3+
---
4+
5+
feat(runtim-coree): support load manifest in node env

.changeset/young-dots-carry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@module-federation/rsbuild-plugin': minor
3+
---
4+
5+
feat(rsbuild-plugin): support build node mf assets

apps/modern-component-data-fetch/provider-csr/rslib.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,8 @@ export default defineConfig({
2424
server: {
2525
port: 5003,
2626
},
27-
plugins: [pluginReact(), pluginModuleFederation(mfConfig, { ssr: true })],
27+
plugins: [
28+
pluginReact(),
29+
pluginModuleFederation(mfConfig, { target: 'dual' }),
30+
],
2831
});

apps/modernjs-ssr/dynamic-remote/rslib.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,8 @@ export default defineConfig({
4545
server: {
4646
port: 3053,
4747
},
48-
plugins: [pluginReact(), pluginModuleFederation(mfConfig, { ssr: true })],
48+
plugins: [
49+
pluginReact(),
50+
pluginModuleFederation(mfConfig, { target: 'dual' }),
51+
],
4952
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { createModuleFederationConfig } from '@module-federation/rsbuild-plugin';
2+
3+
export default createModuleFederationConfig({
4+
name: 'node_remote',
5+
filename: 'remoteEntry.js',
6+
exposes: {
7+
'./test': './src/expose.js',
8+
},
9+
remotes: {
10+
remote1: 'node_dynamic_remote@http://localhost:3026/remoteEntry.js',
11+
},
12+
dts: false,
13+
});

apps/node-remote/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
"private": true,
44
"dependencies": {
55
"@module-federation/enhanced": "workspace:*",
6+
"@module-federation/rsbuild-plugin": "workspace:*",
67
"@module-federation/node": "workspace:*"
8+
},
9+
"scripts": {
10+
"dev": "rslib mf-dev",
11+
"build": "rslib build"
12+
},
13+
"devDependencies": {
14+
"@rslib/core": "^0.9.0"
715
}
816
}

apps/node-remote/project.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@
5050
}
5151
]
5252
},
53+
"build:rslib": {
54+
"executor": "nx:run-commands",
55+
"options": {
56+
"commands": ["npm run build --prefix apps/node-remote"]
57+
}
58+
},
59+
"serve:rslib": {
60+
"executor": "nx:run-commands",
61+
"options": {
62+
"commands": ["npm run dev --prefix apps/node-remote"]
63+
},
64+
"dependsOn": [
65+
{
66+
"target": "build",
67+
"dependencies": true
68+
}
69+
]
70+
},
5371
"serve": {
5472
"executor": "@nx/webpack:dev-server",
5573
"dependsOn": [

apps/node-remote/rslib.config.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
2+
import { defineConfig } from '@rslib/core';
3+
import mfConfig from './module-federation.config.ts';
4+
5+
export default defineConfig({
6+
source: {
7+
entry: {
8+
main: './src/main.tsx',
9+
},
10+
},
11+
output: {
12+
cleanDistPath: true,
13+
},
14+
lib: [
15+
{
16+
dts: {
17+
bundle: false,
18+
},
19+
format: 'mf',
20+
output: {
21+
distPath: {
22+
root: './dist/mf',
23+
},
24+
},
25+
},
26+
],
27+
server: {
28+
port: 3022,
29+
},
30+
tools: {
31+
rspack: {
32+
optimization: {
33+
minimize: false,
34+
},
35+
},
36+
},
37+
plugins: [pluginModuleFederation(mfConfig, { target: 'node' })],
38+
});
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
[
22
"announcement",
33
"hoisted-runtime",
4-
"error-load-remote"
4+
"error-load-remote",
5+
{
6+
"type": "file",
7+
"name": "node",
8+
"label": "Module Federation on Node.js, Made Easy"
9+
}
510
]
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# {props.name || 'Module Federation'} on Node.js, Made Easy
2+
3+
import RuntimeConsumerDemo from '@components/common/node/runtime-consumer-demo';
4+
import HostRspackConfig from '@components/common/node/host-rspack-config';
5+
import HostUsage from '@components/common/node/host-usage';
6+
import RemoteRslibConfig from '@components/common/node/remote-rslib-config';
7+
import RemoteRspackConfig from '@components/common/node/remote-rspack-config';
8+
9+
{props.name || 'Module Federation'} supports Node.js out of the box. Whether you are consuming modules at runtime only, or integrating into a Webpack/Rspack build pipeline, it can be adopted with a relatively small amount of configuration. This document walks through common ways to use {props.name || 'Module Federation'} in Node.js.
10+
11+
## Overview
12+
13+
In a Node.js server application, you can load remote modules via {props.name || 'Module Federation'}. These modules can be local files built in CommonJS format, or remote services accessed over HTTP. This enables a flexible foundation for server-side microservices, dynamic feature delivery, and shared resources.
14+
15+
## Consumer
16+
17+
### Runtime-Only
18+
19+
If you are only consuming modules in Node.js and do not want to introduce bundlers like Webpack/Rspack, you can use the runtime-only approach. The key idea is: **no build plugins required**. You only need the APIs provided by { props.runtime || '@module-federation/runtime' }.
20+
21+
Steps:
22+
23+
1. **Create an MF instance** with `createInstance`.
24+
2. **Register remotes** via the `remotes` array.
25+
3. **Load modules** via `loadRemote`.
26+
27+
The following example shows how to load a remote module exposed over HTTP:
28+
29+
{props.runtimeConsumerDemo || <RuntimeConsumerDemo />}
30+
31+
### Using a Bundler Plugin (Rspack/Webpack)
32+
33+
If your Node.js application is built with Webpack or Rspack, integrating {props.name || 'Module Federation'} is straightforward: add the plugin and the required runtime configuration.
34+
35+
For the Host (consumer), the key is to add `@module-federation/node/runtimePlugin` and set `remoteType: 'script'` and `target: 'async-node'`, then apply the rest of the configuration.
36+
37+
Rspack example (Webpack is largely the same):
38+
39+
{props.hostRspackConfig || <HostRspackConfig />}
40+
41+
After that, you can directly `import` remote modules in your code:
42+
43+
{props.hostUsage || <HostUsage />}
44+
45+
## Provider
46+
47+
On the producer side, we recommend using Rslib. You only need to use the { props.rsbuildPlugin || '@module-federation/rsbuild-plugin' } plugin and set `target: 'async-node'` to generate a remote that can be consumed in Node.js.
48+
49+
Key configuration from `apps/node-remote/rslib.config.ts`:
50+
51+
{props.remoteRslibConfig || <RemoteRslibConfig />}
52+
53+
If you are not using Rslib, you can also build the remote with Rspack/Webpack (the configuration is largely the same). The key points are: set `target` to `async-node`, and output `remoteEntry.js` with `library.type = 'commonjs-module'`.
54+
55+
Rspack example:
56+
57+
{props.remoteRspackConfig || <RemoteRspackConfig />}
58+
59+
Webpack configuration is largely the same as the Rspack example above.
60+
61+
## FAQ
62+
63+
### 1. What does `target: 'async-node'` do?
64+
65+
`target: 'async-node'` is a Webpack build target that produces output suitable for Node.js with asynchronous loading. This is important for Module Federation’s dynamic, async loading model—especially when you need top-level `await` while loading remotes.
66+
67+
### 2. Why do I need to set `remoteType: 'script'`?
68+
69+
Currently, the MF bundler runtime only supports the `script` remote loading type, so for Node.js consumption you need to explicitly set `remoteType: 'script'`.
70+
71+
## References
72+
73+
- **Host (Consumer) example config**: [`apps/node-host/webpack.config.js`](https://github.com/module-federation/core/blob/main/apps/node-host/webpack.config.js)
74+
- **Host (Consumer) example code**: [`apps/node-host/src/main.js`](https://github.com/module-federation/core/blob/main/apps/node-host/src/main.js)
75+
- **Remote (Producer) Rslib example config**: [`apps/node-remote/rslib.config.ts`](https://github.com/module-federation/core/blob/main/apps/node-remote/rslib.config.ts)
76+
- **Remote (Producer) Webpack example config**: [`apps/node-remote/webpack.config.js`](https://github.com/module-federation/core/blob/main/apps/node-remote/webpack.config.js)

0 commit comments

Comments
 (0)