Skip to content

Commit fa7a0bd

Browse files
authored
feat: add remote-entry script resource retry for retry-plugin (#3321)
1 parent 85ef6c4 commit fa7a0bd

File tree

9 files changed

+153
-60
lines changed

9 files changed

+153
-60
lines changed

.changeset/small-eyes-change.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@module-federation/retry-plugin': patch
3+
'@module-federation/runtime': patch
4+
---
5+
6+
feat: add remote-entry script resource retry for retry-plugin

apps/router-demo/router-host-2000/rsbuild.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default defineConfig({
3131
shared: ['react', 'react-dom', 'antd'],
3232
runtimePlugins: [
3333
path.join(__dirname, './src/runtime-plugin/shared-strategy.ts'),
34-
// path.join(__dirname, './src/runtime-plugin/retry.ts'),
34+
path.join(__dirname, './src/runtime-plugin/retry.ts'),
3535
],
3636
// bridge: {
3737
// disableAlias: true,

apps/router-demo/router-host-2000/src/App.tsx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,22 @@ init({
1414
remotes: [],
1515
plugins: [
1616
BridgeReactPlugin(),
17-
RetryPlugin({
18-
fetch: {
19-
url: 'http://localhost:2008/not-exist-mf-manifest.json',
20-
fallback: () => 'http://localhost:2001/mf-manifest.json',
21-
},
22-
script: {
23-
retryTimes: 3,
24-
retryDelay: 1000,
25-
moduleName: ['remote1'],
26-
cb: (resolve, error) => {
27-
return setTimeout(() => {
28-
resolve(error);
29-
}, 1000);
30-
},
31-
},
32-
}),
17+
// RetryPlugin({
18+
// fetch: {
19+
// url: 'http://localhost:2008/not-exist-mf-manifest.json',
20+
// fallback: () => 'http://localhost:2001/mf-manifest.json',
21+
// },
22+
// script: {
23+
// retryTimes: 3,
24+
// retryDelay: 1000,
25+
// moduleName: ['remote1'],
26+
// cb: (resolve, error) => {
27+
// return setTimeout(() => {
28+
// resolve(error);
29+
// }, 1000);
30+
// },
31+
// },
32+
// }),
3333
],
3434
});
3535

packages/retry-plugin/src/fetch-retry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ async function fetchWithRetry({
3333
} catch (error) {
3434
if (retryTimes <= 0) {
3535
logger.log(
36-
`>>>>>>>>> retry failed after ${retryTimes} times for url: ${url}, now will try fallbackUrl url <<<<<<<<<`,
36+
`[ Module Federation RetryPlugin ]: retry failed after ${retryTimes} times for url: ${url}, now will try fallbackUrl url`,
3737
);
38+
3839
if (fallback && typeof fallback === 'function') {
3940
return fetchWithRetry({
4041
url: fallback(url),

packages/retry-plugin/src/index.ts

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FederationRuntimePlugin } from '@module-federation/runtime/types';
22
import { fetchWithRetry } from './fetch-retry';
3-
import { defaultRetries, defaultRetryDelay } from './constant';
43
import type { RetryPluginParams } from './types';
4+
import { scriptCommonRetry } from './util';
55

66
const RetryPlugin: (params: RetryPluginParams) => FederationRuntimePlugin = ({
77
fetch: fetchOption,
@@ -36,39 +36,39 @@ const RetryPlugin: (params: RetryPluginParams) => FederationRuntimePlugin = ({
3636
}
3737
return fetch(url, options);
3838
},
39-
async getModuleFactory({ remoteEntryExports, expose, moduleInfo }) {
40-
let moduleFactory;
41-
const { retryTimes = defaultRetries, retryDelay = defaultRetryDelay } =
42-
scriptOption || {};
43-
44-
if (
45-
(scriptOption?.moduleName &&
46-
scriptOption?.moduleName.some(
47-
(m) => moduleInfo.name === m || (moduleInfo as any)?.alias === m,
48-
)) ||
49-
scriptOption?.moduleName === undefined
50-
) {
51-
let attempts = 0;
5239

53-
while (attempts - 1 < retryTimes) {
54-
try {
55-
moduleFactory = await remoteEntryExports.get(expose);
56-
break;
57-
} catch (error) {
58-
attempts++;
59-
if (attempts - 1 >= retryTimes) {
60-
scriptOption?.cb &&
61-
(await new Promise(
62-
(resolve) =>
63-
scriptOption?.cb && scriptOption?.cb(resolve, error),
64-
));
65-
throw error;
66-
}
67-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
68-
}
69-
}
70-
}
71-
return moduleFactory;
40+
async loadEntryError({
41+
getRemoteEntry,
42+
origin,
43+
remoteInfo,
44+
remoteEntryExports,
45+
globalLoading,
46+
uniqueKey,
47+
}) {
48+
if (!scriptOption) return;
49+
const retryFn = getRemoteEntry;
50+
const beforeExecuteRetry = () => delete globalLoading[uniqueKey];
51+
const getRemoteEntryRetry = scriptCommonRetry({
52+
scriptOption,
53+
moduleInfo: remoteInfo,
54+
retryFn,
55+
beforeExecuteRetry,
56+
});
57+
return getRemoteEntryRetry({
58+
origin,
59+
remoteInfo,
60+
remoteEntryExports,
61+
});
62+
},
63+
async getModuleFactory({ remoteEntryExports, expose, moduleInfo }) {
64+
if (!scriptOption) return;
65+
const retryFn = remoteEntryExports.get;
66+
const getRemoteEntryRetry = scriptCommonRetry({
67+
scriptOption,
68+
moduleInfo,
69+
retryFn,
70+
});
71+
return getRemoteEntryRetry(expose);
7272
},
7373
});
7474

packages/retry-plugin/src/types.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RemoteInfo } from '@module-federation/runtime/types';
12
export interface FetchWithRetryOptions {
23
url?: string;
34
options?: RequestInit;
@@ -24,3 +25,10 @@ export type RequiredFetchWithRetryOptions = Required<
2425
Pick<FetchWithRetryOptions, 'url'>
2526
> &
2627
Omit<FetchWithRetryOptions, 'url'>;
28+
29+
export type ScriptCommonRetryOption = {
30+
scriptOption: ScriptWithRetryOptions;
31+
moduleInfo: RemoteInfo & { alias?: string };
32+
retryFn: (...args: any[]) => Promise<any> | (() => Promise<any>);
33+
beforeExecuteRetry?: (...args: any[]) => void;
34+
};

packages/retry-plugin/src/util.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { defaultRetries, defaultRetryDelay } from './constant';
2+
import type { ScriptCommonRetryOption } from './types';
3+
import logger from './logger';
4+
5+
export function scriptCommonRetry<T extends (...args: any[]) => void>({
6+
scriptOption,
7+
moduleInfo,
8+
retryFn,
9+
beforeExecuteRetry = () => {},
10+
}: ScriptCommonRetryOption) {
11+
return async function (...args: Parameters<T>) {
12+
let retryResponse;
13+
const { retryTimes = defaultRetries, retryDelay = defaultRetryDelay } =
14+
scriptOption || {};
15+
if (
16+
(scriptOption?.moduleName &&
17+
scriptOption?.moduleName.some(
18+
(m) => moduleInfo.name === m || moduleInfo?.alias === m,
19+
)) ||
20+
scriptOption?.moduleName === undefined
21+
) {
22+
let attempts = 0;
23+
while (attempts - 1 < retryTimes) {
24+
try {
25+
beforeExecuteRetry && beforeExecuteRetry();
26+
retryResponse = await retryFn(...args);
27+
break;
28+
} catch (error) {
29+
attempts++;
30+
if (attempts - 1 >= retryTimes) {
31+
scriptOption?.cb &&
32+
(await new Promise(
33+
(resolve) =>
34+
scriptOption?.cb && scriptOption?.cb(resolve, error),
35+
));
36+
throw error;
37+
}
38+
logger.log(
39+
`[ Module Federation RetryPlugin ]: script resource retrying ${attempts} times`,
40+
);
41+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
42+
}
43+
}
44+
}
45+
return retryResponse;
46+
};
47+
}

packages/runtime/src/core.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
InitTokens,
1919
CallFrom,
2020
} from './type';
21-
import { getBuilderId, registerPlugins } from './utils';
21+
import { getBuilderId, registerPlugins, getRemoteEntry } from './utils';
2222
import { Module } from './module';
2323
import {
2424
AsyncHook,
@@ -114,6 +114,22 @@ export class FederationHost {
114114
[string, RequestInit],
115115
Promise<Response> | void | false
116116
>(),
117+
loadEntryError: new AsyncHook<
118+
[
119+
{
120+
getRemoteEntry: typeof getRemoteEntry;
121+
origin: FederationHost;
122+
remoteInfo: RemoteInfo;
123+
remoteEntryExports?: RemoteEntryExports | undefined;
124+
globalLoading: Record<
125+
string,
126+
Promise<void | RemoteEntryExports> | undefined
127+
>;
128+
uniqueKey: string;
129+
},
130+
],
131+
Promise<(() => Promise<RemoteEntryExports | undefined>) | undefined>
132+
>(),
117133
getModuleFactory: new AsyncHook<
118134
[
119135
{

packages/runtime/src/module/index.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import {
55
RUNTIME_002,
66
runtimeDescMap,
77
} from '@module-federation/error-codes';
8-
import { getRemoteEntry } from '../utils/load';
8+
import { getRemoteEntry, getRemoteEntryUniqueKey } from '../utils/load';
99
import { FederationHost } from '../core';
1010
import { RemoteEntryExports, RemoteInfo, InitScope } from '../type';
11+
import { globalLoading } from '../global';
1112

1213
export type ModuleOptions = ConstructorParameters<typeof Module>[0];
1314

@@ -34,18 +35,32 @@ class Module {
3435
return this.remoteEntryExports;
3536
}
3637

37-
// Get remoteEntry.js
38-
const remoteEntryExports = await getRemoteEntry({
39-
origin: this.host,
40-
remoteInfo: this.remoteInfo,
41-
remoteEntryExports: this.remoteEntryExports,
42-
});
38+
let remoteEntryExports;
39+
try {
40+
remoteEntryExports = await getRemoteEntry({
41+
origin: this.host,
42+
remoteInfo: this.remoteInfo,
43+
remoteEntryExports: this.remoteEntryExports,
44+
});
45+
} catch (err) {
46+
const uniqueKey = getRemoteEntryUniqueKey(this.remoteInfo);
47+
remoteEntryExports =
48+
await this.host.loaderHook.lifecycle.loadEntryError.emit({
49+
getRemoteEntry,
50+
origin: this.host,
51+
remoteInfo: this.remoteInfo,
52+
remoteEntryExports: this.remoteEntryExports,
53+
globalLoading,
54+
uniqueKey,
55+
});
56+
}
57+
4358
assert(
4459
remoteEntryExports,
4560
`remoteEntryExports is undefined \n ${safeToString(this.remoteInfo)}`,
4661
);
4762

48-
this.remoteEntryExports = remoteEntryExports;
63+
this.remoteEntryExports = remoteEntryExports as RemoteEntryExports;
4964
return this.remoteEntryExports;
5065
}
5166

0 commit comments

Comments
 (0)