@module-federation/bridge-react 0.0.0-next-20250408084324 → 0.0.0-next-20250410121036

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +9 -3
  2. package/dist/index.cjs.js +64 -136
  3. package/dist/index.d.ts +24 -1
  4. package/dist/index.es.js +67 -139
  5. package/dist/internal/bridge-base-CW88-1Be.es.js +103 -0
  6. package/dist/internal/bridge-base-DBiwuddQ.cjs.js +119 -0
  7. package/dist/internal/react-error-boundary.esm-CMdlkNPP.es.js +99 -0
  8. package/dist/internal/react-error-boundary.esm-D8nxmvmv.cjs.js +98 -0
  9. package/dist/legacy.cjs.js +4 -0
  10. package/dist/legacy.d.ts +104 -0
  11. package/dist/legacy.es.js +4 -0
  12. package/dist/router-v5.cjs.js +1 -1
  13. package/dist/router-v5.es.js +1 -1
  14. package/dist/router-v6.cjs.js +1 -1
  15. package/dist/router-v6.es.js +1 -1
  16. package/dist/router.cjs.js +1 -1
  17. package/dist/router.es.js +1 -1
  18. package/dist/v16.cjs.js +22 -0
  19. package/dist/v16.d.ts +104 -0
  20. package/dist/v16.es.js +22 -0
  21. package/dist/v18.cjs.js +15 -0
  22. package/dist/v18.d.ts +105 -0
  23. package/dist/v18.es.js +15 -0
  24. package/dist/v19.cjs.js +15 -0
  25. package/dist/v19.d.ts +106 -0
  26. package/dist/v19.es.js +15 -0
  27. package/package.json +19 -4
  28. package/src/index.ts +6 -0
  29. package/src/legacy.ts +13 -0
  30. package/src/provider/create.tsx +73 -20
  31. package/src/provider/versions/bridge-base.tsx +122 -0
  32. package/src/provider/versions/legacy.ts +42 -0
  33. package/src/provider/versions/v18.ts +47 -0
  34. package/src/provider/versions/v19.ts +48 -0
  35. package/src/remote/create.tsx +4 -2
  36. package/src/types.ts +19 -0
  37. package/src/utils/index.ts +0 -20
  38. package/src/v18.ts +9 -0
  39. package/src/v19.ts +9 -0
  40. package/vite.config.ts +54 -0
  41. package/src/provider/compat.ts +0 -60
  42. /package/dist/{context-C79iMWYD.cjs → internal/context.cjs.js} +0 -0
  43. /package/dist/{context-Dbqf0szX.js → internal/context.es.js} +0 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Base bridge component implementation
