Skip to content

Commit 839f7a5

Browse files
fix: modernize dynamic-remotes example implementation
Critical improvements to address identified issues: - **React 18 Upgrade**: Updated from 16.13.0 to 18.3.1 across all apps - Enhanced security and performance - Access to modern React features (Concurrent Mode, Suspense improvements) - Updated react-redux and redux to compatible versions - **Environment-based Configuration**: Replaced hardcoded URLs - Dynamic remote entry generation based on NODE_ENV - Support for REACT_APP_REMOTE_BASE_URL environment variable - Production deployment flexibility - **Enhanced Error Handling**: Comprehensive error boundary and loading states - Custom ErrorBoundary component with retry functionality - Loading states with visual feedback - Detailed error reporting with user-friendly fallbacks - Disabled buttons during loading to prevent race conditions - **Optimized Shared Dependencies**: Standardized configurations - Consistent React 18+ requirements across webpack/rspack configs - Added react/jsx-runtime for modern JSX transform - Proper strictVersion enforcement for singletons - Moment.js configured as non-singleton for flexibility - **Legacy Configuration Cleanup**: Removed deprecated patterns - Removed unnecessary library configuration from app3 - Standardized ModuleFederationPlugin configurations - Consistent shared dependency patterns All applications now build successfully and follow Module Federation best practices. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 1cb6629 commit 839f7a5

File tree

11 files changed

+438
-179
lines changed

11 files changed

+438
-179
lines changed

advanced-api/dynamic-remotes/app1/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"clean": "rm -rf dist"
2626
},
2727
"dependencies": {
28-
"react": "^16.13.0",
29-
"react-dom": "^16.13.0"
28+
"react": "^18.3.1",
29+
"react-dom": "^18.3.1"
3030
}
3131
}

advanced-api/dynamic-remotes/app1/rspack.config.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,23 @@ module.exports = {
5656
// so it will always use the higher version found
5757
shared: {
5858
react: {
59-
import: 'react', // the "react" package will be used a provided and fallback module
60-
shareKey: 'react', // under this name the shared module will be placed in the share scope
61-
shareScope: 'default', // share scope with this name will be used
62-
singleton: true, // only a single version of the shared module is allowed
59+
import: 'react',
60+
shareKey: 'react',
61+
shareScope: 'default',
62+
singleton: true,
63+
requiredVersion: '^18.3.1',
64+
strictVersion: true,
65+
},
66+
'react/jsx-runtime': {
67+
singleton: true,
68+
},
69+
'react/jsx-dev-runtime': {
70+
singleton: true,
6371
},
64-
'react/jsx-dev-runtime': {},
6572
'react-dom': {
66-
singleton: true, // only a single version of the shared module is allowed
73+
singleton: true,
74+
requiredVersion: '^18.3.1',
75+
strictVersion: true,
6776
},
6877
},
6978
}),

advanced-api/dynamic-remotes/app1/src/App.js

Lines changed: 166 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,115 @@
11
import React, { useState, useEffect, Suspense } from 'react';
22
import { init, loadRemote } from '@module-federation/runtime';
33

4+
class ErrorBoundary extends React.Component {
5+
constructor(props) {
6+
super(props);
7+
this.state = { hasError: false, error: null };
8+
}
9+
10+
static getDerivedStateFromError(error) {
11+
return { hasError: true, error };
12+
}
13+
14+
componentDidCatch(error, errorInfo) {
15+
console.error('Remote component error:', error, errorInfo);
16+
}
17+
18+
render() {
19+
if (this.state.hasError) {
20+
return (
21+
<div style={{
22+
padding: '2em',
23+
border: '2px solid #ff6b6b',
24+
borderRadius: '4px',
25+
backgroundColor: '#ffe0e0',
26+
color: '#c92a2a'
27+
}}>
28+
<h3>⚠️ Component Failed to Load</h3>
29+
<p>Unable to load the remote component. Please try again or check the remote application.</p>
30+
<details>
31+
<summary>Error Details</summary>
32+
<pre style={{ fontSize: '12px', overflow: 'auto' }}>
33+
{this.state.error?.toString()}
34+
</pre>
35+
</details>
36+
<button
37+
onClick={() => this.setState({ hasError: false, error: null })}
38+
style={{
39+
marginTop: '1em',
40+
padding: '0.5em 1em',
41+
backgroundColor: '#c92a2a',
42+
color: 'white',
43+
border: 'none',
44+
borderRadius: '4px',
45+
cursor: 'pointer'
46+
}}
47+
>
48+
Retry
49+
</button>
50+
</div>
51+
);
52+
}
53+
54+
return this.props.children;
55+
}
56+
}
57+
58+
const getRemoteEntry = (port) => {
59+
const baseUrl = process.env.NODE_ENV === 'production'
60+
? (process.env.REACT_APP_REMOTE_BASE_URL || window.location.origin)
61+
: 'http://localhost';
62+
return `${baseUrl}:${port}/remoteEntry.js`;
63+
};
64+
465
init({
566
name: 'app1',
667
remotes: [
768
{
869
name: 'app2',
9-
entry: 'http://localhost:3002/remoteEntry.js',
70+
entry: getRemoteEntry(3002),
1071
},
1172
{
1273
name: 'app3',
13-
entry: 'http://localhost:3003/remoteEntry.js',
74+
entry: getRemoteEntry(3003),
1475
},
1576
],
1677
});
1778

