Skip to content
Closed
Show file tree
Hide file tree
Changes from 75 commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
1d1bd34
examples/federation: migrate to Rspack-only, Node-friendly federation
ScriptedAlchemy Jan 7, 2026
260a724
examples/federation: lint cleanup and react import fixes
ScriptedAlchemy Jan 7, 2026
82d0d37
examples/federation: enhance SSR federated tests and fixes\n\n- Imple…
ScriptedAlchemy Jan 7, 2026
9113b6b
feat(core): first-class module federation mode
ScriptedAlchemy Jan 7, 2026
385b04b
fix(core): update mock runtime code and module loader to support MF t…
ScriptedAlchemy Jan 7, 2026
d09a068
chore(main-app): configure MF for test runner (node runtime)
ScriptedAlchemy Jan 7, 2026
9703611
chore(main-app): remove legacy runtimePlugin shim
ScriptedAlchemy Jan 7, 2026
a3992f4
feat(examples): add node-local-remote remote with test expose
ScriptedAlchemy Jan 7, 2026
46c43d0
test(federation): add SSR and dynamic import tests; update e2e and su…
ScriptedAlchemy Jan 7, 2026
ebe2024
feat(component-app): refine components and configs for federation SSR…
ScriptedAlchemy Jan 7, 2026
a2b678d
docs(federation): update README for SSR and node remote setup
ScriptedAlchemy Jan 7, 2026
61fdda8
chore(main-app): define __NODE_LOCAL_REMOTE__ for tests
ScriptedAlchemy Jan 7, 2026
7b8809d
test(main-app): remove obsolete sum.test
ScriptedAlchemy Jan 7, 2026
bcecaa0
chore(tests): ignore lowercase duplicate test files
ScriptedAlchemy Jan 7, 2026
79faf3c
chore(main-app): sync package metadata and lockfile via pnpm dedupe
ScriptedAlchemy Jan 7, 2026
728777d
feat(core): update federation plugin; test(main-app): add RTL test wi…
ScriptedAlchemy Jan 7, 2026
08cd6ee
chore: clean up gitignore by removing test files
ScriptedAlchemy Jan 7, 2026
d4da327
federation(main-app): RTL test with dynamic import, upgrade examples …
ScriptedAlchemy Jan 7, 2026
576cfa1
fix(federation): stabilize example setup
ScriptedAlchemy Jan 7, 2026
fb5b55c
examples(federation): rename browser configs to rspack.browser.config…
ScriptedAlchemy Jan 7, 2026
16ce118
examples(federation): jsdom-only rstest, remove node project; rename …
ScriptedAlchemy Jan 7, 2026
98f4d03
fix(federation): improve test setup with better port management and d…
ScriptedAlchemy Jan 7, 2026
cfe7ee9
fix(federation): set output.module to false for CommonJS compatibility
ScriptedAlchemy Jan 7, 2026
57b1089
fix(core): federation compatibility cleanup
ScriptedAlchemy Jan 8, 2026
955eeea
refactor(core): centralize federation external rules
ScriptedAlchemy Jan 8, 2026
0da8730
fix(federation): keep splitChunks enabled
ScriptedAlchemy Jan 8, 2026
45dbd16
examples(federation): serve component-app on 3001 only; remove node s…
ScriptedAlchemy Jan 8, 2026
334125d
examples(federation): speed up setup by checking port 3001 before kil…
ScriptedAlchemy Jan 8, 2026
3f566a1
examples(federation/main-app): consolidate to single rstest.setup.ts …
ScriptedAlchemy Jan 8, 2026
3db3a21
examples(federation/main-app): remove blind pretest kill-port; rely o…
ScriptedAlchemy Jan 8, 2026
bea2f6e
examples(federation): fix duplicate library property; enforce commonj…
ScriptedAlchemy Jan 8, 2026
7fb095e
core(federation): set output.module=false via environment config; kee…
ScriptedAlchemy Jan 8, 2026
dd84fef
chore: commit changes
ScriptedAlchemy Jan 8, 2026
960563e
chore: sync lockfile and licenses
ScriptedAlchemy Jan 8, 2026
0a41f60
fix(federation): stabilize remote startup and node target
ScriptedAlchemy Jan 8, 2026
eb437ca
chore(federation): share react singletons
ScriptedAlchemy Jan 8, 2026
1834273
fix(core): gate federation plugin + mf target
ScriptedAlchemy Jan 8, 2026
1436122
chore(examples): set mf optimization target
ScriptedAlchemy Jan 8, 2026
2353eaa
Merge branch 'main' into feat/federation
ScriptedAlchemy Jan 8, 2026
63e5703
fix(core): avoid await thenable in federation plugin
ScriptedAlchemy Jan 8, 2026
53ca3a5
Merge branch 'main' into feat/federation
ScriptedAlchemy Jan 8, 2026
93f0909
examples(federation): restore component-app rspack.node.config.js fro…
ScriptedAlchemy Jan 8, 2026
cda85e1
chore(examples): update react rstest config
ScriptedAlchemy Jan 8, 2026
b369f66
chore(examples): add rspack node config
ScriptedAlchemy Jan 8, 2026
91524c2
Merge branch 'main' into feat/federation
ScriptedAlchemy Jan 8, 2026
5742c56
fix(federation): set output.module to false for CommonJS compatibility
ScriptedAlchemy Jan 7, 2026
cc56574
test(core): normalize pnpm store paths in snapshots
ScriptedAlchemy Jan 8, 2026
c1d39e9
chore(scripts): simplify snapshot serializer
ScriptedAlchemy Jan 8, 2026
e4cbeb9
test: normalize global pnpm store paths
ScriptedAlchemy Jan 8, 2026
7190c82
chore(scripts): clarify pnpm store comment
ScriptedAlchemy Jan 8, 2026
19120e5
fix(examples): make federation example a projects umbrella
ScriptedAlchemy Jan 8, 2026
26616e5
fix(core): remove ENV_TARGET federation shim
ScriptedAlchemy Jan 8, 2026
b38a1be
Merge branch 'main' into feat/federation
ScriptedAlchemy Jan 8, 2026
fc7f655
test(core): update rsbuild snapshots
ScriptedAlchemy Jan 8, 2026
ff17edc
docs(core): clarify rstest dynamic import binding
ScriptedAlchemy Jan 8, 2026
eb9c36d
chore(core): use logger for federation warnings
ScriptedAlchemy Jan 8, 2026
19fcd34
fix(core): stabilize e2e errors and snapshot paths
ScriptedAlchemy Jan 8, 2026
ff40a31
fix(test): normalize pnpm inner paths in snapshots
ScriptedAlchemy Jan 8, 2026
9153ec4
fix(federation): build node-local remote in example tests
ScriptedAlchemy Jan 8, 2026
519a0e4
fix(federation): run pnpm build in globalSetup on Windows
ScriptedAlchemy Jan 8, 2026
432678d
fix(core): gate federation shims to federation mode
ScriptedAlchemy Jan 9, 2026
861b5ea
Merge branch 'main' into feat/federation
ScriptedAlchemy Jan 9, 2026
947f804
fix(core): enable federation shims in global setup
ScriptedAlchemy Jan 9, 2026
2bbae71
Merge branch 'main' into feat/federation
ScriptedAlchemy Jan 9, 2026
07c03dd
Merge remote-tracking branch 'origin/main' into feat/federation
ScriptedAlchemy Jan 9, 2026
2637876
fix(core): stabilize snapshots and handled errors
ScriptedAlchemy Jan 9, 2026
ff5cec3
fix(core): satisfy lint ban-types
ScriptedAlchemy Jan 9, 2026
5a28054
fix(core): support federation without writeToDisk
ScriptedAlchemy Jan 10, 2026
f4292c5
fix(core): scope virtual FS to federation runs
ScriptedAlchemy Jan 10, 2026
8c4fca5
fix(core): make virtual FS buildable
ScriptedAlchemy Jan 10, 2026
a974e70
merge: origin/main into feat/federation
ScriptedAlchemy Jan 20, 2026
8e3a9ab
feat(core): expose federation rsbuild plugin
ScriptedAlchemy Jan 21, 2026
395e031
Merge remote-tracking branch 'origin/main' into feat/federation
ScriptedAlchemy Jan 21, 2026
1a7624d
fix(core): guard virtual fs uri decoding
ScriptedAlchemy Jan 21, 2026
2f02972
merge: origin/main into feat/federation
ScriptedAlchemy Jan 23, 2026
087155e
fix(core): address federation review comments
ScriptedAlchemy Jan 23, 2026
5d8272c
chore: fix cspell for virtual fs test
ScriptedAlchemy Jan 23, 2026
2cddcb4
fix(examples): avoid dynamic import in federation globalSetup
ScriptedAlchemy Jan 23, 2026
2143b53
fix(core): make virtual fs path lookup windows-safe
ScriptedAlchemy Jan 23, 2026
a93a196
Merge remote-tracking branch 'origin/main' into feat/federation
ScriptedAlchemy Jan 24, 2026
1eeac83
merge: origin/main into feat/federation
ScriptedAlchemy Jan 24, 2026
5a0f317
feat(federation): merge main and refine federation handling
ScriptedAlchemy Jan 29, 2026
952376d
merge: resolve conflicts with origin/main for PR #842
ScriptedAlchemy Feb 10, 2026
05481d4
fix(core): move federation external bypass into federation plugin (#962)
9aoy Feb 16, 2026
bc3164f
refactor(core): consume external federation plugin package
ScriptedAlchemy Feb 16, 2026
03c5e64
refactor(core): use rstest-plugin auto-apply federation API
ScriptedAlchemy Feb 16, 2026
d21ba9f
merge: sync feat/federation with origin/main
ScriptedAlchemy Mar 18, 2026
445ab73
fix(federation): use async startup in example configs
ScriptedAlchemy Mar 19, 2026
884f76f
fix(federation): declare script remote type for HTTP remotes
ScriptedAlchemy Mar 19, 2026
0fb0365
Merge remote-tracking branch 'origin/main' into feat/federation
ScriptedAlchemy Mar 19, 2026
9b8e22d
fix(core): harden federation worker loading
ScriptedAlchemy Mar 19, 2026
9f3b683
fix(federation): update rstest canary in examples
ScriptedAlchemy Mar 19, 2026
82868b6
fix(core): remove federation output.module default
ScriptedAlchemy Mar 19, 2026
01ddb7a
chore(federation): drop asyncStartup overrides
ScriptedAlchemy Mar 19, 2026
04f50e6
refactor(core): reduce federation branch churn
ScriptedAlchemy Mar 20, 2026
345f8db
refactor(core): minimize federation package diff
ScriptedAlchemy Mar 20, 2026
1725324
chore(federation): remove example playwright config
ScriptedAlchemy Mar 20, 2026
c92e922
chore: revert biome ignore override
ScriptedAlchemy Mar 20, 2026
8ae9383
chore(examples): normalize federation package names
ScriptedAlchemy Mar 20, 2026
a5cd3f8
Merge branch 'main' into feat/federation
ScriptedAlchemy Mar 20, 2026
e18d3a6
fix(browser): restore runtime config compatibility
ScriptedAlchemy Mar 21, 2026
0126a4c
Merge remote-tracking branch 'origin/main' into feat/federation
Jun 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.log*
*.cpuprofile
node_modules/
examples/federation/node_modules/

dist/
dist-*
Expand Down Expand Up @@ -43,4 +44,4 @@ fixtures-test-*/
tests-dist/
.vscode-test/
packages/vscode/*.vsix
packages/vscode/icon.png
packages/vscode/icon.png
18 changes: 15 additions & 3 deletions e2e/dom/fixtures/test/handledError.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,21 @@ test('should handle click error', async () => {

const element = screen.getByText('Rsbuild with React');

window.addEventListener('error', (event) => {
expect(event.message).toBe('click error');
await new Promise<void>((resolve) => {
window.addEventListener(
'error',
(event) => {
expect(event.message).toBe('click error');
event.preventDefault();
// Some DOM implementations (e.g. happy-dom) don't reflect preventDefault()
// via `defaultPrevented`; this ensures frameworks can treat it as handled.
(event as any).returnValue = false;
resolve();
},
{ once: true },
);
element.click();
});

element.click();
// Ensure the assertion above is reached before the test completes.
});
8 changes: 8 additions & 0 deletions examples/federation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules/
dist/
dist-*/
.rstest-*
.rstest-temp/
.rstest-mf-node-remote.lock
.DS_Store

32 changes: 32 additions & 0 deletions examples/federation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# react-webpack-MF

[中文](./README_zh-cn.md)

A complete Webpack Module Federation Case with React.

# project directory

## lib-app

Removed in this simplified example.

## component-app

It exposes UI components to `main-app` via Module Federation.

It is a pure `remote`.

## main-app

The top-level app, which depends on `component-app`.

It is a pure host.

# how to use

- `pnpm install`
- `pnpm run start`

After running these commands, open your browser at `http://localhost:3002` and open the DevTools network tab to see resource loading details.

[Best practices, rules and more interesting information here](../../playwright-e2e/README.md)
30 changes: 30 additions & 0 deletions examples/federation/README_zh-cn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# react-webpack-MF

[English](./README.md)

一个相对完整的应用`Webpack Module Federation`的 React 项目案例

# 目录结构

## lib-app

该示例已简化,不再包含 `lib-app`。

## component-app

组件层 App,通过 Module Federation 暴露组件给 `main-app` 使用。

它是一个纯粹的 `remote`。

## main-app

上层 App,依赖 `component-app` 应用。它也是一个纯粹的 `host`。

# 如何使用

- `pnpm install`
- `pnpm run start`

执行完上述命令,打开浏览器,输入 `http://localhost:3002` 查看页面结果。

[最佳实践、规则和更多信息请参阅](../../playwright-e2e/README.md)
43 changes: 43 additions & 0 deletions examples/federation/component-app/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import Button from './src/Button';
import Dialog from './src/Dialog';
import Logo from './src/Logo';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dialogVisible: false,
};
this.handleClick = this.handleClick.bind(this);
this.handleSwitchVisible = this.handleSwitchVisible.bind(this);
}
handleClick(ev) {
console.log(ev);
this.setState({
dialogVisible: true,
});
}
handleSwitchVisible(visible) {
this.setState({
dialogVisible: visible,
});
}
render() {
return (
<div>
<Logo />
<br />
<Button />
<br />

<button type="button" onClick={this.handleClick}>
click to open dialog
</button>
<Dialog
switchVisible={this.handleSwitchVisible}
visible={this.state.dialogVisible}
/>
</div>
);
}
}
5 changes: 5 additions & 0 deletions examples/federation/component-app/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('app'));
1 change: 1 addition & 0 deletions examples/federation/component-app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import('./bootstrap.js');
32 changes: 32 additions & 0 deletions examples/federation/component-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "rstest_example_component-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "rspack build --config rspack.browser.config.js ",
"build:node": "rspack build --config rspack.node.config.js",
"serve": "serve dist -p 3001",
"serve:node": "serve dist-node -p 3001",
"start": "concurrently \"npm run build\" \"npm run serve\"",
"pretest": "pnpm -C ../node-local-remote build:node",
"test": "rstest --reporter=verbose"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@module-federation/enhanced": "0.22.0",
"@rstest/core": "workspace:*",
"@rsbuild/plugin-react": "^1.4.2",
"@rspack/cli": "1.6.8",
"@rspack/core": "1.6.8",
"concurrently": "8.2.2",
"serve": "14.2.3"
},
"dependencies": {
"@module-federation/node": "^2.7.26",
"react": "^19.2.3",
"react-dom": "^19.2.3"
}
}
12 changes: 12 additions & 0 deletions examples/federation/component-app/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
55 changes: 55 additions & 0 deletions examples/federation/component-app/rspack.browser.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const { HtmlRspackPlugin } = require('@rspack/core');
const {
ModuleFederationPlugin,
} = require('@module-federation/enhanced/rspack');

