Skip to content

Commit 762c3b4

Browse files
authored
fix(plugin-assets-retry): support output.assetPrefix with absolute url (#4129)
1 parent 8e9d9be commit 762c3b4

File tree

3 files changed

+58
-50
lines changed

3 files changed

+58
-50
lines changed

e2e/cases/assets/assets-retry/index.test.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ test('react ErrorBoundary should catch error when all retries failed', async ({
244244
await gotoPage(page, rsbuild);
245245
const compTestElement = page.locator('#async-comp-test-error');
246246
await expect(compTestElement).toHaveText(
247-
/ChunkLoadError: Loading chunk src_AsyncCompTest_tsx from \/static\/js\/async\/src_AsyncCompTest_tsx\.js failed after 3 retries: "Loading chunk src_AsyncCompTest_tsx failed.*/,
247+
/ChunkLoadError: Loading chunk src_AsyncCompTest_tsx from "static\/js\/async\/src_AsyncCompTest_tsx\.js" failed after 3 retries: "Loading chunk src_AsyncCompTest_tsx failed.*/,
248248
);
249249
const blockedResponseCount = count404Response(
250250
logs,
@@ -448,7 +448,7 @@ test('onRetry and onSuccess options should work when retrying async chunk succes
448448
await rsbuild.close();
449449
});
450450

451-
test('onRetry and onFail options should work when retrying initial chunk failed', async ({
451+
test('domain, onRetry and onFail options should work when retrying initial chunk failed', async ({
452452
page,
453453
}) => {
454454
const blockedMiddleware = createBlockMiddleware({
@@ -461,7 +461,7 @@ test('onRetry and onFail options should work when retrying initial chunk failed'
461461
blockedMiddleware,
462462
{
463463
minify: true,
464-
domain: [`http://localhost:${port}`, 'http://a.com', 'http://b.com'],
464+
domain: [`http://localhost:${port}`, 'http://a.com/foo-path', 'http://b.com'],
465465
onRetry(context) {
466466
console.info('onRetry', context);
467467
},
@@ -497,8 +497,8 @@ test('onRetry and onFail options should work when retrying initial chunk failed'
497497
},
498498
{
499499
times: 1,
500-
domain: 'http://a.com',
501-
url: 'http://a.com/static/js/index.js',
500+
domain: 'http://a.com/foo-path',
501+
url: 'http://a.com/foo-path/static/js/index.js',
502502
tagName: 'script',
503503
isAsyncChunk: false,
504504
},
@@ -524,7 +524,7 @@ test('onRetry and onFail options should work when retrying initial chunk failed'
524524
await rsbuild.close();
525525
});
526526

527-
test('onRetry and onFail options should work when retrying async chunk failed', async ({
527+
test('domain, onRetry and onFail options should work when retrying async chunk failed', async ({
528528
page,
529529
}) => {
530530
const blockedMiddleware = createBlockMiddleware({
@@ -537,7 +537,7 @@ test('onRetry and onFail options should work when retrying async chunk failed',
537537
blockedMiddleware,
538538
{
539539
minify: true,
540-
domain: [`http://localhost:${port}`, 'http://a.com', 'http://b.com'],
540+
domain: [`http://localhost:${port}`, 'http://a.com/foo-path', 'http://b.com'],
541541
onRetry(context) {
542542
console.info('onRetry', context);
543543
},
@@ -558,7 +558,7 @@ test('onRetry and onFail options should work when retrying async chunk failed',
558558
await gotoPage(page, rsbuild);
559559
const compTestElement = page.locator('#async-comp-test-error');
560560
await expect(compTestElement).toHaveText(
561-
/ChunkLoadError: Loading chunk src_AsyncCompTest_tsx from \/static\/js\/async\/src_AsyncCompTest_tsx\.js failed after 3 retries: "Loading chunk src_AsyncCompTest_tsx failed.*/,
561+
/ChunkLoadError: Loading chunk src_AsyncCompTest_tsx from "static\/js\/async\/src_AsyncCompTest_tsx\.js" failed after 3 retries: "Loading chunk src_AsyncCompTest_tsx failed.*/,
562562
);
563563
await delay();
564564

@@ -577,8 +577,8 @@ test('onRetry and onFail options should work when retrying async chunk failed',
577577
},
578578
{
579579
times: 1,
580-
domain: 'http://a.com',
581-
url: 'http://a.com/static/js/async/src_AsyncCompTest_tsx.js',
580+
domain: 'http://a.com/foo-path',
581+
url: 'http://a.com/foo-path/static/js/async/src_AsyncCompTest_tsx.js',
582582
tagName: 'script',
583583
isAsyncChunk: true,
584584
},

packages/plugin-assets-retry/src/runtime/asyncChunkRetry.ts

+30-37
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type Retry = {
1010
nextRetryUrl: ChunkSrcUrl;
1111
originalScriptFilename: ChunkFilename;
1212
originalSrcUrl: ChunkSrcUrl;
13+
originalQuery: string;
1314
};
1415

1516
type RetryCollector = Record<ChunkId, Record<number, Retry>>;
@@ -99,37 +100,16 @@ function getUrlRetryQuery(
99100
return '';
100101
}
101102

102-
function removeDomainFromUrl(url: string): string {
103-
const protocolStartIndex = url.indexOf('//');
104-
105-
// case /app/main/static/js/index.js
106-
if (protocolStartIndex === -1 && url.startsWith('/')) {
107-
return url;
108-
}
109-
110-
// case "//cdn.com/app/main/static/js/index.js"
111-
// case "http://cdn.com/app/main/static/js/index.js"
112-
const protocolEndIndex = protocolStartIndex + 2;
113-
const pathStartIndex = url.indexOf('/', protocolEndIndex);
114-
115-
return url.slice(pathStartIndex);
116-
}
117-
118-
// "http://cdn.com/app/main/static/js/index.js?query=1#hash" -> "/app/main/static/js/index.js"
119-
function getAbsolutePathFromUrl(url: string): string {
120-
return cleanUrl(removeDomainFromUrl(url));
121-
}
122-
123103
function getNextRetryUrl(
124-
existRetryTimes: number,
104+
currRetryUrl: string,
105+
domain: string,
125106
nextDomain: string,
126-
originalSrcUrl: string,
107+
existRetryTimes: number,
108+
originalQuery: string,
127109
) {
128-
const absolutePath = getAbsolutePathFromUrl(originalSrcUrl);
129110
return (
130-
nextDomain +
131-
absolutePath +
132-
getUrlRetryQuery(existRetryTimes, getQueryFromUrl(originalSrcUrl))
111+
cleanUrl(currRetryUrl.replace(domain, nextDomain)) +
112+
getUrlRetryQuery(existRetryTimes + 1, originalQuery)
133113
);
134114
}
135115

@@ -145,18 +125,28 @@ function getCurrentRetry(
145125
function initRetry(chunkId: string): Retry {
146126
const originalScriptFilename = originalGetChunkScriptFilename(chunkId);
147127

148-
const originalSrcUrl =
149-
__RUNTIME_GLOBALS_PUBLIC_PATH__ + originalScriptFilename;
128+
const originalPublicPath = __RUNTIME_GLOBALS_PUBLIC_PATH__;
129+
const originalSrcUrl = originalPublicPath.startsWith('/')
130+
? window.origin + originalPublicPath + originalScriptFilename
131+
: originalPublicPath + originalScriptFilename;
132+
const originalQuery = getQueryFromUrl(originalSrcUrl);
150133

151-
const existRetryTimes = 1;
134+
const existRetryTimes = 0;
152135
const nextDomain = config.domain?.[0] ?? window.origin;
153136

154137
return {
155138
nextDomain,
156-
nextRetryUrl: getNextRetryUrl(existRetryTimes, nextDomain, originalSrcUrl),
139+
nextRetryUrl: getNextRetryUrl(
140+
originalSrcUrl,
141+
nextDomain,
142+
nextDomain,
143+
existRetryTimes,
144+
originalQuery,
145+
),
157146

158147
originalScriptFilename,
159148
originalSrcUrl,
149+
originalQuery,
160150
};
161151
}
162152

@@ -170,19 +160,22 @@ function nextRetry(chunkId: string, existRetryTimes: number): Retry {
170160
nextRetry = initRetry(chunkId);
171161
retryCollector[chunkId] = [];
172162
} else {
173-
const { originalScriptFilename, originalSrcUrl } = currRetry;
163+
const { originalScriptFilename, originalSrcUrl, originalQuery } = currRetry;
174164
const nextDomain = findNextDomain(currRetry.nextDomain);
175165

176166
nextRetry = {
177167
nextDomain,
178168
nextRetryUrl: getNextRetryUrl(
179-
nextExistRetryTimes,
169+
currRetry.nextRetryUrl,
170+
currRetry.nextDomain,
180171
nextDomain,
181-
originalSrcUrl,
172+
existRetryTimes,
173+
originalQuery,
182174
),
183175

184176
originalScriptFilename,
185177
originalSrcUrl,
178+
originalQuery,
186179
};
187180
}
188181

@@ -258,13 +251,13 @@ function ensureChunk(
258251
// the first calling is not retry
259252
// if the failed request is 4 in network panel, callingCounter.count === 4, the first one is the normal request, and existRetryTimes is 3, retried 3 times
260253
const existRetryTimes = callingCounter.count - 1;
261-
let originalSrcUrl: string;
254+
let originalScriptFilename: string;
262255
let nextRetryUrl: string;
263256
let nextDomain: string;
264257

265258
try {
266259
const retryResult = nextRetry(chunkId, existRetryTimes);
267-
originalSrcUrl = retryResult.originalSrcUrl;
260+
originalScriptFilename = retryResult.originalScriptFilename;
268261
nextRetryUrl = retryResult.nextRetryUrl;
269262
nextDomain = retryResult.nextDomain;
270263
} catch (e) {
@@ -292,7 +285,7 @@ function ensureChunk(
292285
if (existRetryTimes >= maxRetries) {
293286
error.message = error.message?.includes('retries:')
294287
? error.message
295-
: `Loading chunk ${chunkId} from ${originalSrcUrl} failed after ${maxRetries} retries: "${error.message}"`;
288+
: `Loading chunk ${chunkId} from "${originalScriptFilename}" failed after ${maxRetries} retries: "${error.message}"`;
296289
if (typeof config.onFail === 'function') {
297290
config.onFail(context);
298291
}

packages/plugin-assets-retry/src/runtime/initialChunkRetry.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ declare global {
2222
var __RB_ASYNC_CHUNKS__: Record<string, boolean>;
2323
}
2424

25+
// this function is the same as async chunk retry
2526
function findCurrentDomain(url: string, domainList: string[]) {
2627
let domain = '';
2728
for (let i = 0; i < domainList.length; i++) {
@@ -33,6 +34,7 @@ function findCurrentDomain(url: string, domainList: string[]) {
3334
return domain || window.origin;
3435
}
3536

37+
// this function is the same as async chunk retry
3638
function findNextDomain(url: string, domainList: string[]) {
3739
const currentDomain = findCurrentDomain(url, domainList);
3840
const index = domainList.indexOf(currentDomain);
@@ -246,6 +248,8 @@ function retry(config: RuntimeRetryOptions, e: Event) {
246248
// if the initial request is "/static/js/async/src_Hello_tsx.js?q=1", retry url would be "/static/js/async/src_Hello_tsx.js?q=1&retry=1"
247249
const originalQuery =
248250
target.dataset.rsbuildOriginalQuery ?? getQueryFromUrl(url);
251+
252+
// this function is the same as async chunk retry
249253
function getUrlRetryQuery(existRetryTimes: number): string {
250254
if (config.addQuery === true) {
251255
return originalQuery !== ''
@@ -258,15 +262,26 @@ function retry(config: RuntimeRetryOptions, e: Event) {
258262
return '';
259263
}
260264

265+
// this function is the same as async chunk retry
266+
function getNextRetryUrl(
267+
currRetryUrl: string,
268+
domain: string,
269+
nextDomain: string,
270+
existRetryTimes: number,
271+
) {
272+
return (
273+
cleanUrl(currRetryUrl.replace(domain, nextDomain)) +
274+
getUrlRetryQuery(existRetryTimes + 1)
275+
);
276+
}
277+
261278
const isAsync =
262279
Boolean(target.dataset.rsbuildAsync) ||
263280
(target as HTMLScriptElement).async ||
264281
(target as HTMLScriptElement).defer;
265282

266283
const attributes: ScriptElementAttributes = {
267-
url:
268-
cleanUrl(url.replace(domain, nextDomain)) +
269-
getUrlRetryQuery(existRetryTimes + 1),
284+
url: getNextRetryUrl(url, domain, nextDomain, existRetryTimes),
270285
times: existRetryTimes + 1,
271286
crossOrigin: config.crossOrigin,
272287
isAsync,

0 commit comments

Comments
 (0)