1879
function useDynamicImport({ module, scope }) {
1980
const [component, setComponent] = useState(null);
81+
const [loading, setLoading] = useState(false);
82+
const [error, setError] = useState(null);
2083

2184
useEffect(() => {
22-
if (!module || !scope) return;
85+
if (!module || !scope) {
86+
setComponent(null);
87+
setError(null);
88+
return;
89+
}
2390

2491
const loadComponent = async () => {
92+
setLoading(true);
93+
setError(null);
94+
setComponent(null);
95+
2596
try {
97+
console.log(`Loading remote module: ${scope}/${module}`);
2698
const { default: Component } = await loadRemote(`${scope}/${module}`);
2799
setComponent(() => Component);
100+
console.log(`Successfully loaded: ${scope}/${module}`);
28101
} catch (error) {
29102
console.error(`Error loading remote module ${scope}/${module}:`, error);
103+
setError(error);
104+
} finally {
105+
setLoading(false);
30106
}
31107
};
32108

33109
loadComponent();
34110
}, [module, scope]);
35111

36-
return component;
112+
return { component, loading, error };
37113
}
38114

39115
function App() {
@@ -53,7 +129,54 @@ function App() {
53129
});
54130
};
55131

56-
const Component = useDynamicImport({ module, scope });
132+
const { component: Component, loading, error } = useDynamicImport({ module, scope });
133+
134+
const renderRemoteComponent = () => {
135+
if (loading) {
136+
return (
137+
<div style={{
138+
padding: '2em',
139+
textAlign: 'center',
140+
backgroundColor: '#f8f9fa',
141+
borderRadius: '4px',
142+
border: '2px dashed #dee2e6'
143+
}}>
144+
<div>🔄 Loading {scope}/{module}...</div>
145+
</div>
146+
);
147+
}
148+
149+
if (error) {
150+
return (
151+
<div style={{
152+
padding: '2em',
153+
border: '2px solid #ffc107',
154+
borderRadius: '4px',
155+
backgroundColor: '#fff3cd',
156+
color: '#856404'
157+
}}>
158+
<h3>⚠️ Failed to Load Remote Component</h3>
159+
<p>Could not load {scope}/{module}</p>
160+
<details>
161+
<summary>Error Details</summary>
162+
<pre style={{ fontSize: '12px', overflow: 'auto', marginTop: '1em' }}>
163+
{error.toString()}
164+
</pre>
165+
</details>
166+
</div>
167+
);
168+
}
169+
170+
if (Component) {
171+
return (
172+
<ErrorBoundary>
173+
<Component />
174+
</ErrorBoundary>
175+
);
176+
}
177+
178+
return null;
179+
};
57180

58181
return (
59182
<div
@@ -68,10 +191,45 @@ function App() {
68191
The Dynamic System will take advantage of Module Federation <strong>remotes</strong> and{' '}
69192
<strong>exposes</strong>. It will not load components that have already been loaded.
70193
</p>
71-
<button onClick={setApp2}>Load App 2 Widget</button>
72-
<button onClick={setApp3}>Load App 3 Widget</button>
194+
<div style={{ marginBottom: '1em' }}>
195+
<button
196+
onClick={setApp2}
197+
disabled={loading}
198+
style={{
199+
marginRight: '1em',
200+
padding: '0.5em 1em',
201+
backgroundColor: loading ? '#ccc' : '#007bff',
202+
color: 'white',
203+
border: 'none',
204+
borderRadius: '4px',
205+
cursor: loading ? 'not-allowed' : 'pointer'
206+
}}
207+
>
208+
Load App 2 Widget
209+
</button>
210+
<button
211+
onClick={setApp3}
212+
disabled={loading}
213+
style={{
214+
padding: '0.5em 1em',
215+
backgroundColor: loading ? '#ccc' : '#007bff',
216+
color: 'white',
217+
border: 'none',
218+
borderRadius: '4px',
219+
cursor: loading ? 'not-allowed' : 'pointer'
220+
}}
221+
>
222+
Load App 3 Widget
223+
</button>
224+
</div>
73225
<div style={{ marginTop: '2em' }}>
74-
<Suspense fallback="Loading System">{Component ? <Component /> : null}</Suspense>
226+
<Suspense fallback={
227+
<div style={{ padding: '2em', textAlign: 'center' }}>
228+
🔄 Initializing component...
229+
</div>
230+
}>
231+
{renderRemoteComponent()}
232+
</Suspense>
75233
</div>
76234
</div>
77235
);

advanced-api/dynamic-remotes/app1/webpack.config.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,20 @@ module.exports = {
4141
// so it will always use the higher version found
4242
shared: {
4343
react: {
44-
import: 'react', // the "react" package will be used a provided and fallback module
45-
shareKey: 'react', // under this name the shared module will be placed in the share scope
46-
shareScope: 'default', // share scope with this name will be used
47-
singleton: true, // only a single version of the shared module is allowed
44+
import: 'react',
45+
shareKey: 'react',
46+
shareScope: 'default',
47+
singleton: true,
48+
requiredVersion: '^18.3.1',
49+
strictVersion: true,
4850
},
4951
'react-dom': {
50-
singleton: true, // only a single version of the shared module is allowed
52+
singleton: true,
53+
requiredVersion: '^18.3.1',
54+
strictVersion: true,
55+
},
56+
'react/jsx-runtime': {
57+
singleton: true,
5158
},
5259
},
5360
}),

