@module-federation/bridge-react 0.0.0-docs-remove-invalid-lark-link-20251205062649

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 (112) hide show
  1. package/CHANGELOG.md +728 -0
  2. package/LICENSE +21 -0
  3. package/README.md +131 -0
  4. package/__tests__/bridge.spec.tsx +160 -0
  5. package/__tests__/createLazyComponent.spec.tsx +209 -0
  6. package/__tests__/prefetch.spec.ts +156 -0
  7. package/__tests__/router.spec.tsx +82 -0
  8. package/__tests__/setupTests.ts +8 -0
  9. package/__tests__/util.ts +36 -0
  10. package/dist/base.cjs.js +29 -0
  11. package/dist/base.d.ts +311 -0
  12. package/dist/base.es.js +30 -0
  13. package/dist/bridge-base-CPSTBjEp.mjs +211 -0
  14. package/dist/bridge-base-RStDxH71.js +226 -0
  15. package/dist/createHelpers-B_L612IN.js +190 -0
  16. package/dist/createHelpers-Ui5pt7je.mjs +191 -0
  17. package/dist/data-fetch-server-middleware.cjs.js +163 -0
  18. package/dist/data-fetch-server-middleware.d.ts +15 -0
  19. package/dist/data-fetch-server-middleware.es.js +164 -0
  20. package/dist/data-fetch-utils.cjs.js +24 -0
  21. package/dist/data-fetch-utils.d.ts +81 -0
  22. package/dist/data-fetch-utils.es.js +26 -0
  23. package/dist/index-DRSBaSu3.js +45 -0
  24. package/dist/index-DyQNwY2M.mjs +46 -0
  25. package/dist/index.cjs.js +125 -0
  26. package/dist/index.d.ts +299 -0
  27. package/dist/index.es.js +109 -0
  28. package/dist/index.esm-BWaKho-8.js +491 -0
  29. package/dist/index.esm-CPwSeCvw.mjs +492 -0
  30. package/dist/lazy-load-component-plugin-CSRkMmKF.js +521 -0
  31. package/dist/lazy-load-component-plugin-DXqhuywC.mjs +522 -0
  32. package/dist/lazy-load-component-plugin.cjs.js +6 -0
  33. package/dist/lazy-load-component-plugin.d.ts +16 -0
  34. package/dist/lazy-load-component-plugin.es.js +6 -0
  35. package/dist/lazy-utils.cjs.js +24 -0
  36. package/dist/lazy-utils.d.ts +149 -0
  37. package/dist/lazy-utils.es.js +24 -0
  38. package/dist/plugin.cjs.js +14 -0
  39. package/dist/plugin.d.ts +22 -0
  40. package/dist/plugin.es.js +14 -0
  41. package/dist/prefetch-A3QkU5oZ.js +1272 -0
  42. package/dist/prefetch-zMJL79zx.mjs +1273 -0
  43. package/dist/router-v5.cjs.js +55 -0
  44. package/dist/router-v5.d.ts +18 -0
  45. package/dist/router-v5.es.js +32 -0
  46. package/dist/router-v6.cjs.js +84 -0
  47. package/dist/router-v6.d.ts +20 -0
  48. package/dist/router-v6.es.js +61 -0
  49. package/dist/router-v7.cjs.js +83 -0
  50. package/dist/router-v7.d.ts +20 -0
  51. package/dist/router-v7.es.js +61 -0
  52. package/dist/router.cjs.js +82 -0
  53. package/dist/router.d.ts +20 -0
  54. package/dist/router.es.js +60 -0
  55. package/dist/utils-dUgb9Jkm.mjs +2016 -0
  56. package/dist/utils-tM9yE73c.js +2015 -0
  57. package/dist/v18.cjs.js +15 -0
  58. package/dist/v18.d.ts +114 -0
  59. package/dist/v18.es.js +15 -0
  60. package/dist/v19.cjs.js +15 -0
  61. package/dist/v19.d.ts +115 -0
  62. package/dist/v19.es.js +15 -0
  63. package/jest.config.ts +21 -0
  64. package/package.json +173 -0
  65. package/project.json +23 -0
  66. package/src/base.ts +50 -0
  67. package/src/index.ts +50 -0
  68. package/src/lazy/AwaitDataFetch.tsx +215 -0
  69. package/src/lazy/constant.ts +30 -0
  70. package/src/lazy/createLazyComponent.tsx +411 -0
  71. package/src/lazy/data-fetch/cache.ts +291 -0
  72. package/src/lazy/data-fetch/call-data-fetch.ts +13 -0
  73. package/src/lazy/data-fetch/data-fetch-server-middleware.ts +196 -0
  74. package/src/lazy/data-fetch/index.ts +16 -0
  75. package/src/lazy/data-fetch/inject-data-fetch.ts +109 -0
  76. package/src/lazy/data-fetch/prefetch.ts +112 -0
  77. package/src/lazy/data-fetch/runtime-plugin.ts +115 -0
  78. package/src/lazy/index.ts +35 -0
  79. package/src/lazy/logger.ts +6 -0
  80. package/src/lazy/types.ts +75 -0
  81. package/src/lazy/utils.ts +372 -0
  82. package/src/lazy/wrapNoSSR.tsx +10 -0
  83. package/src/modern-app-env.d.ts +2 -0
  84. package/src/plugins/lazy-load-component-plugin.spec.ts +21 -0
  85. package/src/plugins/lazy-load-component-plugin.ts +57 -0
  86. package/src/provider/context.tsx +4 -0
  87. package/src/provider/plugin.ts +22 -0
  88. package/src/provider/versions/bridge-base.tsx +150 -0
  89. package/src/provider/versions/legacy.ts +87 -0
  90. package/src/provider/versions/v18.ts +47 -0
  91. package/src/provider/versions/v19.ts +48 -0
  92. package/src/remote/RemoteAppWrapper.tsx +108 -0
  93. package/src/remote/base-component/component.tsx +2 -0
  94. package/src/remote/base-component/create.tsx +23 -0
  95. package/src/remote/base-component/index.tsx +10 -0
  96. package/src/remote/createHelpers.tsx +130 -0
  97. package/src/remote/router-component/component.tsx +104 -0
  98. package/src/remote/router-component/create.tsx +23 -0
  99. package/src/remote/router-component/index.tsx +10 -0
  100. package/src/router/default.tsx +73 -0
  101. package/src/router/v5.tsx +43 -0
  102. package/src/router/v6.tsx +74 -0
  103. package/src/router/v7.tsx +75 -0
  104. package/src/types.ts +147 -0
  105. package/src/utils/index.ts +44 -0
  106. package/src/v18.ts +9 -0
  107. package/src/v19.ts +9 -0
  108. package/tsconfig.json +42 -0
  109. package/tsconfig.node.json +11 -0
  110. package/tsconfig.spec.json +26 -0
  111. package/vite.config.ts +112 -0
  112. package/vitest.config.ts +27 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 ScriptedAlchemy LLC (Zack Jackson) Zhou Shaw (zhouxiao)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # React Bridge
