Skip to content

Commit 0b34514

Browse files
authored
fix(retry-plugin): implement error handling in getRemoteEntry to avoid throw RUNTIME_008 error on console (#4064)
1 parent 3fd26a7 commit 0b34514

File tree

9 files changed

+76
-48
lines changed

9 files changed

+76
-48
lines changed

apps/router-demo/router-host-2000/src/runtime-plugin/retry.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { RetryPlugin } from '@module-federation/retry-plugin';
22

33
const retryPlugin = () =>
44
RetryPlugin({
5-
retryTimes: 3,
5+
retryTimes: 4,
66
retryDelay: 1000,
77
onRetry: (params) => {
88
console.log('onRetry', params);
@@ -13,19 +13,19 @@ const retryPlugin = () =>
1313
onError: (params) => {
1414
console.log('onError', params);
1515
},
16-
manifestDomains: [
17-
'https://m1.example.com',
18-
'https://m2.example.com',
19-
'https://m3.example.com',
20-
],
21-
domains: [
22-
'http://localhost:2011',
23-
'http://localhost:2021',
24-
'http://localhost:2031',
25-
],
26-
addQuery: ({ times, originalQuery }) => {
27-
return `${originalQuery}&retry=${times}&retryTimeStamp=${new Date().valueOf()}`;
28-
},
16+
// manifestDomains: [
17+
// 'https://m1.example.com',
18+
// 'https://m2.example.com',
19+
// 'https://m3.example.com',
20+
// ],
21+
// domains: [
22+
// 'http://localhost:2011',
23+
// 'http://localhost:2021',
24+
// 'http://localhost:2031',
25+
// ],
26+
// addQuery: ({ times, originalQuery }) => {
27+
// return `${originalQuery}&retry=${times}&retryTimeStamp=${new Date().valueOf()}`;
28+
// },
2929
fetchOptions: {
3030
method: 'GET',
3131
},

packages/retry-plugin/__tests__/retry.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, it, expect, beforeEach, vi } from 'vitest';
22
import { fetchRetry } from '../src/fetch-retry';
33
import { scriptRetry } from '../src/script-retry';
4-
import { ERROR_ABANDONED } from '../src/constant';
4+
import { ERROR_ABANDONED, RUNTIME_008 } from '../src/constant';
55

66
const mockFetch = vi.fn();
77
global.fetch = mockFetch;
@@ -53,7 +53,7 @@ describe('Retry Plugin', () => {
5353
retryTimes: 2,
5454
retryDelay: 10,
5555
}),
56-
).rejects.toThrow(ERROR_ABANDONED);
56+
).rejects.toThrow(RUNTIME_008);
5757

5858
expect(mockFetch).toHaveBeenCalledTimes(3); // 1 initial + 2 retries
5959
});

