-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
Copy pathApp.js
143 lines (120 loc) · 3.49 KB
/
App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import React from 'react';
import { init,loadRemote } from '@module-federation/runtime'
init({
name: 'app1',
remotes: [
{
name:'app2',
entry: 'http://localhost:3002/remoteEntry.js'
},
{
name:'app3',
entry: 'http://localhost:3003/remoteEntry.js'
},
]
})
function loadComponent(scope, module) {
return async () => {
// Initializes the share scope. This fills it with known provided modules from this build and all remotes
const Module = await loadRemote(`${scope}/${module.slice(2)}`);
return Module;
};
}
const urlCache = new Set();
const useDynamicScript = url => {
const [ready, setReady] = React.useState(false);
const [errorLoading, setErrorLoading] = React.useState(false);
React.useEffect(() => {
if (!url) return;
if (urlCache.has(url)) {
setReady(true);
setErrorLoading(false);
return;
}
setReady(false);
setErrorLoading(false);
const element = document.createElement('script');
element.src = url;
element.type = 'text/javascript';
element.async = true;
element.onload = () => {
urlCache.add(url);
setReady(true);
};
element.onerror = () => {
setReady(false);
setErrorLoading(true);
};
document.head.appendChild(element);
return () => {
urlCache.delete(url);
document.head.removeChild(element);
};
}, [url]);
return {
errorLoading,
ready,
};
};
const componentCache = new Map();
export const useFederatedComponent = (remoteUrl, scope, module) => {
const key = `${remoteUrl}-${scope}-${module}`;
const [Component, setComponent] = React.useState(null);
const { ready, errorLoading } = useDynamicScript(remoteUrl);
React.useEffect(() => {
if (Component) setComponent(null);
// Only recalculate when key changes
}, [key]);
React.useEffect(() => {
if (ready && !Component) {
const Comp = React.lazy(loadComponent(scope, module));
componentCache.set(key, Comp);
setComponent(Comp);
}
// key includes all dependencies (scope/module)
}, [Component, ready, key]);
return { errorLoading, Component };
};
function App() {
const [{ module, scope, url }, setSystem] = React.useState({});
function setApp2() {
setSystem({
url: 'http://localhost:3002/remoteEntry.js',
scope: 'app2',
module: './Widget',
});
}
function setApp3() {
setSystem({
url: 'http://localhost:3003/remoteEntry.js',
scope: 'app3',
module: './Widget',
});
}
const { Component: FederatedComponent, errorLoading } = useFederatedComponent(url, scope, module);
return (
<div
style={{
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
}}
>
<h1>Dynamic System Host</h1>
<h2>App 1</h2>
<p>
The Dynamic System will take advantage Module Federation <strong>remotes</strong> and{' '}
<strong>exposes</strong>. It will no load components that have been loaded already.
</p>
<button onClick={setApp2}>Load App 2 Widget</button>
<button onClick={setApp3}>Load App 3 Widget</button>
<div style={{ marginTop: '2em' }}>
<React.Suspense fallback="Loading System">
{errorLoading
? `Error loading module "${module}"`
: FederatedComponent && <FederatedComponent />}
</React.Suspense>
</div>
</div>
);
}
export default App;