module.exports = {
entry: './index.js',
mode: 'development',
devtool: 'hidden-source-map',
output: {
publicPath: 'http://localhost:3001/',
clean: true,
},
resolve: {
extensions: ['.jsx', '.js', '.json', '.wasm'],
},
experiments: {
css: true,
},
module: {
rules: [
{ test: /\.(jpg|png|gif|jpeg)$/, type: 'asset/resource' },
{
test: /\.(js|jsx)$/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: { syntax: 'ecmascript', jsx: true },
transform: { react: { runtime: 'automatic' } },
},
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'component_app',
filename: 'remoteEntry.js',
library: { type: 'var', name: 'component_app' },
exposes: {
'./Button': './src/Button.jsx',
'./Dialog': './src/Dialog.jsx',
'./Logo': './src/Logo.jsx',
'./ToolTip': './src/ToolTip.jsx',
},
shared: {
react: { singleton: true, requiredVersion: '19.2.3' },
'react-dom': { singleton: true, requiredVersion: '19.2.3' },
},
}),
new HtmlRspackPlugin({ template: './public/index.html' }),
],
};
81 changes: 81 additions & 0 deletions examples/federation/component-app/rspack.node.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const {
ModuleFederationPlugin,
} = require('@module-federation/enhanced/rspack');
const path = require('node:path');