advanced-api/dynamic-remotes/app2/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
},
2626
"dependencies": {
2727
"moment": "^2.29.4",
28-
"react": "^16.13.0",
29-
"react-dom": "^16.13.0"
28+
"react": "^18.3.1",
29+
"react-dom": "^18.3.1"
3030
}
3131
}

advanced-api/dynamic-remotes/app2/rspack.config.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,28 @@ module.exports = {
5858
'./Widget': './src/Widget',
5959
},
6060
shared: {
61-
moment: deps.moment,
62-
'react/jsx-dev-runtime': {},
61+
moment: {
62+
requiredVersion: deps.moment,
63+
singleton: false,
64+
},
65+
'react/jsx-runtime': {
66+
singleton: true,
67+
},
68+
'react/jsx-dev-runtime': {
69+
singleton: true,
70+
},
6371
react: {
64-
requiredVersion: deps.react,
65-
import: 'react', // the "react" package will be used a provided and fallback module
66-
shareKey: 'react', // under this name the shared module will be placed in the share scope
67-
shareScope: 'default', // share scope with this name will be used
68-
singleton: true, // only a single version of the shared module is allowed
72+
requiredVersion: '^18.3.1',
73+
import: 'react',
74+
shareKey: 'react',
75+
shareScope: 'default',
76+
singleton: true,
77+
strictVersion: true,
6978
},
7079
'react-dom': {
71-
requiredVersion: deps['react-dom'],
72-
singleton: true, // only a single version of the shared module is allowed
80+
requiredVersion: '^18.3.1',
81+
singleton: true,
82+
strictVersion: true,
7383
},
7484
},
7585
}),

advanced-api/dynamic-remotes/app2/webpack.config.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,25 @@ module.exports = {
4141
'./Widget': './src/Widget',
4242
},
4343
shared: {
44-
moment: deps.moment,
44+
moment: {
45+
requiredVersion: deps.moment,
46+
singleton: false,
47+
},
4548
react: {
46-
requiredVersion: deps.react,
47-
import: 'react', // the "react" package will be used a provided and fallback module
48-
shareKey: 'react', // under this name the shared module will be placed in the share scope
49-
shareScope: 'default', // share scope with this name will be used
50-
singleton: true, // only a single version of the shared module is allowed
49+
requiredVersion: '^18.3.1',
50+
import: 'react',
51+
shareKey: 'react',
52+
shareScope: 'default',
53+
singleton: true,
54+
strictVersion: true,
5155
},
5256
'react-dom': {
53-
requiredVersion: deps['react-dom'],
54-
singleton: true, // only a single version of the shared module is allowed
57+
requiredVersion: '^18.3.1',
58+
singleton: true,
59+
strictVersion: true,
60+
},
61+
'react/jsx-runtime': {
62+
singleton: true,
5563
},
5664
},
5765
}),

advanced-api/dynamic-remotes/app3/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
},
2626
"dependencies": {
2727
"moment": "^2.29.4",
28-
"react": "^16.13.0",
29-
"react-dom": "^16.13.0",
30-
"react-redux": "^7.2.0",
31-
"redux": "^4.2.1"
28+
"react": "^18.3.1",
29+
"react-dom": "^18.3.1",
30+
"react-redux": "^9.1.2",
31+
"redux": "^5.0.1"
3232
}
3333
}

0 commit comments

Comments
 (0)