@module-federation/bridge-react 0.0.0-next-20240731082143 → 0.0.0-next-20240731105745

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/src/create.tsx CHANGED
@@ -1,4 +1,9 @@
1
1
  import React, { forwardRef } from 'react';
2
+ import type {
3
+ ForwardRefExoticComponent,
4
+ PropsWithoutRef,
5
+ RefAttributes,
6
+ } from 'react';
2
7
  import type { ProviderParams } from '@module-federation/bridge-shared';
3
8
  import { LoggerInstance } from './utils';
4
9
  import {
@@ -13,11 +18,7 @@ export interface RenderFnParams extends ProviderParams {
13
18
 
14
19
  interface RemoteModule {
15
20
  provider: () => {
16
- render: (
17
- info: ProviderParams & {
18
- dom: any;
19
- },
20
- ) => void;
21
+ render: (info: RenderFnParams) => void;
21
22
  destroy: (info: { dom: any }) => void;
22
23
  };
23
24
  }
@@ -27,6 +28,7 @@ function createLazyRemoteComponent<T, E extends keyof T>(info: {
27
28
  loading: React.ReactNode;
28
29
  fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
29
30
  export?: E;
31
+ dom?: string;
30
32
  }) {
31
33
  const exportName = info?.export || 'default';
32
34
  return React.lazy(async () => {
@@ -53,12 +55,13 @@ function createLazyRemoteComponent<T, E extends keyof T>(info: {
53
55
  basename?: ProviderParams['basename'];
54
56
  memoryRoute?: ProviderParams['memoryRoute'];
55
57
  }
56
- >((props, _ref) => {
58
+ >((props, ref) => {
57
59
  return (
58
60
  <RemoteApp
59
61
  name={moduleName}
60
62
  providerInfo={exportFn}
61
63
  exportName={info.export || 'default'}
64
+ ref={ref}
62
65
  {...props}
63
66
  />
64
67
  );
@@ -89,30 +92,27 @@ export function createRemoteComponent<T, E extends keyof T>(info: {
89
92
  loading: React.ReactNode;
90
93
  fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
91
94
  export?: E;
92
- }) {
93
- type ExportType = T[E] extends (...args: any) => any
94
- ? ReturnType<T[E]>
95
- : never;
96
- type RawComponentType = '__BRIDGE_FN__' extends keyof ExportType
97
- ? ExportType['__BRIDGE_FN__'] extends (...args: any) => any
98
- ? Parameters<ExportType['__BRIDGE_FN__']>[0]
99
- : {}
100
- : {};
101
-
102
- const LazyComponent = createLazyRemoteComponent(info);
95
+ dom?: string;
96
+ }): ForwardRefExoticComponent<
97
+ PropsWithoutRef<ProviderParams> & RefAttributes<HTMLElement | HTMLDivElement>
98
+ > {
99
+ // type ExportType = T[E] extends (...args: any) => any
100
+ // ? ReturnType<T[E]>
101
+ // : never;
102
+ // type RawComponentType = '__BRIDGE_FN__' extends keyof ExportType
103
+ // ? ExportType['__BRIDGE_FN__'] extends (...args: any) => any
104
+ // ? Parameters<ExportType['__BRIDGE_FN__']>[0]
105
+ // : {}
106
+ // : {};
103
107
 
104
- return (
105
- props: {
106
- basename?: ProviderParams['basename'];
107
- memoryRoute?: ProviderParams['memoryRoute'];
108
- } & RawComponentType,
109
- ) => {
108
+ return forwardRef(function (props, ref) {
109
+ const LazyComponent = createLazyRemoteComponent(info);
110
110
  return (
111
111
  <ErrorBoundary FallbackComponent={info.fallback}>
112
112
  <React.Suspense fallback={info.loading}>
113
- <LazyComponent {...props} />
113
+ <LazyComponent {...props} dom={info?.dom} ref={ref} />
114
114
  </React.Suspense>
115
115
  </ErrorBoundary>
116
116
  );
117
- };
117
+ });
118
118
  }
package/src/provider.tsx CHANGED
@@ -9,45 +9,69 @@ import type {
9
9
  } from '@module-federation/bridge-shared';
10
10
  import { LoggerInstance, atLeastReact18 } from './utils';
11
11
 
12
+ type RootType = HTMLElement | ReactDOMClient.Root;
12
13
  type ProviderFnParams<T> = {
13
14
  rootComponent: React.ComponentType<T>;
15
+ render?: (
16
+ App: React.ReactElement,
17
+ id?: HTMLElement | string,
18
+ ) => RootType | Promise<RootType>;
14
19
  };
15
20
 
16
21
  export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
17
22
  return () => {
18
- const rootMap = new Map<any, ReactDOMClient.Root>();
19
-
23
+ const rootMap = new Map<any, RootType>();
20
24
  const RawComponent = (info: { propsInfo: T; appInfo: ProviderParams }) => {
21
- const { appInfo, propsInfo } = info;
25
+ const { appInfo, propsInfo, ...restProps } = info;
22
26
  const { name, memoryRoute, basename = '/' } = appInfo;
23
27
 
24
28
  return (
25
29
  <RouterContext.Provider value={{ name, basename, memoryRoute }}>
26
- <bridgeInfo.rootComponent {...propsInfo} basename={basename} />
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
42
  const { name, basename, memoryRoute, ...propsInfo } = info;
35
-
36
43
  if (atLeastReact18(React)) {
37
- const root = ReactDOMClient.createRoot(info.dom);
38
- rootMap.set(info.dom, root);
39
- root.render(
40
- <RawComponent
41
- propsInfo={propsInfo}
42
- appInfo={{
43
- name,
44
- basename,
45
- memoryRoute,
46
- }}
47
- />,
48
- );
44
+ if (bridgeInfo?.render) {
45
+ Promise.resolve(
46
+ bridgeInfo?.render(
47
+ <RawComponent
48
+ propsInfo={propsInfo}
49
+ appInfo={{
50
+ name,
51
+ basename,
52
+ memoryRoute,
53
+ }}
54
+ />,
55
+ info.dom,
56
+ ),
57
+ ).then((root: RootType) => rootMap.set(info.dom, root));
58
+ } else {
59
+ const root: RootType = ReactDOMClient.createRoot(info.dom);
60
+ root.render(
61
+ <RawComponent
62
+ propsInfo={propsInfo}
63
+ appInfo={{
64
+ name,
65
+ basename,
66
+ memoryRoute,
67
+ }}
68
+ />,
69
+ );
70
+ rootMap.set(info.dom, root);
71
+ }
49
72
  } else {
50
- ReactDOM.render(
73
+ const renderFunc = bridgeInfo?.render || ReactDOM.render;
74
+ renderFunc(
51
75
  <RawComponent
52
76
  propsInfo={propsInfo}
53
77
  appInfo={{
@@ -60,13 +84,13 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
60
84
  );
61
85
  }
62
86
  },
63
- destroy(info: { dom: HTMLElement }) {
87
+ async destroy(info: { dom: HTMLElement }) {
64
88
  LoggerInstance.log(`createBridgeComponent destroy Info`, {
65
89
  dom: info.dom,
66
90
  });
67
91
  if (atLeastReact18(React)) {
68
92
  const root = rootMap.get(info.dom);
69
- root?.unmount();
93
+ (root as ReactDOMClient.Root)?.unmount();
70
94
  } else {
71
95
  ReactDOM.unmountComponentAtNode(info.dom);
72
96
  }
@@ -84,5 +108,3 @@ export function ShadowRoot(info: { children: () => JSX.Element }) {
84
108
 
85
109
  return <div ref={domRef}>{root && <info.children />}</div>;
86
110
  }
87
-
88
- // function ShadowContent() {}
@@ -1,11 +1,16 @@
1
- import React, { useContext, useEffect, useRef, useState } from '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';
6
12
 
7
13
  declare const __APP_VERSION__: string;
8
-
9
14
  export interface RenderFnParams extends ProviderParams {
10
15
  dom?: any;
11
16
  }
@@ -27,66 +32,99 @@ interface RemoteAppParams {
27
32
  exportName: string | number | symbol;
28
33
  }
29
34
 
30
- const RemoteApp = ({
31
- name,
32
- memoryRoute,
33
- basename,
34
- providerInfo,
35
- ...resProps
36
- }: RemoteAppParams & ProviderParams) => {
37
- const rootRef = useRef(null);
38
- const renderDom = useRef(null);
39
- const providerInfoRef = useRef<any>(null);
40
-
41
- useEffect(() => {
42
- const renderTimeout = setTimeout(() => {
43
- const providerReturn = providerInfo();
44
- providerInfoRef.current = providerReturn;
45
- const renderProps = {
46
- name,
47
- dom: rootRef.current,
48
- basename,
49
- memoryRoute,
50
- ...resProps,
51
- };
52
- renderDom.current = rootRef.current;
53
- LoggerInstance.log(
54
- `createRemoteComponent LazyComponent render >>>`,
55
- renderProps,
56
- );
57
- providerReturn.render(renderProps);
58
- });
59
-
60
- return () => {
61
- clearTimeout(renderTimeout);
62
- setTimeout(() => {
63
- if (providerInfoRef.current?.destroy) {
64
- LoggerInstance.log(
65
- `createRemoteComponent LazyComponent destroy >>>`,
66
- { name, basename, dom: renderDom.current },
67
- );
68
- providerInfoRef.current?.destroy({
69
- dom: renderDom.current,
70
- });
35
+ const RemoteAppWrapper = forwardRef(function (
36
+ props: RemoteAppParams & RenderFnParams,
37
+ ref,
38
+ ) {
39
+ const RemoteApp = () => {
40
+ const {
41
+ name,
42
+ memoryRoute,
43
+ basename,
44
+ providerInfo,
45
+ dom,
46
+ className,
47
+ style,
48
+ ...resProps
49
+ } = props;
50
+
51
+ const rootRef: React.MutableRefObject<HTMLElement | null> =
52
+ ref && 'current' in ref
53
+ ? (ref as React.MutableRefObject<HTMLElement | null>)
54
+ : useRef(null);
55
+ const renderDom: React.MutableRefObject<HTMLElement | null> = useRef(null);
56
+ const providerInfoRef = useRef<any>(null);
57
+
58
+ useEffect(() => {
59
+ const renderTimeout = setTimeout(() => {
60
+ const providerReturn = providerInfo();
61
+ providerInfoRef.current = providerReturn;
62
+
63
+ let domElement = null;
64
+ if (dom) {
65
+ domElement = document.querySelector(dom);
66
+ if (!domElement || !(domElement instanceof HTMLElement)) {
67
+ throw new Error(`Invalid dom: ${dom}`);
68
+ }
69
+ rootRef.current = domElement;
70
+ } else {
71
+ domElement = rootRef.current;
71
72
  }
73
+
74
+ const renderProps = {
75
+ name,
76
+ dom: domElement,
77
+ basename,
78
+ memoryRoute,
79
+ ...resProps,
80
+ };
81
+ renderDom.current = rootRef.current;
82
+ LoggerInstance.log(
83
+ `createRemoteComponent LazyComponent render >>>`,
84
+ renderProps,
85
+ );
86
+ providerReturn.render(renderProps);
72
87
  });
73
- };
74
- }, []);
75
88
 
76
- //@ts-ignore
77
- return <div ref={rootRef}></div>;
78
- };
89
+ return () => {
90
+ clearTimeout(renderTimeout);
91
+ setTimeout(() => {
92
+ if (providerInfoRef.current?.destroy) {
93
+ LoggerInstance.log(
94
+ `createRemoteComponent LazyComponent destroy >>>`,
95
+ { name, basename, dom: renderDom.current },
96
+ );
97
+ providerInfoRef.current?.destroy({
98
+ dom: renderDom.current,
99
+ });
100
+ }
101
+ });
102
+ };
103
+ }, []);
104
+
105
+ return dom ? null : (
106
+ <div
107
+ className={props?.className}
108
+ style={props?.style}
109
+ ref={rootRef}
110
+ ></div>
111
+ );
112
+ };
79
113
 
80
- (RemoteApp as any)['__APP_VERSION__'] = __APP_VERSION__;
114
+ (RemoteApp as any)['__APP_VERSION__'] = __APP_VERSION__;
115
+ return <RemoteApp />;
116
+ });
81
117
 
82
118
  interface ExtraDataProps {
83
119
  basename?: string;
84
120
  }
85
121
 
86
- export function withRouterData<P extends Parameters<typeof RemoteApp>[0]>(
122
+ export function withRouterData<
123
+ P extends Parameters<typeof RemoteAppWrapper>[0],
124
+ >(
87
125
  WrappedComponent: React.ComponentType<P & ExtraDataProps>,
88
126
  ): React.FC<Omit<P, keyof ExtraDataProps>> {
89
- return (props: any) => {
127
+ const Component = forwardRef(function (props: any, ref) {
90
128
  let enableDispathPopstate = false;
91
129
  let routerContextVal: any;
92
130
  try {
@@ -158,8 +196,12 @@ export function withRouterData<P extends Parameters<typeof RemoteApp>[0]>(
158
196
  }, [location]);
159
197
  }
160
198
 
161
- return <WrappedComponent {...(props as P)} basename={basename} />;
162
- };
199
+ return <WrappedComponent {...(props as P)} basename={basename} ref={ref} />;
200
+ });
201
+
202
+ return forwardRef(function (props, ref) {
203
+ return <Component {...props} ref={ref} />;
204
+ }) as any;
163
205
  }
164
206
 
165
- export default withRouterData(RemoteApp);
207
+ export default withRouterData(RemoteAppWrapper);
@@ -0,0 +1,78 @@
1
+ import React, { useContext } from 'react';
2
+ // The upper alias react-router-dom$ into this file avoids the loop
3
+ // @ts-ignore
4
+ import * as ReactRouterDom from 'react-router-dom/index.js';
5
+
6
+ import { RouterContext } from './context';
7
+ import { LoggerInstance } from './utils';
8
+
9
+ function WraperRouter(
10
+ props:
11
+ | Parameters<typeof ReactRouterDom.BrowserRouter>[0]
12
+ | Parameters<typeof ReactRouterDom.MemoryRouter>[0],
13
+ ) {
14
+ const { basename, ...propsRes } = props;
15
+ const routerContextProps = useContext(RouterContext) || {};
16
+
17
+ LoggerInstance.log(`WraperRouter info >>>`, {
18
+ ...routerContextProps,
19
+ routerContextProps,
20
+ WraperRouterProps: props,
21
+ });
22
+ if (!routerContextProps) return <ReactRouterDom.BrowserRouter {...props} />;
23
+
24
+ if (routerContextProps?.memoryRoute) {
25
+ return (
26
+ <ReactRouterDom.MemoryRouter
27
+ {...props}
28
+ initialEntries={[routerContextProps?.memoryRoute.entryPath]}
29
+ />
30
+ );
31
+ }
32
+ return (
33
+ <ReactRouterDom.BrowserRouter
34
+ {...propsRes}
35
+ basename={routerContextProps?.basename || basename}
36
+ />
37
+ );
38
+ }
39
+
40
+ function WraperRouterProvider(
41
+ props: Parameters<typeof ReactRouterDom.RouterProvider>[0],
42
+ ) {
43
+ const { router, ...propsRes } = props;
44
+ const routerContextProps = useContext(RouterContext) || {};
45
+ const routers = router.routes;
46
+ LoggerInstance.log(`WraperRouterProvider info >>>`, {
47
+ ...routerContextProps,
48
+ routerContextProps,
49
+ WraperRouterProviderProps: props,
50
+ router,
51
+ });
52
+ const RouterProvider = (ReactRouterDom as any)['Router' + 'Provider'];
53
+ const createMemoryRouter = (ReactRouterDom as any)['create' + 'MemoryRouter'];
54
+ const createBrowserRouter = (ReactRouterDom as any)[
55
+ 'create' + 'BrowserRouter'
56
+ ];
57
+ if (!routerContextProps) return <RouterProvider {...props} />;
58
+
59
+ if (routerContextProps.memoryRoute) {
60
+ const MemeoryRouterInstance = createMemoryRouter(routers, {
61
+ initialEntries: [routerContextProps?.memoryRoute.entryPath],
62
+ });
63
+ return <RouterProvider router={MemeoryRouterInstance} />;
64
+ } else {
65
+ const BrowserRouterInstance = createBrowserRouter(routers, {
66
+ basename: routerContextProps.basename,
67
+ future: router.future,
68
+ window: router.window,
69
+ });
70
+ return <RouterProvider {...propsRes} router={BrowserRouterInstance} />;
71
+ }
72
+ }
73
+
74
+ // @ts-ignore
75
+ // export * from 'react-router-dom/index.js';
76
+
77
+ export { WraperRouter as BrowserRouter };
78
+ export { WraperRouterProvider as RouterProvider };
@@ -0,0 +1,76 @@
1
+ import React, { useContext } from 'react';
2
+ // The upper alias react-router-dom$ into this file avoids the loop
3
+ import * as ReactRouterDom from 'react-router-dom/dist/index.js';
4
+
5
+ import { RouterContext } from './context';
6
+ import { LoggerInstance } from './utils';
7
+
8
+ function WraperRouter(
9
+ props:
10
+ | Parameters<typeof ReactRouterDom.BrowserRouter>[0]
11
+ | Parameters<typeof ReactRouterDom.MemoryRouter>[0],
12
+ ) {
13
+ const { basename, ...propsRes } = props;
14
+ const routerContextProps = useContext(RouterContext) || {};
15
+
16
+ LoggerInstance.log(`WraperRouter info >>>`, {
17
+ ...routerContextProps,
18
+ routerContextProps,
19
+ WraperRouterProps: props,
20
+ });
21
+ if (!routerContextProps) return <ReactRouterDom.BrowserRouter {...props} />;
22
+
23
+ if (routerContextProps?.memoryRoute) {
24
+ return (
25
+ <ReactRouterDom.MemoryRouter
26
+ {...props}
27
+ initialEntries={[routerContextProps?.memoryRoute.entryPath]}
28
+ />
29
+ );
30
+ }
31
+ return (
32
+ <ReactRouterDom.BrowserRouter
33
+ {...propsRes}
34
+ basename={routerContextProps?.basename || basename}
35
+ />
36
+ );
37
+ }
38
+
39
+ function WraperRouterProvider(
40
+ props: Parameters<typeof ReactRouterDom.RouterProvider>[0],
41
+ ) {
42
+ const { router, ...propsRes } = props;
43
+ const routerContextProps = useContext(RouterContext) || {};
44
+ const routers = router.routes;
45
+ LoggerInstance.log(`WraperRouterProvider info >>>`, {
46
+ ...routerContextProps,
47
+ routerContextProps,
48
+ WraperRouterProviderProps: props,
49
+ router,
50
+ });
51
+ const RouterProvider = (ReactRouterDom as any)['Router' + 'Provider'];
52
+ const createMemoryRouter = (ReactRouterDom as any)['create' + 'MemoryRouter'];
53
+ const createBrowserRouter = (ReactRouterDom as any)[
54
+ 'create' + 'BrowserRouter'
55
+ ];
56
+ if (!routerContextProps) return <RouterProvider {...props} />;
57
+
58
+ if (routerContextProps.memoryRoute) {
59
+ const MemeoryRouterInstance = createMemoryRouter(routers, {
60
+ initialEntries: [routerContextProps?.memoryRoute.entryPath],
61
+ });
62
+ return <RouterProvider router={MemeoryRouterInstance} />;
63
+ } else {
64
+ const BrowserRouterInstance = createBrowserRouter(routers, {
65
+ basename: routerContextProps.basename,
66
+ future: router.future,
67
+ window: router.window,
68
+ });
69
+ return <RouterProvider {...propsRes} router={BrowserRouterInstance} />;
70
+ }
71
+ }
72
+
73
+ export * from 'react-router-dom/dist/index.js';
74
+
75
+ export { WraperRouter as BrowserRouter };
76
+ export { WraperRouterProvider as RouterProvider };
package/src/router.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import React, { useContext } from 'react';
2
2
  // The upper alias react-router-dom$ into this file avoids the loop
3
3
  import * as ReactRouterDom from 'react-router-dom/';
4
+
4
5
  import { RouterContext } from './context';
5
6
  import { LoggerInstance } from './utils';
6
7
 
@@ -52,6 +53,7 @@ function WraperRouterProvider(
52
53
  const createBrowserRouter = (ReactRouterDom as any)[
53
54
  'create' + 'BrowserRouter'
54
55
  ];
56
+
55
57
  if (!routerContextProps) return <RouterProvider {...props} />;
56
58
 
57
59
  if (routerContextProps.memoryRoute) {
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-v5.tsx'),
26
+ 'router-v6': path.resolve(__dirname, 'src/router-v6.tsx'),
25
27
  },
26
28
  formats: ['cjs', 'es'],
27
29
  fileName: (format, entryName) => `${entryName}.${format}.js`,
@@ -32,6 +34,8 @@ 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',
35
39
  ],
36
40
  },
37
41
  minify: false,