@module-federation/bridge-react 0.16.0 → 0.17.1
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 +39 -0
- package/__tests__/bridge.spec.tsx +10 -10
- package/__tests__/createLazyComponent.spec.tsx +209 -0
- package/__tests__/prefetch.spec.ts +156 -0
- package/__tests__/router.spec.tsx +3 -3
- package/__tests__/setupTests.ts +8 -0
- package/dist/{bridge-base-P6pEjY1q.js → bridge-base-BoshEggF.mjs} +1 -1
- package/dist/{bridge-base-BBH982Tz.cjs → bridge-base-UGCwcMnG.js} +1 -1
- package/dist/data-fetch-server-middleware.cjs.js +163 -0
- package/dist/data-fetch-server-middleware.d.ts +15 -0
- package/dist/data-fetch-server-middleware.es.js +164 -0
- package/dist/data-fetch-utils.cjs.js +24 -0
- package/dist/data-fetch-utils.d.ts +81 -0
- package/dist/data-fetch-utils.es.js +26 -0
- package/dist/index-C0fDZB5b.js +45 -0
- package/dist/index-CqxytsLY.mjs +46 -0
- package/dist/index.cjs.js +35 -9
- package/dist/index.d.ts +141 -1
- package/dist/index.es.js +39 -13
- package/dist/index.esm-BCeUd-x9.mjs +418 -0
- package/dist/index.esm-j_1sIRzg.js +417 -0
- package/dist/lazy-load-component-plugin-B80Ud11k.js +521 -0
- package/dist/lazy-load-component-plugin-_UbR2mWQ.mjs +522 -0
- package/dist/lazy-load-component-plugin.cjs.js +6 -0
- package/dist/lazy-load-component-plugin.d.ts +16 -0
- package/dist/lazy-load-component-plugin.es.js +6 -0
- package/dist/lazy-utils.cjs.js +24 -0
- package/dist/lazy-utils.d.ts +149 -0
- package/dist/lazy-utils.es.js +24 -0
- package/dist/plugin.d.ts +13 -4
- package/dist/prefetch-BaKIdUwP.js +1338 -0
- package/dist/prefetch-YpJYjpWC.mjs +1339 -0
- package/dist/router-v5.cjs.js +1 -1
- package/dist/router-v5.d.ts +9 -0
- package/dist/router-v5.es.js +1 -1
- package/dist/router-v6.cjs.js +1 -1
- package/dist/router-v6.d.ts +9 -0
- package/dist/router-v6.es.js +1 -1
- package/dist/router.cjs.js +1 -1
- package/dist/router.d.ts +9 -0
- package/dist/router.es.js +1 -1
- package/dist/utils-C4oPJV34.mjs +2016 -0
- package/dist/utils-iEVlDmyk.js +2015 -0
- package/dist/v18.cjs.js +1 -1
- package/dist/v18.d.ts +9 -0
- package/dist/v18.es.js +1 -1
- package/dist/v19.cjs.js +1 -1
- package/dist/v19.d.ts +9 -0
- package/dist/v19.es.js +1 -1
- package/jest.config.ts +21 -0
- package/package.json +48 -6
- package/project.json +4 -8
- package/src/index.ts +32 -1
- package/src/lazy/AwaitDataFetch.tsx +215 -0
- package/src/lazy/constant.ts +30 -0
- package/src/lazy/createLazyComponent.tsx +411 -0
- package/src/lazy/data-fetch/cache.ts +291 -0
- package/src/lazy/data-fetch/call-data-fetch.ts +13 -0
- package/src/lazy/data-fetch/data-fetch-server-middleware.ts +196 -0
- package/src/lazy/data-fetch/index.ts +16 -0
- package/src/lazy/data-fetch/inject-data-fetch.ts +109 -0
- package/src/lazy/data-fetch/prefetch.ts +106 -0
- package/src/lazy/data-fetch/runtime-plugin.ts +115 -0
- package/src/lazy/index.ts +35 -0
- package/src/lazy/logger.ts +6 -0
- package/src/lazy/types.ts +75 -0
- package/src/lazy/utils.ts +372 -0
- package/src/lazy/wrapNoSSR.tsx +10 -0
- package/src/plugins/lazy-load-component-plugin.spec.ts +21 -0
- package/src/plugins/lazy-load-component-plugin.ts +57 -0
- package/src/provider/plugin.ts +4 -4
- package/src/remote/component.tsx +3 -3
- package/src/remote/create.tsx +18 -5
- package/tsconfig.json +1 -1
- package/tsconfig.spec.json +26 -0
- package/vite.config.ts +13 -0
- package/vitest.config.ts +6 -1
- package/dist/index-Cv3p6r66.cjs +0 -235
- package/dist/index-D4yt7Udv.js +0 -236
- package/src/.eslintrc.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @module-federation/bridge-react
|
|
2
2
|
|
|
3
|
+
## 0.17.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 65aa038: chore(bridge-react): set sideEffects false
|
|
8
|
+
- a7cf276: chore: upgrade NX to 21.2.3, Storybook to 9.0.9, and TypeScript to 5.8.3
|
|
9
|
+
|
|
10
|
+
- Upgraded NX from 21.0.3 to 21.2.3 with workspace configuration updates
|
|
11
|
+
- Migrated Storybook from 8.3.5 to 9.0.9 with updated configurations and automigrations
|
|
12
|
+
- Upgraded TypeScript from 5.7.3 to 5.8.3 with compatibility fixes
|
|
13
|
+
- Fixed package exports and type declaration paths across all packages
|
|
14
|
+
- Resolved module resolution issues and TypeScript compatibility problems
|
|
15
|
+
- Updated build configurations and dependencies to support latest versions
|
|
16
|
+
|
|
17
|
+
- d31a326: refactor: sink React packages from root to individual packages
|
|
18
|
+
|
|
19
|
+
- Removed React dependencies from root package.json and moved them to packages that actually need them
|
|
20
|
+
- Fixed rsbuild-plugin configuration to match workspace patterns
|
|
21
|
+
- Updated tests to handle platform-specific files
|
|
22
|
+
- This change improves dependency management by ensuring packages only have the dependencies they actually use
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [a7cf276]
|
|
25
|
+
- Updated dependencies [d31a326]
|
|
26
|
+
- @module-federation/sdk@0.17.1
|
|
27
|
+
- @module-federation/bridge-shared@0.17.1
|
|
28
|
+
|
|
29
|
+
## 0.17.0
|
|
30
|
+
|
|
31
|
+
### Minor Changes
|
|
32
|
+
|
|
33
|
+
- e874c64: refactor(bridge-react): rename createRemoteComponent as createRemoteAppComponent
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- e874c64: feat(bridge-react): export createLazyCompoent api
|
|
38
|
+
- 3f736b6: chore: rename FederationHost to ModuleFederation
|
|
39
|
+
- @module-federation/sdk@0.17.0
|
|
40
|
+
- @module-federation/bridge-shared@0.17.0
|
|
41
|
+
|
|
3
42
|
## 0.16.0
|
|
4
43
|
|
|
5
44
|
### Patch Changes
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { createBridgeComponent, createRemoteComponent } from '../src';
|
|
2
|
+
import { createBridgeComponent, createRemoteAppComponent } from '../src';
|
|
4
3
|
import {
|
|
5
4
|
act,
|
|
6
5
|
fireEvent,
|
|
@@ -39,19 +38,20 @@ describe('bridge', () => {
|
|
|
39
38
|
|
|
40
39
|
lifeCycle.destroy({
|
|
41
40
|
dom: containerInfo?.container,
|
|
41
|
+
moduleName: 'test',
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
expect(document.querySelector('#container')!.innerHTML).toContain('');
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
it('
|
|
47
|
+
it('createRemoteAppComponent', async () => {
|
|
48
48
|
function Component({ props }: { props?: Record<string, any> }) {
|
|
49
49
|
return <div>life cycle render {props?.msg}</div>;
|
|
50
50
|
}
|
|
51
51
|
const BridgeComponent = createBridgeComponent({
|
|
52
52
|
rootComponent: Component,
|
|
53
53
|
});
|
|
54
|
-
const RemoteComponent =
|
|
54
|
+
const RemoteComponent = createRemoteAppComponent({
|
|
55
55
|
loader: async () => {
|
|
56
56
|
return {
|
|
57
57
|
default: BridgeComponent,
|
|
@@ -71,7 +71,7 @@ describe('bridge', () => {
|
|
|
71
71
|
expect(getHtml(container)).toMatch('hello world');
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it('
|
|
74
|
+
it('createRemoteAppComponent and obtain ref property', async () => {
|
|
75
75
|
const ref = {
|
|
76
76
|
current: null,
|
|
77
77
|
};
|
|
@@ -82,7 +82,7 @@ describe('bridge', () => {
|
|
|
82
82
|
const BridgeComponent = createBridgeComponent({
|
|
83
83
|
rootComponent: Component,
|
|
84
84
|
});
|
|
85
|
-
const RemoteComponent =
|
|
85
|
+
const RemoteComponent = createRemoteAppComponent({
|
|
86
86
|
loader: async () => {
|
|
87
87
|
return {
|
|
88
88
|
default: BridgeComponent,
|
|
@@ -103,8 +103,8 @@ describe('bridge', () => {
|
|
|
103
103
|
expect(ref.current).not.toBeNull();
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
it('
|
|
107
|
-
const renderMock =
|
|
106
|
+
it('createRemoteAppComponent with custom createRoot prop', async () => {
|
|
107
|
+
const renderMock = jest.fn();
|
|
108
108
|
|
|
109
109
|
function Component({ props }: { props?: Record<string, any> }) {
|
|
110
110
|
return <div>life cycle render {props?.msg}</div>;
|
|
@@ -114,11 +114,11 @@ describe('bridge', () => {
|
|
|
114
114
|
createRoot: () => {
|
|
115
115
|
return {
|
|
116
116
|
render: renderMock,
|
|
117
|
-
unmount:
|
|
117
|
+
unmount: jest.fn(),
|
|
118
118
|
};
|
|
119
119
|
},
|
|
120
120
|
});
|
|
121
|
-
const RemoteComponent =
|
|
121
|
+
const RemoteComponent = createRemoteAppComponent({
|
|
122
122
|
loader: async () => {
|
|
123
123
|
return {
|
|
124
124
|
default: BridgeComponent,
|
|
@@ -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
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// Test file for router
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import {
|
|
@@ -15,7 +15,7 @@ import { getHtml, getWindowImpl } from './util';
|
|
|
15
15
|
describe('react router proxy', () => {
|
|
16
16
|
it('BrowserRouter not wraper context', async () => {
|
|
17
17
|
let { container } = render(
|
|
18
|
-
<RouterContext.Provider value={{
|
|
18
|
+
<RouterContext.Provider value={{ basename: '/test' } as any}>
|
|
19
19
|
<BrowserRouter basename="/" window={getWindowImpl('/test', false)}>
|
|
20
20
|
<ul>
|
|
21
21
|
<li>
|
|
@@ -73,7 +73,7 @@ describe('react router proxy', () => {
|
|
|
73
73
|
},
|
|
74
74
|
);
|
|
75
75
|
let { container } = render(
|
|
76
|
-
<RouterContext.Provider value={{
|
|
76
|
+
<RouterContext.Provider value={{ basename: '/test' } as any}>
|
|
77
77
|
<RouterProvider router={router} />
|
|
78
78
|
</RouterContext.Provider>,
|
|
79
79
|
);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// In vitest, you can use the setupFiles option in your configuration file to import any necessary setup files for your tests.
|
|
2
|
+
// For example, if you want to use testing-library's custom matchers, you can import them in a setup file like this:
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
|
|
5
|
+
// Fix TextEncoder/TextDecoder not defined in Node.js
|
|
6
|
+
import { TextEncoder, TextDecoder } from 'util';
|
|
7
|
+
global.TextEncoder = TextEncoder;
|
|
8
|
+
global.TextDecoder = TextDecoder as any;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Component, createElement, createContext } from "react";
|
|
3
|
-
import { L as LoggerInstance, R as RouterContext } from "./index-
|
|
3
|
+
import { L as LoggerInstance, R as RouterContext } from "./index-CqxytsLY.mjs";
|
|
4
4
|
import { federationRuntime } from "./plugin.es.js";
|
|
5
5
|
const ErrorBoundaryContext = createContext(null);
|
|
6
6
|
const initialState = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const React = require("react");
|
|
3
|
-
const index = require("./index-
|
|
3
|
+
const index = require("./index-C0fDZB5b.js");
|
|
4
4
|
const plugin = require("./plugin.cjs.js");
|
|
5
5
|
function _interopNamespaceDefault(e) {
|
|
6
6
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const lazyUtils = require("./utils-iEVlDmyk.js");
|
|
3
|
+
const index_esm = require("./index.esm-j_1sIRzg.js");
|
|
4
|
+
function wrapSetTimeout(targetPromise, delay = 2e4, id) {
|
|
5
|
+
if (targetPromise && typeof targetPromise.then === "function") {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const timeoutId = setTimeout(() => {
|
|
8
|
+
lazyUtils.logger.warn(`Data fetch for ID ${id} timed out after 20 seconds.`);
|
|
9
|
+
reject(new Error(`Data fetch for ID ${id} timed out after 20 seconds`));
|
|
10
|
+
}, delay);
|
|
11
|
+
targetPromise.then((value) => {
|
|
12
|
+
clearTimeout(timeoutId);
|
|
13
|
+
resolve(value);
|
|
14
|
+
}).catch((err) => {
|
|
15
|
+
clearTimeout(timeoutId);
|
|
16
|
+
reject(err);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function addProtocol(url) {
|
|
22
|
+
if (url.startsWith("//")) {
|
|
23
|
+
return "https:" + url;
|
|
24
|
+
}
|
|
25
|
+
return url;
|
|
26
|
+
}
|
|
27
|
+
const getDecodeQuery = (url, name) => {
|
|
28
|
+
const res = url.searchParams.get(name);
|
|
29
|
+
if (!res) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return decodeURIComponent(res);
|
|
33
|
+
};
|
|
34
|
+
const dataFetchServerMiddleware = async (ctx, next) => {
|
|
35
|
+
var _a, _b, _c;
|
|
36
|
+
let url;
|
|
37
|
+
let dataFetchKey;
|
|
38
|
+
let params;
|
|
39
|
+
let remoteInfo;
|
|
40
|
+
try {
|
|
41
|
+
url = new URL(ctx.req.url);
|
|
42
|
+
dataFetchKey = getDecodeQuery(url, lazyUtils.DATA_FETCH_QUERY);
|
|
43
|
+
params = JSON.parse(getDecodeQuery(url, "params") || "{}");
|
|
44
|
+
const remoteInfoQuery = getDecodeQuery(url, "remoteInfo");
|
|
45
|
+
remoteInfo = remoteInfoQuery ? JSON.parse(remoteInfoQuery) : null;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
lazyUtils.logger.error("fetch data from server, error: ", e);
|
|
48
|
+
return next();
|
|
49
|
+
}
|
|
50
|
+
if (!dataFetchKey) {
|
|
51
|
+
return next();
|
|
52
|
+
}
|
|
53
|
+
lazyUtils.logger.log("fetch data from server, dataFetchKey: ", dataFetchKey);
|
|
54
|
+
lazyUtils.logger.debug(
|
|
55
|
+
"fetch data from server, moduleInfo: ",
|
|
56
|
+
(_a = globalThis.__FEDERATION__) == null ? void 0 : _a.moduleInfo
|
|
57
|
+
);
|
|
58
|
+
try {
|
|
59
|
+
const dataFetchMap = lazyUtils.getDataFetchMap();
|
|
60
|
+
if (!dataFetchMap) {
|
|
61
|
+
lazyUtils.initDataFetchMap();
|
|
62
|
+
}
|
|
63
|
+
const fetchDataPromise = (_b = dataFetchMap[dataFetchKey]) == null ? void 0 : _b[1];
|
|
64
|
+
lazyUtils.logger.debug(
|
|
65
|
+
"fetch data from server, fetchDataPromise: ",
|
|
66
|
+
fetchDataPromise
|
|
67
|
+
);
|
|
68
|
+
if (fetchDataPromise && ((_c = dataFetchMap[dataFetchKey]) == null ? void 0 : _c[2]) !== lazyUtils.MF_DATA_FETCH_STATUS.ERROR) {
|
|
69
|
+
const targetPromise = fetchDataPromise[0];
|
|
70
|
+
const wrappedPromise = wrapSetTimeout(targetPromise, 2e4, dataFetchKey);
|
|
71
|
+
if (wrappedPromise) {
|
|
72
|
+
const res = await wrappedPromise;
|
|
73
|
+
lazyUtils.logger.log("fetch data from server, fetchDataPromise res: ", res);
|
|
74
|
+
return ctx.json(res);
|
|
75
|
+
}
|
|
76
|
+
lazyUtils.logger.error(
|
|
77
|
+
`Expected a Promise from fetchDataPromise[0] for dataFetchKey ${dataFetchKey}, but received:`,
|
|
78
|
+
targetPromise,
|
|
79
|
+
"Will try call new dataFetch again..."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (remoteInfo) {
|
|
83
|
+
try {
|
|
84
|
+
const hostInstance2 = globalThis.__FEDERATION__.__INSTANCES__[0];
|
|
85
|
+
const remoteEntry = `${addProtocol(remoteInfo.ssrPublicPath) + remoteInfo.ssrRemoteEntry}`;
|
|
86
|
+
if (!hostInstance2) {
|
|
87
|
+
throw new Error("host instance not found!");
|
|
88
|
+
}
|
|
89
|
+
const remote = hostInstance2.options.remotes.find(
|
|
90
|
+
(remote2) => remote2.name === remoteInfo.name
|
|
91
|
+
);
|
|
92
|
+
lazyUtils.logger.debug("find remote: ", JSON.stringify(remote));
|
|
93
|
+
if (!remote) {
|
|
94
|
+
hostInstance2.registerRemotes([
|
|
95
|
+
{
|
|
96
|
+
name: remoteInfo.name,
|
|
97
|
+
entry: remoteEntry,
|
|
98
|
+
entryGlobalName: remoteInfo.globalName
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
} else if (!("entry" in remote) || !remote.entry.includes(index_esm.MANIFEST_EXT)) {
|
|
102
|
+
const { hostGlobalSnapshot, remoteSnapshot } = hostInstance2.snapshotHandler.getGlobalRemoteInfo(remoteInfo);
|
|
103
|
+
lazyUtils.logger.debug(
|
|
104
|
+
"find hostGlobalSnapshot: ",
|
|
105
|
+
JSON.stringify(hostGlobalSnapshot)
|
|
106
|
+
);
|
|
107
|
+
lazyUtils.logger.debug("find remoteSnapshot: ", JSON.stringify(remoteSnapshot));
|
|
108
|
+
if (!hostGlobalSnapshot || !remoteSnapshot) {
|
|
109
|
+
if ("version" in remote) {
|
|
110
|
+
delete remote.version;
|
|
111
|
+
}
|
|
112
|
+
remote.entry = remoteEntry;
|
|
113
|
+
remote.entryGlobalName = remoteInfo.globalName;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {
|
|
117
|
+
ctx.status(500);
|
|
118
|
+
return ctx.text(
|
|
119
|
+
`failed to fetch ${remoteInfo.name} data, error:
|
|
120
|
+
${e}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const dataFetchItem = dataFetchMap[dataFetchKey];
|
|
125
|
+
lazyUtils.logger.debug("fetch data from server, dataFetchItem: ", dataFetchItem);
|
|
126
|
+
if (dataFetchItem) {
|
|
127
|
+
const callFetchDataPromise = lazyUtils.fetchData(dataFetchKey, {
|
|
128
|
+
...params,
|
|
129
|
+
isDowngrade: !remoteInfo,
|
|
130
|
+
_id: dataFetchKey
|
|
131
|
+
});
|
|
132
|
+
const wrappedPromise = wrapSetTimeout(
|
|
133
|
+
callFetchDataPromise,
|
|
134
|
+
2e4,
|
|
135
|
+
dataFetchKey
|
|
136
|
+
);
|
|
137
|
+
if (wrappedPromise) {
|
|
138
|
+
const res = await wrappedPromise;
|
|
139
|
+
lazyUtils.logger.log("fetch data from server, dataFetchItem res: ", res);
|
|
140
|
+
return ctx.json(res);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const remoteId = dataFetchKey.split(index_esm.SEPARATOR)[0];
|
|
144
|
+
const hostInstance = globalThis.__FEDERATION__.__INSTANCES__[0];
|
|
145
|
+
if (!hostInstance) {
|
|
146
|
+
throw new Error("host instance not found!");
|
|
147
|
+
}
|
|
148
|
+
const dataFetchFn = await lazyUtils.loadDataFetchModule(hostInstance, remoteId);
|
|
149
|
+
const data = await dataFetchFn({
|
|
150
|
+
...params,
|
|
151
|
+
isDowngrade: !remoteInfo,
|
|
152
|
+
_id: dataFetchKey
|
|
153
|
+
});
|
|
154
|
+
lazyUtils.logger.log("fetch data from server, loadDataFetchModule res: ", data);
|
|
155
|
+
return ctx.json(data);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
lazyUtils.logger.error("server plugin data fetch error: ", e);
|
|
158
|
+
ctx.status(500);
|
|
159
|
+
return ctx.text(`failed to fetch ${remoteInfo.name} data, error:
|
|
160
|
+
${e}`);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
module.exports = dataFetchServerMiddleware;
|