packages/retry-plugin/src/constant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export const defaultRetries = 3;
22
export const defaultRetryDelay = 1000;
33
export const PLUGIN_IDENTIFIER = '[ Module Federation RetryPlugin ]';
44
export const ERROR_ABANDONED = 'The request failed and has now been abandoned';
5+
export const RUNTIME_008 = 'RUNTIME-008';

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
defaultRetryDelay,
55
PLUGIN_IDENTIFIER,
66
ERROR_ABANDONED,
7+
RUNTIME_008,
78
} from './constant';
89
import logger from './logger';
910
import { getRetryUrl, combineUrlDomainWithPathQuery } from './utils';
@@ -83,11 +84,13 @@ async function fetchRetry(
8384
`${PLUGIN_IDENTIFIER}: retry failed, no retries left for url: ${requestUrl}`,
8485
);
8586
}
86-
throw new Error(`${PLUGIN_IDENTIFIER}: ${ERROR_ABANDONED}`);
87+
// Throw error with RUNTIME_008 to match loadEntryScript behavior
88+
throw new Error(
89+
`${RUNTIME_008}: ${PLUGIN_IDENTIFIER}: ${ERROR_ABANDONED} | url: ${requestUrl}`,
90+
);
8791
} else {
8892
// Prepare next retry using the same domain extraction logic
89-
const nextIndex = total - retryTimes + 1; // upcoming retry count
90-
93+
const nextIndex = total - retryTimes + 1;
9194
// For prediction, use current request URL's domain but original URL's path/query
9295
const predictedBaseUrl = combineUrlDomainWithPathQuery(requestUrl, url);
9396

packages/retry-plugin/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const RetryPlugin = (
1515
// @ts-ignore
1616
if (params?.fetch || params?.script) {
1717
logger.warn(
18-
`${PLUGIN_IDENTIFIER}: fetch or script config is deprecated, please use the new config style. See docs: https://module-federation.io/plugin/plugins/retry-plugin.html`,
18+
`${PLUGIN_IDENTIFIER}: params is ${params}, fetch or script config is deprecated, please use the new config style. See docs: https://module-federation.io/plugin/plugins/retry-plugin.html`,
1919
);
2020
}
2121
const {

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ export function scriptRetry<T extends Record<string, any>>({
2929
} = retryOptions || {};
3030

3131
let attempts = 0; // number of attempts already performed
32-
while (attempts < retryTimes) {
32+
const maxAttempts = retryTimes; // maximum number of attempts allowed
33+
34+
while (attempts < maxAttempts) {
3335
try {
3436
beforeExecuteRetry();
35-
if (retryDelay > 0) {
37+
if (retryDelay > 0 && attempts > 0) {
3638
await new Promise((resolve) => setTimeout(resolve, retryDelay));
3739
}
3840
const retryIndex = attempts + 1;
@@ -79,11 +81,14 @@ export function scriptRetry<T extends Record<string, any>>({
7981
} catch (error) {
8082
lastError = error;
8183
attempts++;
82-
if (attempts >= retryTimes) {
84+
if (attempts >= maxAttempts) {
8385
onError &&
8486
lastRequestUrl &&
8587
onError({ domains, url: lastRequestUrl, tagName: 'script' });
86-
throw new Error(`${PLUGIN_IDENTIFIER}: ${ERROR_ABANDONED}`);
88+
89+
throw new Error(
90+
`${PLUGIN_IDENTIFIER}: ${ERROR_ABANDONED} | url: ${lastRequestUrl || 'unknown'}`,
91+
);
8792
}
8893
}
8994
}

packages/runtime-core/src/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export class ModuleFederation {
136136
uniqueKey: string;
137137
},
138138
],
139-
Promise<(() => Promise<RemoteEntryExports | undefined>) | undefined>
139+
Promise<Promise<RemoteEntryExports | undefined> | undefined>
140140
>(),
141141
getModuleFactory: new AsyncHook<
142142
[

packages/runtime-core/src/module/index.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,12 @@ class Module {
3737
}
3838

3939
let remoteEntryExports;
40-
try {
41-
remoteEntryExports = await getRemoteEntry({
42-
origin: this.host,
43-
remoteInfo: this.remoteInfo,
44-
remoteEntryExports: this.remoteEntryExports,
45-
});
46-
} catch (err) {
47-
const uniqueKey = getRemoteEntryUniqueKey(this.remoteInfo);
48-
const isScriptLoadError =
49-
err instanceof Error && err.message.includes(RUNTIME_008);
50-
if (isScriptLoadError) {
51-
remoteEntryExports =
52-
await this.host.loaderHook.lifecycle.loadEntryError.emit({
53-
getRemoteEntry,
54-
origin: this.host,
55-
remoteInfo: this.remoteInfo,
56-
remoteEntryExports: this.remoteEntryExports,
57-
globalLoading,
58-
uniqueKey,
59-
});
60-
}
61-
}
40+
41+
remoteEntryExports = await getRemoteEntry({
42+
origin: this.host,
43+
remoteInfo: this.remoteInfo,
44+
remoteEntryExports: this.remoteEntryExports,
45+
});
6246

6347
assert(
6448
remoteEntryExports,

packages/runtime-core/src/utils/load.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,15 @@ export async function getRemoteEntry(params: {
237237
remoteInfo: RemoteInfo;
238238
remoteEntryExports?: RemoteEntryExports | undefined;
239239
getEntryUrl?: (url: string) => string;
240+
_inErrorHandling?: boolean; // Add flag to prevent recursion
240241
}): Promise<RemoteEntryExports | false | void> {
241-
const { origin, remoteEntryExports, remoteInfo, getEntryUrl } = params;
242+
const {
243+
origin,
244+
remoteEntryExports,
245+
remoteInfo,
246+
getEntryUrl,
247+
_inErrorHandling = false,
248+
} = params;
242249
const uniqueKey = getRemoteEntryUniqueKey(remoteInfo);
243250
if (remoteEntryExports) {
244251
return remoteEntryExports;
@@ -272,6 +279,34 @@ export async function getRemoteEntry(params: {
272279
getEntryUrl,
273280
})
274281
: loadEntryNode({ remoteInfo, loaderHook });
282+
})
283+
.catch(async (err) => {
284+
const uniqueKey = getRemoteEntryUniqueKey(remoteInfo);
285+
const isScriptLoadError =
286+
err instanceof Error && err.message.includes(RUNTIME_008);
287+
288+
if (isScriptLoadError && !_inErrorHandling) {
289+
const wrappedGetRemoteEntry = (
290+
params: Parameters<typeof getRemoteEntry>[0],
291+
) => {
292+
return getRemoteEntry({ ...params, _inErrorHandling: true });
293+
};
294+
295+
const RemoteEntryExports =
296+
await origin.loaderHook.lifecycle.loadEntryError.emit({
297+
getRemoteEntry: wrappedGetRemoteEntry,
298+
origin,
299+
remoteInfo: remoteInfo,
300+
remoteEntryExports,
301+
globalLoading,
302+
uniqueKey,
303+
});
304+
305+
if (RemoteEntryExports) {
306+
return RemoteEntryExports;
307+
}
308+
}
309+
throw err;
275310
});
276311
}
277312

0 commit comments

Comments
 (0)