@module-federation/bridge-react 0.3.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/__tests__/bridge.spec.tsx +37 -3
- package/dist/index.cjs.js +127 -92
- package/dist/index.d.ts +10 -7
- package/dist/index.es.js +129 -94
- package/dist/router-v5.cjs.js +15 -0
- package/dist/router-v5.d.ts +11 -0
- package/dist/router-v5.es.js +8 -0
- package/dist/router-v6.cjs.js +15 -0
- package/dist/router-v6.d.ts +11 -0
- package/dist/router-v6.es.js +8 -0
- package/dist/router.cjs.js +8 -12
- package/dist/router.es.js +8 -12
- package/package.json +12 -2
- package/src/create.tsx +14 -19
- package/src/provider.tsx +50 -33
- package/src/remote/index.tsx +92 -56
- package/src/router.tsx +8 -10
- package/vite.config.ts +29 -0
package/src/provider.tsx
CHANGED
|
@@ -8,65 +8,84 @@ import type {
|
|
|
8
8
|
RenderFnParams,
|
|
9
9
|
} from '@module-federation/bridge-shared';
|
|
10
10
|
import { LoggerInstance, atLeastReact18 } from './utils';
|
|
11
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
11
12
|
|
|
13
|
+
type RootType = HTMLElement | ReactDOMClient.Root;
|
|
12
14
|
type ProviderFnParams<T> = {
|
|
13
15
|
rootComponent: React.ComponentType<T>;
|
|
16
|
+
render?: (
|
|
17
|
+
App: React.ReactElement,
|
|
18
|
+
id?: HTMLElement | string,
|
|
19
|
+
) => RootType | Promise<RootType>;
|
|
14
20
|
};
|
|
15
21
|
|
|
16
22
|
export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
|
|
17
23
|
return () => {
|
|
18
|
-
const rootMap = new Map<any,
|
|
19
|
-
|
|
24
|
+
const rootMap = new Map<any, RootType>();
|
|
20
25
|
const RawComponent = (info: { propsInfo: T; appInfo: ProviderParams }) => {
|
|
21
|
-
const { appInfo, propsInfo } = info;
|
|
22
|
-
const {
|
|
23
|
-
|
|
26
|
+
const { appInfo, propsInfo, ...restProps } = info;
|
|
27
|
+
const { moduleName, memoryRoute, basename = '/' } = appInfo;
|
|
24
28
|
return (
|
|
25
|
-
<RouterContext.Provider value={{
|
|
26
|
-
<bridgeInfo.rootComponent
|
|
29
|
+
<RouterContext.Provider value={{ moduleName, basename, memoryRoute }}>
|
|
30
|
+
<bridgeInfo.rootComponent
|
|
31
|
+
{...propsInfo}
|
|
32
|
+
basename={basename}
|
|
33
|
+
{...restProps}
|
|
34
|
+
/>
|
|
27
35
|
</RouterContext.Provider>
|
|
28
36
|
);
|
|
29
37
|
};
|
|
30
38
|
|
|
31
39
|
return {
|
|
32
|
-
render(info: RenderFnParams & any) {
|
|
40
|
+
async render(info: RenderFnParams & any) {
|
|
33
41
|
LoggerInstance.log(`createBridgeComponent render Info`, info);
|
|
34
|
-
const {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
const {
|
|
43
|
+
moduleName,
|
|
44
|
+
dom,
|
|
45
|
+
basename,
|
|
46
|
+
memoryRoute,
|
|
47
|
+
fallback,
|
|
48
|
+
...propsInfo
|
|
49
|
+
} = info;
|
|
50
|
+
const rootComponentWithErrorBoundary = (
|
|
51
|
+
// set ErrorBoundary for RawComponent rendering error, usually caused by user app rendering error
|
|
52
|
+
<ErrorBoundary FallbackComponent={fallback}>
|
|
40
53
|
<RawComponent
|
|
41
|
-
propsInfo={propsInfo}
|
|
42
54
|
appInfo={{
|
|
43
|
-
|
|
55
|
+
moduleName,
|
|
44
56
|
basename,
|
|
45
57
|
memoryRoute,
|
|
46
58
|
}}
|
|
47
|
-
/>,
|
|
48
|
-
);
|
|
49
|
-
} else {
|
|
50
|
-
ReactDOM.render(
|
|
51
|
-
<RawComponent
|
|
52
59
|
propsInfo={propsInfo}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
/>
|
|
61
|
+
</ErrorBoundary>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (atLeastReact18(React)) {
|
|
65
|
+
if (bridgeInfo?.render) {
|
|
66
|
+
// in case bridgeInfo?.render is an async function, resolve this to promise
|
|
67
|
+
Promise.resolve(
|
|
68
|
+
bridgeInfo?.render(rootComponentWithErrorBoundary, dom),
|
|
69
|
+
).then((root: RootType) => rootMap.set(info.dom, root));
|
|
70
|
+
} else {
|
|
71
|
+
const root: RootType = ReactDOMClient.createRoot(info.dom);
|
|
72
|
+
root.render(rootComponentWithErrorBoundary);
|
|
73
|
+
rootMap.set(info.dom, root);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
// react 17 render
|
|
77
|
+
const renderFn = bridgeInfo?.render || ReactDOM.render;
|
|
78
|
+
renderFn?.(rootComponentWithErrorBoundary, info.dom);
|
|
61
79
|
}
|
|
62
80
|
},
|
|
63
|
-
destroy(info: { dom: HTMLElement }) {
|
|
81
|
+
async destroy(info: { dom: HTMLElement }) {
|
|
64
82
|
LoggerInstance.log(`createBridgeComponent destroy Info`, {
|
|
65
83
|
dom: info.dom,
|
|
66
84
|
});
|
|
67
85
|
if (atLeastReact18(React)) {
|
|
68
86
|
const root = rootMap.get(info.dom);
|
|
69
|
-
root?.unmount();
|
|
87
|
+
(root as ReactDOMClient.Root)?.unmount();
|
|
88
|
+
rootMap.delete(info.dom);
|
|
70
89
|
} else {
|
|
71
90
|
ReactDOM.unmountComponentAtNode(info.dom);
|
|
72
91
|
}
|
|
@@ -84,5 +103,3 @@ export function ShadowRoot(info: { children: () => JSX.Element }) {
|
|
|
84
103
|
|
|
85
104
|
return <div ref={domRef}>{root && <info.children />}</div>;
|
|
86
105
|
}
|
|
87
|
-
|
|
88
|
-
// function ShadowContent() {}
|
package/src/remote/index.tsx
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useContext,
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
forwardRef,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import * as ReactRouterDOM from 'react-router-dom';
|
|
3
9
|
import type { ProviderParams } from '@module-federation/bridge-shared';
|
|
4
10
|
import { LoggerInstance, pathJoin } from '../utils';
|
|
5
11
|
import { dispatchPopstateEnv } from '@module-federation/bridge-shared';
|
|
12
|
+
import { ErrorBoundaryPropsWithComponent } from 'react-error-boundary';
|
|
6
13
|
|
|
7
14
|
declare const __APP_VERSION__: string;
|
|
8
|
-
|
|
9
15
|
export interface RenderFnParams extends ProviderParams {
|
|
10
16
|
dom?: any;
|
|
17
|
+
fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
|
|
11
18
|
}
|
|
12
19
|
|
|
13
20
|
interface RemoteModule {
|
|
@@ -22,71 +29,96 @@ interface RemoteModule {
|
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
interface RemoteAppParams {
|
|
25
|
-
|
|
32
|
+
moduleName: string;
|
|
26
33
|
providerInfo: NonNullable<RemoteModule['provider']>;
|
|
27
34
|
exportName: string | number | symbol;
|
|
35
|
+
fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
const
|
|
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
|
-
|
|
38
|
+
const RemoteAppWrapper = forwardRef(function (
|
|
39
|
+
props: RemoteAppParams & RenderFnParams,
|
|
40
|
+
ref,
|
|
41
|
+
) {
|
|
42
|
+
const RemoteApp = () => {
|
|
43
|
+
LoggerInstance.log(`RemoteAppWrapper RemoteApp props >>>`, { props });
|
|
44
|
+
const {
|
|
45
|
+
moduleName,
|
|
46
|
+
memoryRoute,
|
|
47
|
+
basename,
|
|
48
|
+
providerInfo,
|
|
49
|
+
className,
|
|
50
|
+
style,
|
|
51
|
+
fallback,
|
|
52
|
+
...resProps
|
|
53
|
+
} = props;
|
|
54
|
+
|
|
55
|
+
const rootRef: React.MutableRefObject<HTMLElement | null> =
|
|
56
|
+
ref && 'current' in ref
|
|
57
|
+
? (ref as React.MutableRefObject<HTMLElement | null>)
|
|
58
|
+
: useRef(null);
|
|
59
|
+
const renderDom: React.MutableRefObject<HTMLElement | null> = useRef(null);
|
|
60
|
+
const providerInfoRef = useRef<any>(null);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const renderTimeout = setTimeout(() => {
|
|
64
|
+
const providerReturn = providerInfo();
|
|
65
|
+
providerInfoRef.current = providerReturn;
|
|
66
|
+
|
|
67
|
+
const renderProps = {
|
|
68
|
+
moduleName,
|
|
69
|
+
dom: rootRef.current,
|
|
70
|
+
basename,
|
|
71
|
+
memoryRoute,
|
|
72
|
+
fallback,
|
|
73
|
+
...resProps,
|
|
74
|
+
};
|
|
75
|
+
renderDom.current = rootRef.current;
|
|
76
|
+
LoggerInstance.log(
|
|
77
|
+
`createRemoteComponent LazyComponent render >>>`,
|
|
78
|
+
renderProps,
|
|
79
|
+
);
|
|
80
|
+
providerReturn.render(renderProps);
|
|
72
81
|
});
|
|
73
|
-
};
|
|
74
|
-
}, []);
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
return () => {
|
|
84
|
+
clearTimeout(renderTimeout);
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
if (providerInfoRef.current?.destroy) {
|
|
87
|
+
LoggerInstance.log(
|
|
88
|
+
`createRemoteComponent LazyComponent destroy >>>`,
|
|
89
|
+
{ moduleName, basename, dom: renderDom.current },
|
|
90
|
+
);
|
|
91
|
+
providerInfoRef.current?.destroy({
|
|
92
|
+
dom: renderDom.current,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
className={props?.className}
|
|
102
|
+
style={props?.style}
|
|
103
|
+
ref={rootRef}
|
|
104
|
+
></div>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
79
107
|
|
|
80
|
-
(RemoteApp as any)['__APP_VERSION__'] = __APP_VERSION__;
|
|
108
|
+
(RemoteApp as any)['__APP_VERSION__'] = __APP_VERSION__;
|
|
109
|
+
return <RemoteApp />;
|
|
110
|
+
});
|
|
81
111
|
|
|
82
112
|
interface ExtraDataProps {
|
|
83
113
|
basename?: string;
|
|
84
114
|
}
|
|
85
115
|
|
|
86
|
-
export function withRouterData<
|
|
116
|
+
export function withRouterData<
|
|
117
|
+
P extends Parameters<typeof RemoteAppWrapper>[0],
|
|
118
|
+
>(
|
|
87
119
|
WrappedComponent: React.ComponentType<P & ExtraDataProps>,
|
|
88
120
|
): React.FC<Omit<P, keyof ExtraDataProps>> {
|
|
89
|
-
|
|
121
|
+
const Component = forwardRef(function (props: any, ref) {
|
|
90
122
|
let enableDispathPopstate = false;
|
|
91
123
|
let routerContextVal: any;
|
|
92
124
|
try {
|
|
@@ -158,8 +190,12 @@ export function withRouterData<P extends Parameters<typeof RemoteApp>[0]>(
|
|
|
158
190
|
}, [location]);
|
|
159
191
|
}
|
|
160
192
|
|
|
161
|
-
return <WrappedComponent {...(props as P)} basename={basename} />;
|
|
162
|
-
};
|
|
193
|
+
return <WrappedComponent {...(props as P)} basename={basename} ref={ref} />;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return forwardRef(function (props, ref) {
|
|
197
|
+
return <Component {...props} ref={ref} />;
|
|
198
|
+
}) as any;
|
|
163
199
|
}
|
|
164
200
|
|
|
165
|
-
export default withRouterData(
|
|
201
|
+
export default withRouterData(RemoteAppWrapper);
|
package/src/router.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import * as ReactRouterDom from 'react-router-dom/';
|
|
|
4
4
|
import { RouterContext } from './context';
|
|
5
5
|
import { LoggerInstance } from './utils';
|
|
6
6
|
|
|
7
|
-
function
|
|
7
|
+
function WrapperRouter(
|
|
8
8
|
props:
|
|
9
9
|
| Parameters<typeof ReactRouterDom.BrowserRouter>[0]
|
|
10
10
|
| Parameters<typeof ReactRouterDom.MemoryRouter>[0],
|
|
@@ -12,12 +12,11 @@ function WraperRouter(
|
|
|
12
12
|
const { basename, ...propsRes } = props;
|
|
13
13
|
const routerContextProps = useContext(RouterContext) || {};
|
|
14
14
|
|
|
15
|
-
LoggerInstance.log(`
|
|
15
|
+
LoggerInstance.log(`WrapperRouter info >>>`, {
|
|
16
16
|
...routerContextProps,
|
|
17
17
|
routerContextProps,
|
|
18
|
-
|
|
18
|
+
WrapperRouterProps: props,
|
|
19
19
|
});
|
|
20
|
-
if (!routerContextProps) return <ReactRouterDom.BrowserRouter {...props} />;
|
|
21
20
|
|
|
22
21
|
if (routerContextProps?.memoryRoute) {
|
|
23
22
|
return (
|
|
@@ -35,16 +34,16 @@ function WraperRouter(
|
|
|
35
34
|
);
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
function
|
|
37
|
+
function WrapperRouterProvider(
|
|
39
38
|
props: Parameters<typeof ReactRouterDom.RouterProvider>[0],
|
|
40
39
|
) {
|
|
41
40
|
const { router, ...propsRes } = props;
|
|
42
41
|
const routerContextProps = useContext(RouterContext) || {};
|
|
43
42
|
const routers = router.routes;
|
|
44
|
-
LoggerInstance.log(`
|
|
43
|
+
LoggerInstance.log(`WrapperRouterProvider info >>>`, {
|
|
45
44
|
...routerContextProps,
|
|
46
45
|
routerContextProps,
|
|
47
|
-
|
|
46
|
+
WrapperRouterProviderProps: props,
|
|
48
47
|
router,
|
|
49
48
|
});
|
|
50
49
|
const RouterProvider = (ReactRouterDom as any)['Router' + 'Provider'];
|
|
@@ -52,7 +51,6 @@ function WraperRouterProvider(
|
|
|
52
51
|
const createBrowserRouter = (ReactRouterDom as any)[
|
|
53
52
|
'create' + 'BrowserRouter'
|
|
54
53
|
];
|
|
55
|
-
if (!routerContextProps) return <RouterProvider {...props} />;
|
|
56
54
|
|
|
57
55
|
if (routerContextProps.memoryRoute) {
|
|
58
56
|
const MemeoryRouterInstance = createMemoryRouter(routers, {
|
|
@@ -71,5 +69,5 @@ function WraperRouterProvider(
|
|
|
71
69
|
|
|
72
70
|
export * from 'react-router-dom/';
|
|
73
71
|
|
|
74
|
-
export {
|
|
75
|
-
export {
|
|
72
|
+
export { WrapperRouter as BrowserRouter };
|
|
73
|
+
export { WrapperRouterProvider as RouterProvider };
|
package/vite.config.ts
CHANGED
|
@@ -22,6 +22,8 @@ export default defineConfig({
|
|
|
22
22
|
entry: {
|
|
23
23
|
index: path.resolve(__dirname, 'src/index.ts'),
|
|
24
24
|
router: path.resolve(__dirname, 'src/router.tsx'),
|
|
25
|
+
'router-v5': path.resolve(__dirname, 'src/router.tsx'),
|
|
26
|
+
'router-v6': path.resolve(__dirname, 'src/router.tsx'),
|
|
25
27
|
},
|
|
26
28
|
formats: ['cjs', 'es'],
|
|
27
29
|
fileName: (format, entryName) => `${entryName}.${format}.js`,
|
|
@@ -32,6 +34,33 @@ export default defineConfig({
|
|
|
32
34
|
'@remix-run/router',
|
|
33
35
|
'react-router',
|
|
34
36
|
'react-router-dom/',
|
|
37
|
+
'react-router-dom/index.js',
|
|
38
|
+
'react-router-dom/dist/index.js',
|
|
39
|
+
],
|
|
40
|
+
plugins: [
|
|
41
|
+
{
|
|
42
|
+
name: 'modify-output-plugin',
|
|
43
|
+
generateBundle(options, bundle) {
|
|
44
|
+
for (const fileName in bundle) {
|
|
45
|
+
const chunk = bundle[fileName];
|
|
46
|
+
if (fileName.includes('router-v6') && chunk.type === 'chunk') {
|
|
47
|
+
chunk.code = chunk.code.replace(
|
|
48
|
+
// Match 'react-router-dom/' followed by single quotes, double quotes, or backticks, replacing only 'react-router-dom/' to react-router-v6 dist file structure
|
|
49
|
+
/react-router-dom\/(?=[\'\"\`])/g,
|
|
50
|
+
'react-router-dom/dist/index.js',
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (fileName.includes('router-v5') && chunk.type === 'chunk') {
|
|
55
|
+
chunk.code = chunk.code.replace(
|
|
56
|
+
// Match 'react-router-dom/' followed by single quotes, double quotes, or backticks, replacing only 'react-router-dom/' to react-router-v5 dist file structure
|
|
57
|
+
/react-router-dom\/(?=[\'\"\`])/g,
|
|
58
|
+
'react-router-dom/index.js',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
35
64
|
],
|
|
36
65
|
},
|
|
37
66
|
minify: false,
|