2
+
3
+ React bridge is used to load the routing module in mf, so that the routing module can work properly with the host environment.
4
+
5
+ > When to use
6
+
7
+ - Load the route module
8
+ - Load across the front end framework
9
+
10
+ ## How to use
11
+
12
+ # 1. Install the react bridge library
13
+
14
+ ```bash
15
+ pnpm add @module-federation/bridge-react
16
+ ```
17
+
18
+ # 2. Configure the react bridge library
19
+
20
+ > Use createBridgeComponent create component provider
21
+
22
+ ```jsx
23
+ // ./src/index.tsx
24
+ import { createBridgeComponent } from '@module-federation/bridge-react';
25
+
26
+ function App() {
27
+ return ( <BrowserRouter basename="/">
28
+ <Routes>
29
+ <Route path="/" Component={()=> <div>Home page</div>}>
30
+ <Route path="/detail" Component={()=> <div>Detail page</div>}>
31
+ </Routes>
32
+ </BrowserRouter>)
33
+ }
34
+
35
+ export default createBridgeComponent({
36
+ rootComponent: App
37
+ });
38
+ ```
39
+
40
+ > set alias to proxy
41
+
42
+ ```js
43
+ //rsbuild.config.ts
44
+ export default defineConfig({
45
+ source: {
46
+ alias: {
47
+ 'react-router-dom$': path.resolve(
48
+ __dirname,
49
+ 'node_modules/@module-federation/bridge-react/dist/router.es.js',
50
+ ),
51
+ },
52
+ },
53
+ server: {
54
+ port: 2001,
55
+ host: 'localhost',
56
+ },
57
+ dev: {
58
+ assetPrefix: 'http://localhost:2001',
59
+ },
60
+ tools: {
61
+ rspack: (config, { appendPlugins }) => {
62
+ delete config.optimization?.splitChunks;
63
+ config.output!.uniqueName = 'remote1';
64
+ appendPlugins([
65
+ new ModuleFederationPlugin({
66
+ name: 'remote1',
67
+ exposes: {
68
+ './export-app': './src/index.tsx',
69
+ }
70
+ }),
71
+ ]);
72
+ },
73
+ },
74
+ });
75
+ ```
76
+
77
+ # 3. Load the module with routing
78
+
79
+ ```js
80
+ //rsbuild.config.ts
81
+ export default defineConfig({
82
+ tools: {
83
+ rspack: (config, { appendPlugins }) => {
84
+ config.output!.uniqueName = 'host';
85
+ appendPlugins([
86
+ new ModuleFederationPlugin({
87
+ name: 'host',
88
+ remotes: {
89
+ remote1: 'remote1@http://localhost:2001/mf-manifest.json',
90
+ },
91
+ }),
92
+ ]);
93
+ },
94
+ },
95
+ });
96
+ ```
97
+
98
+ > Use the module
99
+
100
+ ```jsx
101
+ // ./src/index.tsx
102
+ import { createBridgeComponent } from '@module-federation/bridge-react';
103
+
104
+ const Remote1 = createBridgeComponent(()=> import('remote1/export-app'));
105
+
106
+ function App() {
107
+ return ( <BrowserRouter basename="/">
108
+ <ul>
109
+ <li>
110
+ <Link to="/">
111
+ Home
112
+ </Link>
113
+ </li>
114
+ <li>
115
+ <Link to="/remote1">
116
+ Remote1
117
+ </Link>
118
+ </li>
119
+ </ul>
120
+ <Routes>
121
+ <Route path="/" Component={()=> <div>Home page</div>}>
122
+ <Route path="/remote1" Component={()=> <Remote1 />}>
123
+ </Routes>
124
+ </BrowserRouter>)
125
+ }
126
+
127
+ const root = ReactDOM.createRoot(document.getElementById('root')!);
128
+ root.render(
129
+ <App />
130
+ );
131
+ ```
@@ -0,0 +1,160 @@
1
+ import React from 'react';
2
+ import { createBridgeComponent, createRemoteAppComponent } from '../src';
3
+ import {
4
+ act,
5
+ fireEvent,
6
+ render,
7
+ screen,
8
+ waitFor,
9
+ } from '@testing-library/react';
10
+ import { createContainer, getHtml } from './util';
11
+
12
+ describe('bridge', () => {
13
+ let containerInfo: ReturnType<typeof createContainer>;
14
+ beforeEach(() => {
15
+ containerInfo = createContainer();
16
+ });
17
+
18
+ afterEach(() => {
19
+ containerInfo?.clean();
20
+ });
21
+
22
+ it('createBridgeComponent life cycle', async () => {
23
+ function Component() {
24
+ return <div>life cycle render</div>;
25
+ }
26
+ const lifeCycle = createBridgeComponent({
27
+ rootComponent: Component,
28
+ })();
29
+
30
+ lifeCycle.render({
31
+ dom: containerInfo?.container,
32
+ });
33
+
34
+ await waitFor(
35
+ () => {
36
+ expect(document.querySelector('#container')?.innerHTML).toContain(
37
+ '<div>life cycle render</div>',
38
+ );
39
+ },
40
+ { timeout: 2000 },
41
+ );
42
+
43
+ lifeCycle.destroy({
44
+ dom: containerInfo?.container,
45
+ moduleName: 'test',
46
+ });
47
+
48
+ await waitFor(
49
+ () => {
50
+ expect(
51
+ (document.querySelector('#container')?.innerHTML || '').trim(),
52
+ ).toBe('');
53
+ },
54
+ { timeout: 2000 },
55
+ );
56
+ });
57
+
58
+ it('createRemoteAppComponent', async () => {
59
+ function Component({ props }: { props?: Record<string, any> }) {
60
+ return <div>life cycle render {props?.msg}</div>;
61
+ }
62
+ const BridgeComponent = createBridgeComponent({
63
+ rootComponent: Component,
64
+ });
65
+ const RemoteComponent = createRemoteAppComponent({
66
+ loader: async () => {
67
+ return {
68
+ default: BridgeComponent,
69
+ };
70
+ },
71
+ fallback: () => <div></div>,
72
+ loading: <div>loading</div>,
73
+ });
74
+
75
+ const { container } = render(
76
+ <RemoteComponent props={{ msg: 'hello world' }} />,
77
+ );
78
+ expect(getHtml(container)).toMatch('loading');
79
+
80
+ await waitFor(
81
+ () => {
82
+ expect(getHtml(container)).toMatch('life cycle render');
83
+ expect(getHtml(container)).toMatch('hello world');
84
+ },
85
+ { timeout: 2000 },
86
+ );
87
+ });
88
+
89
+ it('createRemoteAppComponent and obtain ref property', async () => {
90
+ const ref = {
91
+ current: null,
92
+ };
93
+
94
+ function Component({ props }: { props?: Record<string, any> }) {
95
+ return <div>life cycle render {props?.msg}</div>;
96
+ }
97
+ const BridgeComponent = createBridgeComponent({
98
+ rootComponent: Component,
99
+ });
100
+ const RemoteComponent = createRemoteAppComponent({
101
+ loader: async () => {
102
+ return {
103
+ default: BridgeComponent,
104
+ };
105
+ },
106
+ fallback: () => <div></div>,
107
+ loading: <div>loading</div>,
108
+ });
109
+
110
+ const { container } = render(
111
+ <RemoteComponent ref={ref} props={{ msg: 'hello world' }} />,
112
+ );
113
+ expect(getHtml(container)).toMatch('loading');
114
+
115
+ await waitFor(
116
+ () => {
117
+ expect(getHtml(container)).toMatch('life cycle render');
118
+ expect(getHtml(container)).toMatch('hello world');
119
+ expect(ref.current).not.toBeNull();
120
+ },
121
+ { timeout: 2000 },
122
+ );
123
+ });
124
+
125
+ it('createRemoteAppComponent with custom createRoot prop', async () => {
126
+ const renderMock = jest.fn();
127
+
128
+ function Component({ props }: { props?: Record<string, any> }) {
129
+ return <div>life cycle render {props?.msg}</div>;
130
+ }
131
+ const BridgeComponent = createBridgeComponent({
132
+ rootComponent: Component,
133
+ createRoot: () => {
134
+ return {
135
+ render: renderMock,
136
+ unmount: jest.fn(),
137
+ };
138
+ },
139
+ });
140
+ const RemoteComponent = createRemoteAppComponent({
141
+ loader: async () => {
142
+ return {
143
+ default: BridgeComponent,
144
+ };
145
+ },
146
+ fallback: () => <div></div>,
147
+ loading: <div>loading</div>,
148
+ });
149
+
150
+ const { container } = render(<RemoteComponent />);
151
+ expect(getHtml(container)).toMatch('loading');
152
+
153
+ await waitFor(
154
+ () => {
155
+ expect(renderMock).toHaveBeenCalledTimes(1);
156
+ },
157
+ { timeout: 2000 },
158
+ );
159
+ });
160
+ });
@@ -0,0 +1,209 @@
1
+ import React, { Suspense } from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import {
4
+ createLazyComponent,
5
+ collectSSRAssets,
6
+ } from '../src/lazy/createLazyComponent';
7
+ import * as runtime from '@module-federation/runtime';
8
+ import * as utils from '../src/lazy/utils';
9
+
10
+ // Mocking dependencies
11
+ jest.mock('@module-federation/runtime');
12
+ jest.mock('../src/lazy/utils');
13
+
14
+ const mockGetInstance = runtime.getInstance as jest.Mock;
15
+ const mockGetLoadedRemoteInfos = utils.getLoadedRemoteInfos as jest.Mock;
16
+ const mockGetDataFetchMapKey = utils.getDataFetchMapKey as jest.Mock;
17
+ const mockFetchData = utils.fetchData as jest.Mock;
18
+
19
+ const MockComponent = () => <div>Mock Component</div>;
20
+ const LoadingComponent = () => <div>Loading...</div>;
21
+ const ErrorComponent = () => <div>Error!</div>;
22
+
23
+ describe('createLazyComponent', () => {
24
+ let mockInstance: any;
25
+
26
+ beforeEach(() => {
27
+ jest.clearAllMocks();
28
+ mockInstance = {
29
+ name: 'host-app',
30
+ options: { version: '1.0.0' },
31
+ getModuleInfo: jest.fn(),
32
+ };
33
+ mockGetInstance.mockReturnValue(mockInstance);
34
+ mockGetLoadedRemoteInfos.mockReturnValue({
35
+ name: 'remoteApp',
36
+ alias: 'remote',
37
+ expose: './Component',
38
+ version: '1.0.0',
39
+ snapshot: {
40
+ modules: [
41
+ {
42
+ modulePath: './Component',
43
+ assets: {
44
+ css: { sync: [], async: [] },
45
+ js: { sync: [], async: [] },
46
+ },
47
+ },
48
+ ],
49
+ publicPath: 'http://localhost:3001/',
50
+ remoteEntry: 'remoteEntry.js',
51
+ },
52
+ entryGlobalName: 'remoteApp',
53
+ });
54
+ mockGetDataFetchMapKey.mockReturnValue('data-fetch-key');
55
+ });
56
+
57
+ it('should render loading component then the actual component', async () => {
58
+ const loader = jest.fn().mockResolvedValue({
59
+ default: MockComponent,
60
+ [Symbol.for('mf_module_id')]: 'remoteApp/Component',
61
+ });
62
+
63
+ const LazyComponent = createLazyComponent({
64
+ loader,
65
+ instance: mockInstance,
66
+ loading: <LoadingComponent />,
67
+ fallback: <ErrorComponent />,
68
+ });
69
+
70
+ render(
71
+ <Suspense fallback={<LoadingComponent />}>
72
+ <LazyComponent />
73
+ </Suspense>,
74
+ );
75
+
76
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
77
+
78
+ await waitFor(() => {
79
+ expect(screen.getByText('Mock Component')).toBeInTheDocument();
80
+ });
81
+ });
82
+
83
+ it('should render fallback component on data fetch error', async () => {
84
+ mockFetchData.mockRejectedValue(new Error('Data fetch failed'));
85
+ const LazyComponentWithDataFetch = createLazyComponent({
86
+ loader: jest.fn().mockResolvedValue({
87
+ default: MockComponent,
88
+ [Symbol.for('mf_module_id')]: 'remoteApp/Component',
89
+ }),
90
+ instance: mockInstance,
91
+ loading: <LoadingComponent />,
92
+ fallback: <ErrorComponent />,
93
+ });
94
+
95
+ render(<LazyComponentWithDataFetch />);
96
+
97
+ await waitFor(() => {
98
+ expect(screen.getByText('Error!')).toBeInTheDocument();
99
+ });
100
+ });
101
+
102
+ it('should fetch data and pass it to the component', async () => {
103
+ const loader = jest.fn().mockResolvedValue({
104
+ default: (props: { mfData: any }) => (
105
+ <div>Data: {JSON.stringify(props.mfData)}</div>
106
+ ),
107
+ [Symbol.for('mf_module_id')]: 'remoteApp/Component',
108
+ });
109
+ const mockData = { message: 'Hello' };
110
+ mockFetchData.mockResolvedValue(mockData);
111
+
112
+ const LazyComponent = createLazyComponent({
113
+ loader,
114
+ instance: mockInstance,
115
+ loading: <LoadingComponent />,
116
+ fallback: <ErrorComponent />,
117
+ });
118
+
119
+ render(<LazyComponent />);
120
+
121
+ await waitFor(() => {
122
+ expect(
123
+ screen.getByText(`Data: ${JSON.stringify(mockData)}`),
124
+ ).toBeInTheDocument();
125
+ });
126
+ });
127
+ });
128
+
129
+ describe('collectSSRAssets', () => {
130
+ let mockInstance: any;
131
+
132
+ beforeEach(() => {
133
+ jest.clearAllMocks();
134
+ mockInstance = {
135
+ name: 'host-app',
136
+ options: { version: '1.0.0' },
137
+ };
138
+ mockGetInstance.mockReturnValue(mockInstance);
139
+ });
140
+
141
+ it('should return an empty array if instance is not available', () => {
142
+ const assets = collectSSRAssets({
143
+ id: 'test/expose',
144
+ instance: undefined as any,
145
+ });
146
+ expect(assets).toEqual([]);
147
+ });
148
+
149
+ it('should return an empty array if module info is not found', () => {
150
+ mockGetLoadedRemoteInfos.mockReturnValue(undefined);
151
+ const assets = collectSSRAssets({
152
+ id: 'test/expose',
153
+ instance: mockInstance,
154
+ });
155
+ expect(assets).toEqual([]);
156
+ });
157
+
158
+ it('should collect CSS and JS assets for SSR', () => {
159
+ mockGetLoadedRemoteInfos.mockReturnValue({
160
+ name: 'remoteApp',
161
+ expose: './Component',
162
+ snapshot: {
163
+ publicPath: 'http://localhost:3001/',
164
+ remoteEntry: 'remoteEntry.js',
165
+ modules: [
166
+ {
167
+ modulePath: './Component',
168
+ assets: {
169
+ css: { sync: ['main.css'], async: ['extra.css'] },
170
+ js: { sync: ['main.js'], async: [] },
171
+ },
172
+ },
173
+ ],
174
+ },
175
+ });
176
+
177
+ const assets = collectSSRAssets({
178
+ id: 'remoteApp/Component',
179
+ instance: mockInstance,
180
+ injectScript: true,
181
+ injectLink: true,
182
+ });
183
+
184
+ expect(assets).toHaveLength(4); // 2 links, 2 scripts
185
+
186
+ const links = assets.filter(
187
+ (asset) => (asset as React.ReactElement).type === 'link',
188
+ );
189
+ const scripts = assets.filter(
190
+ (asset) => (asset as React.ReactElement).type === 'script',
191
+ );
192
+
193
+ expect(links).toHaveLength(2);
194
+ expect((links[0] as React.ReactElement).props.href).toBe(
195
+ 'http://localhost:3001/extra.css',
196
+ );
197
+ expect((links[1] as React.ReactElement).props.href).toBe(
198
+ 'http://localhost:3001/main.css',
199
+ );
200
+
201
+ expect(scripts).toHaveLength(2);
202
+ expect((scripts[0] as React.ReactElement).props.src).toBe(
203
+ 'http://localhost:3001/remoteEntry.js',
204
+ );
205
+ expect((scripts[1] as React.ReactElement).props.src).toBe(
206
+ 'http://localhost:3001/main.js',
207
+ );
208
+ });
209
+ });
@@ -0,0 +1,156 @@
1
+ import { prefetch } from '../src/lazy/data-fetch/prefetch';
2
+ import * as utils from '../src/lazy/utils';
3
+ import logger from '../src/lazy/logger';
4
+ import helpers from '@module-federation/runtime/helpers';
5
+
6
+ // Mock dependencies
7
+ jest.mock('../src/lazy/logger');
8
+ jest.mock('../src/lazy/utils');
9
+ jest.mock('@module-federation/runtime/helpers', () => ({
10
+ default: {
11
+ utils: {
12
+ matchRemoteWithNameAndExpose: jest.fn(),
13
+ getRemoteInfo: jest.fn(),
14
+ },
15
+ },
16
+ utils: {
17
+ matchRemoteWithNameAndExpose: jest.fn(),
18
+ getRemoteInfo: jest.fn(),
19
+ },
20
+ }));
21
+
22
+ describe('prefetch', () => {
23
+ let mockInstance: any;
24
+
25
+ beforeEach(() => {
26
+ jest.clearAllMocks();
27
+ mockInstance = {
28
+ name: 'host',
29
+ options: {
30
+ version: '1.0.0',
31
+ remotes: [
32
+ {
33
+ name: 'remote1',
34
+ alias: 'remote1_alias',
35
+ entry: 'http://localhost:3001/remoteEntry.js',
36
+ },
37
+ ],
38
+ },
39
+ snapshotHandler: {
40
+ loadRemoteSnapshotInfo: jest.fn(),
41
+ },
42
+ remoteHandler: {
43
+ hooks: {
44
+ lifecycle: {
45
+ generatePreloadAssets: {
46
+ emit: jest.fn(),
47
+ },
48
+ },
49
+ },
50
+ },
51
+ };
52
+ });
53
+
54
+ it('should log an error if id is not provided', async () => {
55
+ // @ts-ignore
56
+ await prefetch({ instance: mockInstance });
57
+ expect(logger.error).toHaveBeenCalledWith('id is required for prefetch!');
58
+ });
59
+
60
+ it('should log an error if instance is not provided', async () => {
61
+ // @ts-ignore
62
+ await prefetch({ id: 'remote1/component1' });
63
+ expect(logger.error).toHaveBeenCalledWith(
64
+ 'instance is required for prefetch!',
65
+ );
66
+ });
67
+
68
+ it('should log an error if remote is not found', async () => {
69
+ (helpers.utils.matchRemoteWithNameAndExpose as jest.Mock).mockReturnValue(
70
+ undefined,
71
+ );
72
+ await prefetch({ id: 'nonexistent/component', instance: mockInstance });
73
+ expect(logger.error).toHaveBeenCalledWith(
74
+ `Can not found 'nonexistent/component' in instance.options.remotes!`,
75
+ );
76
+ });
77
+
78
+ it('should successfully prefetch data and component resources', async () => {
79
+ const mockRemoteInfo = {
80
+ remote: { name: 'remote1', alias: 'remote1_alias' },
81
+ expose: './component1',
82
+ };
83
+ (helpers.utils.matchRemoteWithNameAndExpose as jest.Mock).mockReturnValue(
84
+ mockRemoteInfo,
85
+ );
86
+ (
87
+ mockInstance.snapshotHandler.loadRemoteSnapshotInfo as jest.Mock
88
+ ).mockResolvedValue({
89
+ remoteSnapshot: {},
90
+ globalSnapshot: {},
91
+ });
92
+ (helpers.utils.getRemoteInfo as jest.Mock).mockReturnValue({});
93
+
94
+ const mockDataFetchFn = jest
95
+ .fn()
96
+ .mockResolvedValue({ data: 'prefetched data' });
97
+ const mockGetDataFetchGetter = jest.fn().mockResolvedValue(mockDataFetchFn);
98
+ const mockDataFetchMap = {
99
+ 'remote1_alias@remote1/component1': [
100
+ [mockGetDataFetchGetter, 'GET', undefined],
101
+ ],
102
+ };
103
+ (utils.getDataFetchMap as jest.Mock).mockReturnValue(mockDataFetchMap);
104
+ (utils.getDataFetchInfo as jest.Mock).mockReturnValue({
105
+ name: 'remote1',
106
+ alias: 'remote1_alias',
107
+ id: 'remote1/component1',
108
+ });
109
+ (utils.getDataFetchMapKey as jest.Mock).mockReturnValue(
110
+ 'remote1_alias@remote1/component1',
111
+ );
112
+
113
+ await prefetch({
114
+ id: 'remote1/component1',
115
+ instance: mockInstance,
116
+ dataFetchParams: { some: 'param', isDowngrade: false } as any,
117
+ preloadComponentResource: true,
118
+ });
119
+
120
+ expect(
121
+ mockInstance.remoteHandler.hooks.lifecycle.generatePreloadAssets.emit,
122
+ ).toHaveBeenCalled();
123
+
124
+ expect(mockGetDataFetchGetter).toHaveBeenCalled();
125
+ await new Promise(process.nextTick);
126
+ expect(mockDataFetchFn).toHaveBeenCalledWith({
127
+ some: 'param',
128
+ _id: 'remote1_alias@remote1/component1',
129
+ isDowngrade: false,
130
+ });
131
+ });
132
+
133
+ it('should handle cases where data fetch info is not available', async () => {
134
+ const mockRemoteInfo = {
135
+ remote: { name: 'remote1', alias: 'remote1_alias' },
136
+ expose: './component1',
137
+ };
138
+ (helpers.utils.matchRemoteWithNameAndExpose as jest.Mock).mockReturnValue(
139
+ mockRemoteInfo,
140
+ );
141
+ (
142
+ mockInstance.snapshotHandler.loadRemoteSnapshotInfo as jest.Mock
143
+ ).mockResolvedValue({
144
+ remoteSnapshot: {},
145
+ globalSnapshot: {},
146
+ });
147
+ (utils.getDataFetchMap as jest.Mock).mockReturnValue(undefined);
148
+
149
+ await prefetch({
150
+ id: 'remote1/component1',
151
+ instance: mockInstance,
152
+ });
153
+
154
+ expect(utils.getDataFetchInfo).not.toHaveBeenCalled();
155
+ });
156
+ });