/**
* Node-targeted remote build used by Rstest's Module Federation test.
* This differs from the browser build (rspack.config.js):
* - target: async-node
* - includes a Node runtime plugin so remote chunks can be loaded over HTTP
* - outputs to dist-node (served on a separate port from the browser remote)
*/
module.exports = {
entry: './index.js',
mode: 'development',
devtool: 'hidden-source-map',
target: 'async-node',
output: {
// The Node remote runtime resolves chunk URLs relative to the remoteEntry URL
// when publicPath is explicitly set. `auto` can throw in non-browser contexts.
publicPath: 'http://localhost:3001/',
clean: true,
path: path.resolve(__dirname, 'dist-node'),
},
resolve: {
extensions: ['.jsx', '.js', '.json', '.wasm'],
},
experiments: {
css: true,
},
module: {
rules: [
{
test: /\.(jpg|png|gif|jpeg)$/,
type: 'asset/resource',
},
{
test: /\.(js|jsx)$/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'ecmascript',
jsx: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'component_app',
filename: 'remoteEntry.js',
// This remote is consumed by Rstest using `remoteType: 'script'` while tests
// run under JSDOM. We force the MF runtime to use its Node loader (vm eval),
// so the remoteEntry must export through CommonJS for the loader to return
// the container interface (get/init).
library: { type: 'commonjs-module', name: 'component_app' },
// Required for async-node remotes that load chunks over HTTP in Node.
runtimePlugins: ['@module-federation/node/runtimePlugin'],
exposes: {
'./Button': './src/Button.jsx',
'./Dialog': './src/Dialog.jsx',
'./Logo': './src/Logo.jsx',
'./ToolTip': './src/ToolTip.jsx',
},
shared: {
react: { singleton: true, requiredVersion: '19.2.3' },
'react-dom': { singleton: true, requiredVersion: '19.2.3' },
},
}),
],
};
45 changes: 45 additions & 0 deletions examples/federation/component-app/rstest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import path from 'node:path';
import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack';
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig, federation } from '@rstest/core';

export default defineConfig({
globalSetup: ['./scripts/rstestGlobalSetup.ts'],
testEnvironment: 'node',
plugins: [pluginReact(), federation()],
testTimeout: 15000,
federation: true,
tools: {
rspack: (config) => {
config.plugins ??= [];
config.plugins.push(
new ModuleFederationPlugin({
name: 'component_app_node_test',
library: { type: 'commonjs-module', name: 'component_app_node_test' },
remoteType: 'commonjs',
remotes: {
'node-local-remote': `commonjs ${path.resolve(
__dirname,
'../node-local-remote/dist-node/remoteEntry.js',
)}`,
},
runtimePlugins: ['@module-federation/node/runtimePlugin'],
shared: {
react: { singleton: true, eager: true, requiredVersion: '19.2.3' },
'react-dom': {
singleton: true,
eager: true,
requiredVersion: '19.2.3',
},
},
experiments: {
optimization: {
target: 'node', // Required for Node.js test environment
},
},
}),
);
return config;
},
},
});
Loading
Loading