Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 834122a

Browse files
feat: single runtime example (#4317)
* feat: single runtime example * upd * feat: single runtime example * feat: single runtime example * fix: add e2e tests * lint-staged * fix: add e2e tests * cix: update app and e2e * cix: update app and e2e * fix: update app and e2e * fix: update app and e2e
1 parent d288f00 commit 834122a

File tree

21 files changed

+2497
-910
lines changed

21 files changed

+2497
-910
lines changed

‎package.json‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,14 @@
195195
"forever": "4.0.3",
196196
"husky": "9.0.11",
197197
"jest": "29.7.0",
198+
"js-yaml": "4.1.0",
198199
"lerna": "8.1.8",
200+
"lint-staged": "^15.2.10",
199201
"mocha": "10.6.0",
200202
"prettier": "3.3.3",
201203
"pretty-quick": "4.0.0",
202-
"typescript": "5.5.3",
203-
"js-yaml": "4.1.0",
204-
"semver": "7.6.3"
204+
"semver": "7.6.3",
205+
"typescript": "5.5.3"
205206
},
206207
"scripts": {
207208
"list:all": "pnpm list --filter \"*\" --only-projects --depth -1 --json",

‎pnpm-lock.yaml‎

Lines changed: 1310 additions & 395 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,64 @@
11
# Controlled Vendor Sharing
22

3-
Dynamic Vendor Sharing is an application that implements a control panel in the runtime plugin for module federation 1.5 in rspack or `@module-federation/enhanced`. The control panel allows you to deterministically manage and modify the rules for shared modules, as well as upgrade or downgrade applications based on the inputs from the React form.
3+
This example demonstrates a runtime plugin implementation for Module Federation that provides dynamic control over shared module versions. It allows you to deterministically manage and modify shared module versions across federated applications using a control panel interface and localStorage persistence.
44

55
## Features
66

7-
- Runtime plugin that implements rules for module sharing.
8-
- React form for modifying the rules.
9-
- Ability to upgrade or downgrade applications.
10-
- `app1` and `app2` exposing different button components.
7+
- Runtime plugin for dynamic version control of shared modules
8+
- Control panel UI for managing shared module versions
9+
- Persistent version settings using localStorage (`formDataVMSC` key)
10+
- Support for upgrading/downgrading shared module versions
11+
- E2E tests to verify version control functionality
1112

1213
## Main Components
1314

14-
### `./app1/control-share.ts`
15+
### `control-share.ts`
1516

16-
This is the runtime plugin that implements the rules for module federation.
17+
A runtime plugin that implements version control for Module Federation. Key features:
18+
- Implements the `FederationRuntimePlugin` interface
19+
- Uses localStorage to persist version preferences
20+
- Handles version resolution and module sharing between applications
21+
- Manages share scope mapping and instance tracking
1722

18-
### `./app1/src/ControlPanel.js`
23+
### E2E Tests (`checkAutomaticVendorApps.cy.ts`)
1924

20-
This is a React form that allows for the modification of rules implemented in `control-share.ts`.
25+
Comprehensive E2E tests that verify:
26+
- Initial shared module versions
27+
- Version override functionality through localStorage
28+
- UI updates reflecting version changes
29+
- Button component rendering with correct version information
2130

22-
# Running Demo
31+
## Running Demo
2332

2433
Run `pnpm run start`. This will build and serve both `app1` and `app2` on ports 3001 and 3002 respectively.
2534

26-
- [localhost:3001](http://localhost:3001/)
27-
- [localhost:3002](http://localhost:3002/)
35+
- [localhost:3001](http://localhost:3001/) - Host application with control panel
36+
- [localhost:3002](http://localhost:3002/) - Remote application
2837

29-
# Running Cypress E2E Tests
38+
## Running Cypress E2E Tests
3039

31-
To run tests in interactive mode, run `npm run cypress:debug` from the root directory of the project. It will open Cypress Test Runner and allow to run tests in interactive mode. [More info about "How to run tests"](../../cypress/README.md#how-to-run-tests)
40+
To run tests in interactive mode:
41+
```bash
42+
npm run cypress:debug
43+
```
3244

33-
To build app and run test in headless mode, run `yarn e2e:ci`. It will build app and run tests for this workspace in headless mode. If tets failed cypress will create `cypress` directory in sample root folder with screenshots and videos.
45+
To run tests in headless mode:
46+
```bash
47+
yarn e2e:ci
48+
```
3449

35-
["Best Practices, Rules amd more interesting information here](../../cypress/README.md)
50+
For failed tests, screenshots and videos will be saved in the `cypress` directory.
51+
52+
## Implementation Details
53+
54+
The control panel allows you to:
55+
- View current versions of shared modules (react, react-dom, lodash)
56+
- Override versions for specific applications
57+
- Save settings to localStorage
58+
- Clear settings and reload to default versions
59+
60+
The runtime plugin (`control-share.ts`) handles:
61+
- Version resolution based on localStorage settings
62+
- Share scope management
63+
- Instance tracking and updates
64+
- Cross-application module sharing rules

‎runtime-plugins/control-sharing/app1/src/App.js‎

Lines changed: 119 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,129 @@ import ReactDOM from 'react-dom';
44
import RemoteButton from 'app2/Button';
55
import lodash from 'lodash';
66
import ControlPanel from './ControlPanel';
7+
8+
const styles = {
9+
container: {
10+
padding: '2rem',
11+
maxWidth: '1200px',
12+
margin: '0 auto',
13+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
14+
},
15+
header: {
16+
borderBottom: '2px solid #e9ecef',
17+
marginBottom: '2rem',
18+
paddingBottom: '1rem',
19+
},
20+
title: {
21+
fontSize: '2.5rem',
22+
color: '#2c3e50',
23+
margin: '0 0 1rem 0',
24+
},
25+
subtitle: {
26+
fontSize: '1.8rem',
27+
color: '#34495e',
28+
margin: '1rem 0',
29+
},
30+
versionInfo: {
31+
display: 'flex',
32+
flexDirection: 'column',
33+
gap: '0.5rem',
34+
backgroundColor: '#f8f9fa',
35+
padding: '1.5rem',
36+
borderRadius: '8px',
37+
marginBottom: '2rem',
38+
boxShadow: '0 2px 4px rgba(0,0,0,0.05)',
39+
},
40+
versionText: {
41+
margin: '0',
42+
fontSize: '1rem',
43+
fontWeight: '500',
44+
display: 'flex',
45+
alignItems: 'center',
46+
gap: '0.5rem',
47+
},
48+
dot: {
49+
width: '8px',
50+
height: '8px',
51+
borderRadius: '50%',
52+
display: 'inline-block',
53+
marginRight: '8px',
54+
},
55+
buttonContainer: {
56+
display: 'flex',
57+
gap: '1rem',
58+
marginBottom: '2rem',
59+
},
60+
};
61+
762
const getColorFromString = str => {
8-
let primes = [1, 2, 3, 5, 7, 11, 13, 17, 19, 23];
63+
const colors = [
64+
'#F44336', // red
65+
'#2196F3', // blue
66+
'#4CAF50', // green
67+
'#9C27B0', // purple
68+
'#E91E63', // pink
69+
'#FF9800', // orange
70+
'#03A9F4', // light blue
71+
'#009688', // teal
72+
'#8BC34A', // light green
73+
'#AB47BC' // medium purple
74+
];
75+
976
let hash = 0;
1077
for (let i = 0; i < str.length; i++) {
11-
hash += str.charCodeAt(i) * primes[i % primes.length];
12-
}
13-
let color = '#';
14-
for (let i = 0; i < 3; i++) {
15-
const value = (hash >> (i * 8)) & 0xff;
16-
color += ('00' + value.toString(16)).substr(-2);
78+
hash = ((hash << 5) - hash) + str.charCodeAt(i);
79+
hash = hash & hash; // Convert to 32-bit integer
1780
}
18-
return color;
81+
82+
hash = Math.abs(hash);
83+
84+
return colors[hash % colors.length];
85+
};
86+
87+
const App = () => {
88+
const reactColor = getColorFromString(React.version);
89+
const reactDomColor = getColorFromString(ReactDOM.version);
90+
const lodashColor = getColorFromString(lodash.VERSION);
91+
92+
return (
93+
<div style={styles.container}>
94+
<header style={styles.header}>
95+
<h1 style={styles.title}>Share Control Panel</h1>
96+
<h2 style={styles.subtitle}>App 1</h2>
97+
</header>
98+
99+
<div style={styles.versionInfo}>
100+
<h4 style={{ ...styles.versionText, color: reactColor }}>
101+
Host Used React: {React.version}
102+
</h4>
103+
<h4 style={{ ...styles.versionText, color: reactDomColor }}>
104+
Host Used ReactDOM: {ReactDOM.version}
105+
</h4>
106+
<h4 style={{ ...styles.versionText, color: lodashColor }}>
107+
Host Used Lodash: {lodash.VERSION}
108+
</h4>
109+
</div>
110+
111+
<div style={styles.buttonContainer}>
112+
<LocalButton />
113+
<React.Suspense fallback={
114+
<div style={{
115+
padding: '0.5rem 1rem',
116+
backgroundColor: '#f8f9fa',
117+
borderRadius: '4px',
118+
color: '#666'
119+
}}>
120+
Loading Button...
121+
</div>
122+
}>
123+
<RemoteButton />
124+
</React.Suspense>
125+
</div>
126+
127+
<ControlPanel />
128+
</div>
129+
);
19130
};
20-
const App = () => (
21-
<div>
22-
<h1>Share Control Panel</h1>
23-
<h2>App 1</h2>
24-
<h4 style={{ color: getColorFromString(React.version) }}>Host Used React: {React.version}</h4>
25-
<h4 style={{ color: getColorFromString(ReactDOM.version) }}>
26-
Host Used ReactDOM: {ReactDOM.version}
27-
</h4>
28-
<h4 style={{ color: getColorFromString(lodash.VERSION) }}>
29-
Host Used Lodash: {lodash.VERSION}
30-
</h4>
31-
32-
<LocalButton />
33-
<React.Suspense fallback="Loading Button">
34-
<RemoteButton />
35-
</React.Suspense>
36-
<ControlPanel />
37-
</div>
38-
);
39131

40132
export default App;
Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
11
import React from 'react';
22

33
const style = {
4-
background: '#800',
4+
background: '#ff4444',
55
color: '#fff',
6-
padding: 12,
6+
padding: '12px 24px',
7+
border: 'none',
8+
borderRadius: '6px',
9+
fontSize: '16px',
10+
fontWeight: '600',
11+
cursor: 'pointer',
12+
transition: 'all 0.2s ease',
13+
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
714
};
815

9-
const Button = () => <button style={style}>App 1 Button</button>;
16+
const Button = () => (
17+
<button
18+
style={style}
19+
onMouseEnter={(e) => {
20+
e.target.style.transform = 'translateY(-2px)';
21+
e.target.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
22+
e.target.style.background = '#ff5555';
23+
}}
24+
onMouseLeave={(e) => {
25+
e.target.style.transform = 'translateY(0)';
26+
e.target.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
27+
e.target.style.background = '#ff4444';
28+
}}
29+
>
30+
App 1 Button
31+
</button>
32+
);
1033

1134
export default Button;

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /