@module-federation/bridge-react 0.0.0-next-20241017102355 → 0.0.0-next-20241018034800

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.
@@ -65,7 +65,7 @@ function WrapperRouterProvider(props) {
65
65
  return /* @__PURE__ */ React.createElement(RouterProvider, { router: MemeoryRouterInstance });
66
66
  } else {
67
67
  const BrowserRouterInstance = createBrowserRouter(routers, {
68
- basename: routerContextProps.basename,
68
+ basename: routerContextProps.basename || (router == null ? void 0 : router.basename),
69
69
  future: router.future,
70
70
  window: router.window
71
71
  });
package/dist/router.es.js CHANGED
@@ -47,7 +47,7 @@ function WrapperRouterProvider(props) {
47
47
  return /* @__PURE__ */ React__default.createElement(RouterProvider, { router: MemeoryRouterInstance });
48
48
  } else {
49
49
  const BrowserRouterInstance = createBrowserRouter(routers, {
50
- basename: routerContextProps.basename,
50
+ basename: routerContextProps.basename || (router == null ? void 0 : router.basename),
51
51
  future: router.future,
52
52
  window: router.window
53
53
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@module-federation/bridge-react",
3
- "version": "0.0.0-next-20241017102355",
3
+ "version": "0.0.0-next-20241018034800",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -35,12 +35,13 @@
35
35
  "dependencies": {
36
36
  "@loadable/component": "^5.16.4",
37
37
  "react-error-boundary": "^4.0.13",
38
- "@module-federation/bridge-shared": "0.0.0-next-20241017102355"
38
+ "@module-federation/bridge-shared": "0.0.0-next-20241018034800"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": ">=16.9.0",
42
42
  "react-dom": ">=16.9.0",
43
- "react-router-dom": ">=4"
43
+ "react-router-dom": ">=4",
44
+ "@module-federation/runtime": "0.0.0-next-20241018034800"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@testing-library/react": "15.0.7",
package/src/create.tsx CHANGED
@@ -1,11 +1,11 @@
1
1
  import React, { forwardRef } from 'react';
2
- import type { ProviderParams } from '@module-federation/bridge-shared';
3
- import { LoggerInstance } from './utils';
4
2
  import {
5
3
  ErrorBoundary,
6
4
  ErrorBoundaryPropsWithComponent,
7
5
  } from 'react-error-boundary';
6
+ import { LoggerInstance } from './utils';
8
7
  import RemoteApp from './remote';
8
+ import type { ProviderParams } from '@module-federation/bridge-shared';
9
9
 
10
10
  export interface RenderFnParams extends ProviderParams {
11
11
  dom?: any;
@@ -0,0 +1,28 @@
1
+ import { getInstance } from '@module-federation/runtime';
2
+ import helper from '@module-federation/runtime/helpers';
3
+
4
+ function registerBridgeLifeCycle() {
5
+ const { registerPlugins, pluginHelper } = helper.global;
6
+ const host = getInstance();
7
+ const pluginSystem = new pluginHelper.PluginSystem({
8
+ beforeBridgeRender: new pluginHelper.SyncHook<[Record<string, any>], any>(),
9
+ afterBridgeRender: new pluginHelper.SyncHook<[Record<string, any>], any>(),
10
+ beforeBridgeDestroy: new pluginHelper.SyncHook<
11
+ [Record<string, any>],
12
+ any
13
+ >(),
14
+ afterBridgeDestroy: new pluginHelper.SyncHook<[Record<string, any>], any>(),
15
+ });
16
+
17
+ if (host) {
18
+ registerPlugins<typeof pluginSystem.lifecycle, typeof pluginSystem>(
19
+ host?.options?.plugins,
20
+ [pluginSystem],
21
+ );
22
+ return pluginSystem;
23
+ }
24
+
25
+ return null;
26
+ }
27
+
28
+ export { registerBridgeLifeCycle };
package/src/provider.tsx CHANGED
@@ -2,25 +2,37 @@ import { useLayoutEffect, useRef, useState } from 'react';
2
2
  import * as React from 'react';
3
3
  import ReactDOM from 'react-dom';
4
4
  import ReactDOMClient from 'react-dom/client';
5
- import { RouterContext } from './context';
6
5
  import type {
7
6
  ProviderParams,
8
7
  RenderFnParams,
9
8
  } from '@module-federation/bridge-shared';
10
- import { LoggerInstance, atLeastReact18 } from './utils';
11
9
  import { ErrorBoundary } from 'react-error-boundary';
10
+ import { RouterContext } from './context';
11
+ import { LoggerInstance, atLeastReact18 } from './utils';
12
12
 
13
+ type RenderParams = RenderFnParams & any;
14
+ type DestroyParams = {
15
+ dom: HTMLElement;
16
+ };
13
17
  type RootType = HTMLElement | ReactDOMClient.Root;
18
+
19
+ type BridgeHooks = {
20
+ beforeBridgeRender?: (params: RenderFnParams) => any;
21
+ afterBridgeRender?: (params: RenderFnParams) => any;
22
+ beforeBridgeDestroy?: (params: DestroyParams) => any;
23
+ afterBridgeDestroy?: (params: DestroyParams) => any;
24
+ };
25
+
14
26
  type ProviderFnParams<T> = {
15
27
  rootComponent: React.ComponentType<T>;
16
28
  render?: (
17
29
  App: React.ReactElement,
18
30
  id?: HTMLElement | string,
19
31
  ) => RootType | Promise<RootType>;
32
+ hooks?: BridgeHooks;
20
33
  };
21
-
22
34
  export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
23
- return () => {
35
+ return (params: { hooks?: BridgeHooks }) => {
24
36
  const rootMap = new Map<any, RootType>();
25
37
  const RawComponent = (info: { propsInfo: T; appInfo: ProviderParams }) => {
26
38
  const { appInfo, propsInfo, ...restProps } = info;
@@ -37,7 +49,7 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
37
49
  };
38
50
 
39
51
  return {
40
- async render(info: RenderFnParams & any) {
52
+ async render(info: RenderParams) {
41
53
  LoggerInstance.log(`createBridgeComponent render Info`, info);
42
54
  const {
43
55
  moduleName,
@@ -47,6 +59,21 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
47
59
  fallback,
48
60
  ...propsInfo
49
61
  } = info;
62
+
63
+ const beforeBridgeRender =
64
+ (bridgeInfo?.hooks && bridgeInfo?.hooks.beforeBridgeRender) ||
65
+ params?.hooks?.beforeBridgeRender;
66
+
67
+ // 可通过beforeBridgeRender返回一个props对象,用于传递额外的 props 参数
68
+ const beforeBridgeRenderRes =
69
+ beforeBridgeRender && beforeBridgeRender(info);
70
+ const extraProps =
71
+ beforeBridgeRenderRes &&
72
+ typeof beforeBridgeRenderRes === 'object' &&
73
+ beforeBridgeRenderRes?.extraProps
74
+ ? beforeBridgeRenderRes?.extraProps
75
+ : {};
76
+
50
77
  const rootComponentWithErrorBoundary = (
51
78
  // set ErrorBoundary for RawComponent rendering error, usually caused by user app rendering error
52
79
  <ErrorBoundary FallbackComponent={fallback}>
@@ -56,11 +83,11 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
56
83
  basename,
57
84
  memoryRoute,
58
85
  }}
59
- propsInfo={propsInfo}
86
+ propsInfo={{ ...propsInfo, ...extraProps } as T}
60
87
  />
61
88
  </ErrorBoundary>
62
89
  );
63
-
90
+ // call render function
64
91
  if (atLeastReact18(React)) {
65
92
  if (bridgeInfo?.render) {
66
93
  // in case bridgeInfo?.render is an async function, resolve this to promise
@@ -77,11 +104,33 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
77
104
  const renderFn = bridgeInfo?.render || ReactDOM.render;
78
105
  renderFn?.(rootComponentWithErrorBoundary, info.dom);
79
106
  }
107
+
108
+ const afterBridgeRender =
109
+ (bridgeInfo?.hooks && bridgeInfo?.hooks.afterBridgeDestroy) ||
110
+ params?.hooks?.afterBridgeRender;
111
+ afterBridgeRender && afterBridgeRender(info);
80
112
  },
81
- async destroy(info: { dom: HTMLElement }) {
113
+
114
+ async destroy(info: DestroyParams) {
82
115
  LoggerInstance.log(`createBridgeComponent destroy Info`, {
83
116
  dom: info.dom,
84
117
  });
118
+
119
+ // call beforeBridgeDestroy hook
120
+ if (
121
+ bridgeInfo?.hooks &&
122
+ bridgeInfo?.hooks.beforeBridgeDestroy &&
123
+ typeof bridgeInfo?.hooks.beforeBridgeDestroy === 'function'
124
+ ) {
125
+ bridgeInfo.hooks.beforeBridgeDestroy(info);
126
+ }
127
+
128
+ const beforeBridgeDestroy =
129
+ (bridgeInfo?.hooks && bridgeInfo?.hooks.beforeBridgeDestroy) ||
130
+ params?.hooks?.beforeBridgeDestroy;
131
+ beforeBridgeDestroy && beforeBridgeDestroy(info);
132
+
133
+ // call destroy function
85
134
  if (atLeastReact18(React)) {
86
135
  const root = rootMap.get(info.dom);
87
136
  (root as ReactDOMClient.Root)?.unmount();
@@ -89,6 +138,11 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
89
138
  } else {
90
139
  ReactDOM.unmountComponentAtNode(info.dom);
91
140
  }
141
+
142
+ const afterBridgeDestroy =
143
+ (bridgeInfo?.hooks && bridgeInfo?.hooks.afterBridgeDestroy) ||
144
+ params?.hooks?.afterBridgeDestroy;
145
+ afterBridgeDestroy && afterBridgeDestroy(info);
92
146
  },
93
147
  rawComponent: bridgeInfo.rootComponent,
94
148
  __BRIDGE_FN__: (_args: T) => {},
@@ -7,9 +7,25 @@ import React, {
7
7
  } from 'react';
8
8
  import * as ReactRouterDOM from 'react-router-dom';
9
9
  import type { ProviderParams } from '@module-federation/bridge-shared';
10
- import { LoggerInstance, pathJoin } from '../utils';
11
10
  import { dispatchPopstateEnv } from '@module-federation/bridge-shared';
12
11
  import { ErrorBoundaryPropsWithComponent } from 'react-error-boundary';
12
+ import { registerBridgeLifeCycle } from '../lifecycle';
13
+ import { LoggerInstance, pathJoin } from '../utils';
14
+
15
+ export const getModuleName = (id: string) => {
16
+ // separate module name without detailed module path
17
+ // @vmok-e2e/edenx-demo-app2/button -> @vmok-e2e/edenx-demo-app2
18
+ const idArray = id.split('/');
19
+ if (idArray.length < 2) {
20
+ return id;
21
+ }
22
+ return idArray[0] + '/' + idArray[1];
23
+ };
24
+
25
+ export const getRootDomDefaultClassName = (moduleName: string) => {
26
+ const name = getModuleName(moduleName).replace(/\@/, '').replace(/\//, '-');
27
+ return `bridge-root-component-${name}`;
28
+ };
13
29
 
14
30
  declare const __APP_VERSION__: string;
15
31
  export interface RenderFnParams extends ProviderParams {
@@ -39,6 +55,7 @@ const RemoteAppWrapper = forwardRef(function (
39
55
  props: RemoteAppParams & RenderFnParams,
40
56
  ref,
41
57
  ) {
58
+ const bridgeHook = registerBridgeLifeCycle();
42
59
  const RemoteApp = () => {
43
60
  LoggerInstance.log(`RemoteAppWrapper RemoteApp props >>>`, { props });
44
61
  const {
@@ -65,7 +82,7 @@ const RemoteAppWrapper = forwardRef(function (
65
82
  const providerReturn = providerInfo();
66
83
  providerInfoRef.current = providerReturn;
67
84
 
68
- const renderProps = {
85
+ let renderProps = {
69
86
  moduleName,
70
87
  dom: rootRef.current,
71
88
  basename,
@@ -78,6 +95,24 @@ const RemoteAppWrapper = forwardRef(function (
78
95
  `createRemoteComponent LazyComponent render >>>`,
79
96
  renderProps,
80
97
  );
98
+
99
+ if (bridgeHook && bridgeHook?.lifecycle?.beforeBridgeRender) {
100
+ const beforeBridgeRenderRes =
101
+ bridgeHook?.lifecycle?.beforeBridgeRender.emit({
102
+ ...renderProps,
103
+ });
104
+ const extraProps =
105
+ beforeBridgeRenderRes &&
106
+ typeof beforeBridgeRenderRes === 'object' &&
107
+ beforeBridgeRenderRes?.extraProps
108
+ ? beforeBridgeRenderRes?.extraProps
109
+ : {};
110
+
111
+ renderProps = {
112
+ ...renderProps,
113
+ ...extraProps,
114
+ } as any;
115
+ }
81
116
  providerReturn.render(renderProps);
82
117
  });
83
118
 
@@ -89,6 +124,16 @@ const RemoteAppWrapper = forwardRef(function (
89
124
  `createRemoteComponent LazyComponent destroy >>>`,
90
125
  { moduleName, basename, dom: renderDom.current },
91
126
  );
127
+ if (bridgeHook && bridgeHook?.lifecycle?.afterBridgeDestroy) {
128
+ bridgeHook?.lifecycle?.afterBridgeDestroy.emit({
129
+ moduleName,
130
+ dom: renderDom.current,
131
+ basename,
132
+ memoryRoute,
133
+ fallback,
134
+ ...resProps,
135
+ });
136
+ }
92
137
  providerInfoRef.current?.destroy({
93
138
  dom: renderDom.current,
94
139
  });
@@ -97,9 +142,11 @@ const RemoteAppWrapper = forwardRef(function (
97
142
  };
98
143
  }, []);
99
144
 
145
+ // bridge-remote-root
146
+ const rootComponentClassName = `${getRootDomDefaultClassName(moduleName)} ${props?.className}`;
100
147
  return (
101
148
  <div
102
- className={props?.className}
149
+ className={rootComponentClassName}
103
150
  style={props?.style}
104
151
  ref={rootRef}
105
152
  ></div>
package/src/router-v5.tsx CHANGED
@@ -2,7 +2,6 @@ import React, { useContext } from 'react';
2
2
  // The upper alias react-router-dom$ into this file avoids the loop
3
3
  // @ts-ignore
4
4
  import * as ReactRouterDom from 'react-router-dom/index.js';
5
-
6
5
  import { RouterContext } from './context';
7
6
  import { LoggerInstance } from './utils';
8
7
 
package/src/router.tsx CHANGED
@@ -59,7 +59,7 @@ function WrapperRouterProvider(
59
59
  return <RouterProvider router={MemeoryRouterInstance} />;
60
60
  } else {
61
61
  const BrowserRouterInstance = createBrowserRouter(routers, {
62
- basename: routerContextProps.basename,
62
+ basename: routerContextProps.basename || router?.basename,
63
63
  future: router.future,
64
64
  window: router.window,
65
65
  });