3
+ * This file contains bridge component logic shared across all React versions
4
+ */
5
+ import * as React from 'react';
6
+ import type {
7
+ ProviderParams,
8
+ ProviderFnParams,
9
+ RootType,
10
+ DestroyParams,
11
+ RenderParams,
12
+ CreateRootOptions,
13
+ } from '../../types';
14
+ import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
15
+ import { RouterContext } from '../context';
16
+ import { LoggerInstance } from '../../utils';
17
+ import { federationRuntime } from '../plugin';
18
+
19
+ export function createBaseBridgeComponent<T>({
20
+ createRoot,
21
+ defaultRootOptions,
22
+ ...bridgeInfo
23
+ }: ProviderFnParams<T>) {
24
+ return () => {
25
+ const rootMap = new Map<any, RootType>();
26
+ const instance = federationRuntime.instance;
27
+ LoggerInstance.debug(
28
+ `createBridgeComponent instance from props >>>`,
29
+ instance,
30
+ );
31
+
32
+ const RawComponent = (info: { propsInfo: T; appInfo: ProviderParams }) => {
33
+ const { appInfo, propsInfo, ...restProps } = info;
34
+ const { moduleName, memoryRoute, basename = '/' } = appInfo;
35
+ return (
36
+ <RouterContext.Provider value={{ moduleName, basename, memoryRoute }}>
37
+ <bridgeInfo.rootComponent
38
+ {...propsInfo}
39
+ basename={basename}
40
+ {...restProps}
41
+ />
42
+ </RouterContext.Provider>
43
+ );
44
+ };
45
+
46
+ return {
47
+ async render(info: RenderParams) {
48
+ LoggerInstance.debug(`createBridgeComponent render Info`, info);
49
+ const {
50
+ moduleName,
51
+ dom,
52
+ basename,
53
+ memoryRoute,
54
+ fallback,
55
+ rootOptions,
56
+ ...propsInfo
57
+ } = info;
58
+
59
+ const mergedRootOptions: CreateRootOptions | undefined = {
60
+ ...defaultRootOptions,
61
+ ...(rootOptions as CreateRootOptions),
62
+ };
63
+
64
+ const beforeBridgeRenderRes =
65
+ instance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit(info) || {};
66
+
67
+ const rootComponentWithErrorBoundary = (
68
+ <ErrorBoundary
69
+ FallbackComponent={fallback as React.ComponentType<FallbackProps>}
70
+ >
71
+ <RawComponent
72
+ appInfo={{
73
+ moduleName,
74
+ basename,
75
+ memoryRoute,
76
+ }}
77
+ propsInfo={
78
+ {
79
+ ...propsInfo,
80
+ ...(beforeBridgeRenderRes as any)?.extraProps,
81
+ } as T
82
+ }
83
+ />
84
+ </ErrorBoundary>
85
+ );
86
+
87
+ if (bridgeInfo.render) {
88
+ await Promise.resolve(
89
+ bridgeInfo.render(rootComponentWithErrorBoundary, dom),
90
+ ).then((root: RootType) => rootMap.set(dom, root));
91
+ } else {
92
+ let root = rootMap.get(dom);
93
+ // Do not call createRoot multiple times
94
+ if (!root && createRoot) {
95
+ root = createRoot(dom, mergedRootOptions);
96
+ rootMap.set(dom, root as any);
97
+ }
98
+
99
+ if (root && 'render' in root) {
100
+ root.render(rootComponentWithErrorBoundary);
101
+ }
102
+ }
103
+ instance?.bridgeHook?.lifecycle?.afterBridgeRender?.emit(info) || {};
104
+ },
105
+
106
+ destroy(info: DestroyParams) {
107
+ const { dom } = info;
108
+ LoggerInstance.debug(`createBridgeComponent destroy Info`, info);
109
+ const root = rootMap.get(dom);
110
+ if (root) {
111
+ if ('unmount' in root) {
112
+ root.unmount();
113
+ } else {
114
+ console.warn('Root does not have unmount method');
115
+ }
116
+ rootMap.delete(dom);
117
+ }
118
+ instance?.bridgeHook?.lifecycle?.afterBridgeDestroy?.emit(info);
119
+ },
120
+ };
121
+ };
122
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Entry point for React 16/17 (legacy) specific bridge components
3
+ * This file provides support for React 16 and 17 versions, using the traditional ReactDOM.render API
4
+ */
5
+ import type { ProviderFnParams } from '../../types';
6
+ import { createBaseBridgeComponent } from './bridge-base';
7
+ import ReactDOM from 'react-dom';
8
+
9
+ export interface CreateRootOptions {
10
+ identifierPrefix?: string;
11
+ onRecoverableError?: (error: unknown, errorInfo: unknown) => void;
12
+ }
13
+
14
+ export interface Root {
15
+ render(children: React.ReactNode): void;
16
+ unmount(): void;
17
+ }
18
+
19
+ export function createReact16Or17Root(
20
+ container: Element | DocumentFragment,
21
+ ): Root {
22
+ return {
23
+ render(children: React.ReactNode) {
24
+ // @ts-ignore - React 17's render method is deprecated but still functional
25
+ ReactDOM.render(children, container);
26
+ },
27
+ unmount() {
28
+ ReactDOM.unmountComponentAtNode(container as Element);
29
+ },
30
+ };
31
+ }
32
+
33
+ export function createBridgeComponent<T = any>(
34
+ bridgeInfo: Omit<ProviderFnParams<T>, 'createRoot'>,
35
+ ) {
36
+ const fullBridgeInfo = {
37
+ ...bridgeInfo,
38
+ createRoot: createReact16Or17Root,
39
+ } as unknown as ProviderFnParams<T>;
40
+
41
+ return createBaseBridgeComponent(fullBridgeInfo);
42
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Entry point for React 18 specific bridge components
3
+ */
4
+ import React from 'react';
5
+ import { createRoot as createReactRoot, hydrateRoot } from 'react-dom/client';
6
+ import { createBaseBridgeComponent } from './bridge-base';
7
+ import type { ProviderFnParams } from '../../types';
8
+
9
+ export interface CreateRootOptions {
10
+ identifierPrefix?: string;
11
+ onRecoverableError?: (error: unknown, errorInfo: unknown) => void;
12
+ }
13
+
14
+ export interface Root {
15
+ render(children: React.ReactNode): void;
16
+ unmount(): void;
17
+ }
18
+
19
+ export function createReact18Root(
20
+ container: Element | DocumentFragment,
21
+ options?: CreateRootOptions,
22
+ ): Root {
23
+ return createReactRoot(container, options);
24
+ }
25
+
26
+ export function hydrateReact18Root(
27
+ container: Element | DocumentFragment,
28
+ initialChildren: React.ReactNode,
29
+ options?: CreateRootOptions,
30
+ ) {
31
+ return hydrateRoot(
32
+ container as Element,
33
+ initialChildren as React.ReactElement,
34
+ options,
35
+ );
36
+ }
37
+
38
+ export function createBridgeComponent<T = any>(
39
+ bridgeInfo: Omit<ProviderFnParams<T>, 'createRoot'>,
40
+ ) {
41
+ const fullBridgeInfo = {
42
+ ...bridgeInfo,
43
+ createRoot: createReact18Root,
44
+ } as unknown as ProviderFnParams<T>;
45
+
46
+ return createBaseBridgeComponent(fullBridgeInfo);
47
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Entry point for React 19 specific bridge components
3
+ * This file provides support for React 19 version, using the new ReactDOM.createRoot API
4
+ */
5
+ import React from 'react';
6
+ import { createRoot, hydrateRoot } from 'react-dom/client';
7
+ import { createBaseBridgeComponent } from './bridge-base';
8
+ import type { ProviderFnParams } from '../../types';
9
+ export interface CreateRootOptions {
10
+ identifierPrefix?: string;
11
+ onRecoverableError?: (error: unknown, errorInfo: unknown) => void;
12
+ transitionCallbacks?: unknown;
13
+ }
14
+
15
+ export interface Root {
16
+ render(children: React.ReactNode): void;
17
+ unmount(): void;
18
+ }
19
+
20
+ export function createReact19Root(
21
+ container: Element | DocumentFragment,
22
+ options?: CreateRootOptions,
23
+ ): Root {
24
+ return createRoot(container as Element, options);
25
+ }
26
+
27
+ export function hydrateReact19Root(
28
+ container: Element | DocumentFragment,
29
+ initialChildren: React.ReactNode,
30
+ options?: CreateRootOptions,
31
+ ): Root {
32
+ return hydrateRoot(
33
+ container as Element,
34
+ initialChildren as React.ReactElement,
35
+ options,
36
+ );
37
+ }
38
+
39
+ export function createBridgeComponent<T = any>(
40
+ bridgeInfo: Omit<ProviderFnParams<T>, 'createRoot'>,
41
+ ) {
42
+ const fullBridgeInfo = {
43
+ ...bridgeInfo,
44
+ createRoot: createReact19Root,
45
+ } as unknown as ProviderFnParams<T>;
46
+
47
+ return createBaseBridgeComponent(fullBridgeInfo);
48
+ }
@@ -1,5 +1,5 @@
1
1
  import React, { forwardRef } from 'react';
2
- import { ErrorBoundary } from 'react-error-boundary';
2
+ import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
3
3
  import { LoggerInstance } from '../utils';
4
4
  import RemoteApp from './component';
5
5
  import {
@@ -77,7 +77,9 @@ export function createRemoteComponent<
77
77
  const LazyComponent = createLazyRemoteComponent(info);
78
78
  return forwardRef<HTMLDivElement, RemoteComponentProps>((props, ref) => {
79
79
  return (
80
- <ErrorBoundary FallbackComponent={info.fallback}>
80
+ <ErrorBoundary
81
+ FallbackComponent={info.fallback as React.ComponentType<FallbackProps>}
82
+ >
81
83
  <React.Suspense fallback={info.loading}>
82
84
  <LazyComponent {...props} ref={ref} />
83
85
  </React.Suspense>
package/src/types.ts CHANGED
@@ -34,6 +34,15 @@ export interface RenderParams {
34
34
  initialState?: Record<string, unknown>;
35
35
  };
36
36
  dom: HTMLElement;
37
+ /**
38
+ * Options to pass to createRoot for React 18 and 19
39
+ * @example
40
+ * {
41
+ * identifierPrefix: 'app-',
42
+ * onRecoverableError: (err) => console.error(err)
43
+ * }
44
+ */
45
+ rootOptions?: CreateRootOptions;
37
46
  [key: string]: unknown;
38
47
  }
39
48
 
@@ -81,6 +90,16 @@ export interface ProviderFnParams<T> {
81
90
  container: Element | DocumentFragment,
82
91
  options?: CreateRootOptions,
83
92
  ) => Root;
93
+ /**
94
+ * Default options to pass to createRoot for React 18 and 19
95
+ * These options will be used when creating a root unless overridden by rootOptions in render params
96
+ * @example
97
+ * {
98
+ * identifierPrefix: 'app-',
99
+ * onRecoverableError: (err) => console.error(err)
100
+ * }
101
+ */
102
+ defaultRootOptions?: CreateRootOptions;
84
103
  }
85
104
 
86
105
  /**
@@ -1,29 +1,9 @@
1
- import React from 'react';
2
1
  import { createLogger } from '@module-federation/sdk';
3
2
 
4
3
  export const LoggerInstance = createLogger(
5
4
  '[ Module Federation Bridge React ]',
6
5
  );
7
6
 
8
- type typeReact = typeof React;
9
-
10
- export function atLeastReact18(React: typeReact) {
11
- if (
12
- React &&
13
- typeof React.version === 'string' &&
14
- React.version.indexOf('.') >= 0
15
- ) {
16
- const majorVersionString = React.version.split('.')[0];
17
- try {
18
- return Number(majorVersionString) >= 18;
19
- } catch (err) {
20
- return false;
21
- }
22
- } else {
23
- return false;
24
- }
25
- }
26
-
27
7
  export function pathJoin(...args: string[]) {
28
8
  const res = args.reduce((res, path: string) => {
29
9
  let nPath = path;
package/src/v18.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { createBridgeComponent } from './provider/versions/v18';
2
+ export type { CreateRootOptions, Root } from './provider/versions/v18';
3
+ export type {
4
+ ProviderParams,
5
+ ProviderFnParams,
6
+ RootType,
7
+ DestroyParams,
8
+ RenderParams,
9
+ } from './types';
package/src/v19.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { createBridgeComponent } from './provider/versions/v19';
2
+ export type { CreateRootOptions, Root } from './provider/versions/v19';
3
+ export type {
4
+ ProviderParams,
5
+ ProviderFnParams,
6
+ RootType,
7
+ DestroyParams,
8
+ RenderParams,
9
+ } from './types';
package/vite.config.ts CHANGED
@@ -4,17 +4,53 @@ import path from 'path';
4
4
  import dts from 'vite-plugin-dts';
5
5
  // import react from '@vitejs/plugin-react';
6
6
  import packageJson from './package.json';
7
+ import fs from 'fs';
7
8
 
8
9
  const perDepsKeys = Object.keys(packageJson.peerDependencies);
9
10
 
10
11
  export default defineConfig({
11
12
  plugins: [
13
+ // 添加我们的自定义插件
12
14
  dts({
13
15
  rollupTypes: true,
14
16
  bundledPackages: [
15
17
  '@module-federation/bridge-shared',
16
18
  'react-error-boundary',
17
19
  ],
20
+ // 自定义类型声明文件的输出路径
21
+ afterBuild: () => {
22
+ // 确保provider目录存在
23
+ if (!fs.existsSync('dist/provider')) {
24
+ fs.mkdirSync('dist/provider', { recursive: true });
25
+ }
26
+
27
+ // 复制src/provider/versions中的类型声明文件到dist/provider目录
28
+ const srcDir = path.resolve(__dirname, 'dist/provider/versions');
29
+ const destDir = path.resolve(__dirname, 'dist/provider');
30
+
31
+ if (fs.existsSync(srcDir)) {
32
+ // 读取srcDir中的所有文件
33
+ const files = fs.readdirSync(srcDir);
34
+
35
+ // 复制每个v*.d.ts文件到destDir
36
+ files.forEach((file) => {
37
+ if (file.match(/^v\d+\.d\.ts$/)) {
38
+ const srcFile = path.join(srcDir, file);
39
+ const destFile = path.join(destDir, file);
40
+
41
+ // 读取源文件内容
42
+ const content = fs.readFileSync(srcFile, 'utf8');
43
+
44
+ // 写入目标文件
45
+ fs.writeFileSync(destFile, content);
46
+
47
+ console.log(`Copied ${srcFile} to ${destFile}`);
48
+ }
49
+ });
50
+ } else {
51
+ console.warn(`Source directory ${srcDir} does not exist.`);
52
+ }
53
+ },
18
54
  }),
19
55
  ],
20
56
  build: {
@@ -25,6 +61,10 @@ export default defineConfig({
25
61
  router: path.resolve(__dirname, 'src/router/default.tsx'),
26
62
  'router-v5': path.resolve(__dirname, 'src/router/v5.tsx'),
27
63
  'router-v6': path.resolve(__dirname, 'src/router/v6.tsx'),
64
+ v16: path.resolve(__dirname, 'src/legacy.ts'),
65
+ legacy: path.resolve(__dirname, 'src/legacy.ts'),
66
+ v18: path.resolve(__dirname, 'src/v18.ts'),
67
+ v19: path.resolve(__dirname, 'src/v19.ts'),
28
68
  },
29
69
  formats: ['cjs', 'es'],
30
70
  fileName: (format, entryName) => `${entryName}.${format}.js`,
@@ -39,6 +79,20 @@ export default defineConfig({
39
79
  'react-router-dom/index.js',
40
80
  'react-router-dom/dist/index.js',
41
81
  ],
82
+ output: {
83
+ // 将共享chunk文件放在internal目录下,并使用更清晰的命名
84
+ chunkFileNames: (chunkInfo) => {
85
+ // 根据chunk的名称或内容确定一个更有意义的名称
86
+ const name = chunkInfo.name;
87
+ if (name.includes('context')) {
88
+ return 'internal/context.[format].js';
89
+ }
90
+ if (name.includes('version-specific')) {
91
+ return 'internal/version-specific.[format].js';
92
+ }
93
+ return 'internal/[name]-[hash].[format].js';
94
+ },
95
+ },
42
96
  plugins: [
43
97
  {
44
98
  name: 'modify-output-plugin',
@@ -1,60 +0,0 @@
1
- import ReactDOM from 'react-dom';
2
- import { CreateRootOptions, Root } from '../types';
3
-
4
- // ReactDOM.version is only available in React 16.13.0 and later
5
- const isReact18 = ReactDOM.version?.startsWith('18');
6
-
7
- /**
8
- * Creates a root for a container element compatible with both React 16 and 18
9
- */
10
- export function createRoot(
11
- container: Element | DocumentFragment,
12
- options?: CreateRootOptions,
13
- ): Root {
14
- if (isReact18) {
15
- // For React 18, use the new createRoot API
16
- // @ts-ignore - Types will be available in React 18
17
- return (ReactDOM as any).createRoot(container, options);
18
- }
19
-
20
- // For React 16/17, simulate the new root API using render/unmountComponentAtNode
21
- return {
22
- render(children: React.ReactNode) {
23
- ReactDOM.render(children, container);
24
- },
25
- unmount() {
26
- ReactDOM.unmountComponentAtNode(container);
27
- },
28
- };
29
- }
30
-
31
- /**
32
- * Hydrates a container compatible with both React 16 and 18
33
- */
34
- export function hydrateRoot(
35
- container: Element | DocumentFragment,
36
- initialChildren: React.ReactNode,
37
- options?: CreateRootOptions,
38
- ): Root {
39
- if (isReact18) {
40
- // For React 18, use the new hydrateRoot API
41
- // @ts-ignore - Types will be available in React 18
42
- return (ReactDOM as any).hydrateRoot(container, initialChildren, options);
43
- }
44
-
45
- // For React 16/17, simulate the new root API using hydrate/unmountComponentAtNode
46
- return {
47
- render(children: React.ReactNode) {
48
- // For the initial render, use hydrate
49
- if (children === initialChildren) {
50
- ReactDOM.hydrate(children, container);
51
- } else {
52
- // For subsequent renders, use regular render
53
- ReactDOM.render(children, container);
54
- }
55
- },
56
- unmount() {
57
- ReactDOM.unmountComponentAtNode(container);
58
- },
59
- };
60
- }