diff --git a/e2e/cases/assets/assets-retry/index.test.ts b/e2e/cases/assets/assets-retry/index.test.ts index 4e629657b1..14bfb5a55f 100644 --- a/e2e/cases/assets/assets-retry/index.test.ts +++ b/e2e/cases/assets/assets-retry/index.test.ts @@ -244,7 +244,7 @@ test('react ErrorBoundary should catch error when all retries failed', async ({ await gotoPage(page, rsbuild); const compTestElement = page.locator('#async-comp-test-error'); await expect(compTestElement).toHaveText( - /ChunkLoadError: Loading chunk src_AsyncCompTest_tsx from \/static\/js\/async\/src_AsyncCompTest_tsx\.js failed after 3 retries: "Loading chunk src_AsyncCompTest_tsx failed.*/, + /ChunkLoadError: Loading chunk src_AsyncCompTest_tsx from "static\/js\/async\/src_AsyncCompTest_tsx\.js" failed after 3 retries: "Loading chunk src_AsyncCompTest_tsx failed.*/, ); const blockedResponseCount = count404Response( logs, @@ -448,7 +448,7 @@ test('onRetry and onSuccess options should work when retrying async chunk succes await rsbuild.close(); }); -test('onRetry and onFail options should work when retrying initial chunk failed', async ({ +test('domain, onRetry and onFail options should work when retrying initial chunk failed', async ({ page, }) => { const blockedMiddleware = createBlockMiddleware({ @@ -461,7 +461,7 @@ test('onRetry and onFail options should work when retrying initial chunk failed' blockedMiddleware, { minify: true, - domain: [`http://localhost:${port}`, 'http://a.com', 'http://b.com'], + domain: [`http://localhost:${port}`, 'http://a.com/foo-path', 'http://b.com'], onRetry(context) { console.info('onRetry', context); }, @@ -497,8 +497,8 @@ test('onRetry and onFail options should work when retrying initial chunk failed' }, { times: 1, - domain: 'http://a.com', - url: 'http://a.com/static/js/index.js', + domain: 'http://a.com/foo-path', + url: 'http://a.com/foo-path/static/js/index.js', tagName: 'script', isAsyncChunk: false, }, @@ -524,7 +524,7 @@ test('onRetry and onFail options should work when retrying initial chunk failed' await rsbuild.close(); }); -test('onRetry and onFail options should work when retrying async chunk failed', async ({ +test('domain, onRetry and onFail options should work when retrying async chunk failed', async ({ page, }) => { const blockedMiddleware = createBlockMiddleware({ @@ -537,7 +537,7 @@ test('onRetry and onFail options should work when retrying async chunk failed', blockedMiddleware, { minify: true, - domain: [`http://localhost:${port}`, 'http://a.com', 'http://b.com'], + domain: [`http://localhost:${port}`, 'http://a.com/foo-path', 'http://b.com'], onRetry(context) { console.info('onRetry', context); }, @@ -558,7 +558,7 @@ test('onRetry and onFail options should work when retrying async chunk failed', await gotoPage(page, rsbuild); const compTestElement = page.locator('#async-comp-test-error'); await expect(compTestElement).toHaveText( - /ChunkLoadError: Loading chunk src_AsyncCompTest_tsx from \/static\/js\/async\/src_AsyncCompTest_tsx\.js failed after 3 retries: "Loading chunk src_AsyncCompTest_tsx failed.*/, + /ChunkLoadError: Loading chunk src_AsyncCompTest_tsx from "static\/js\/async\/src_AsyncCompTest_tsx\.js" failed after 3 retries: "Loading chunk src_AsyncCompTest_tsx failed.*/, ); await delay(); @@ -577,8 +577,8 @@ test('onRetry and onFail options should work when retrying async chunk failed', }, { times: 1, - domain: 'http://a.com', - url: 'http://a.com/static/js/async/src_AsyncCompTest_tsx.js', + domain: 'http://a.com/foo-path', + url: 'http://a.com/foo-path/static/js/async/src_AsyncCompTest_tsx.js', tagName: 'script', isAsyncChunk: true, }, diff --git a/packages/plugin-assets-retry/src/runtime/asyncChunkRetry.ts b/packages/plugin-assets-retry/src/runtime/asyncChunkRetry.ts index f1347600ba..8a6c2ad960 100644 --- a/packages/plugin-assets-retry/src/runtime/asyncChunkRetry.ts +++ b/packages/plugin-assets-retry/src/runtime/asyncChunkRetry.ts @@ -10,6 +10,7 @@ type Retry = { nextRetryUrl: ChunkSrcUrl; originalScriptFilename: ChunkFilename; originalSrcUrl: ChunkSrcUrl; + originalQuery: string; }; type RetryCollector = Record>; @@ -99,37 +100,16 @@ function getUrlRetryQuery( return ''; } -function removeDomainFromUrl(url: string): string { - const protocolStartIndex = url.indexOf('//'); - - // case /app/main/static/js/index.js - if (protocolStartIndex === -1 && url.startsWith('/')) { - return url; - } - - // case "//cdn.com/app/main/static/js/index.js" - // case "http://cdn.com/app/main/static/js/index.js" - const protocolEndIndex = protocolStartIndex + 2; - const pathStartIndex = url.indexOf('/', protocolEndIndex); - - return url.slice(pathStartIndex); -} - -// "http://cdn.com/app/main/static/js/index.js?query=1#hash" -> "/app/main/static/js/index.js" -function getAbsolutePathFromUrl(url: string): string { - return cleanUrl(removeDomainFromUrl(url)); -} - function getNextRetryUrl( - existRetryTimes: number, + currRetryUrl: string, + domain: string, nextDomain: string, - originalSrcUrl: string, + existRetryTimes: number, + originalQuery: string, ) { - const absolutePath = getAbsolutePathFromUrl(originalSrcUrl); return ( - nextDomain + - absolutePath + - getUrlRetryQuery(existRetryTimes, getQueryFromUrl(originalSrcUrl)) + cleanUrl(currRetryUrl.replace(domain, nextDomain)) + + getUrlRetryQuery(existRetryTimes + 1, originalQuery) ); } @@ -145,18 +125,28 @@ function getCurrentRetry( function initRetry(chunkId: string): Retry { const originalScriptFilename = originalGetChunkScriptFilename(chunkId); - const originalSrcUrl = - __RUNTIME_GLOBALS_PUBLIC_PATH__ + originalScriptFilename; + const originalPublicPath = __RUNTIME_GLOBALS_PUBLIC_PATH__; + const originalSrcUrl = originalPublicPath.startsWith('/') + ? window.origin + originalPublicPath + originalScriptFilename + : originalPublicPath + originalScriptFilename; + const originalQuery = getQueryFromUrl(originalSrcUrl); - const existRetryTimes = 1; + const existRetryTimes = 0; const nextDomain = config.domain?.[0] ?? window.origin; return { nextDomain, - nextRetryUrl: getNextRetryUrl(existRetryTimes, nextDomain, originalSrcUrl), + nextRetryUrl: getNextRetryUrl( + originalSrcUrl, + nextDomain, + nextDomain, + existRetryTimes, + originalQuery, + ), originalScriptFilename, originalSrcUrl, + originalQuery, }; } @@ -170,19 +160,22 @@ function nextRetry(chunkId: string, existRetryTimes: number): Retry { nextRetry = initRetry(chunkId); retryCollector[chunkId] = []; } else { - const { originalScriptFilename, originalSrcUrl } = currRetry; + const { originalScriptFilename, originalSrcUrl, originalQuery } = currRetry; const nextDomain = findNextDomain(currRetry.nextDomain); nextRetry = { nextDomain, nextRetryUrl: getNextRetryUrl( - nextExistRetryTimes, + currRetry.nextRetryUrl, + currRetry.nextDomain, nextDomain, - originalSrcUrl, + existRetryTimes, + originalQuery, ), originalScriptFilename, originalSrcUrl, + originalQuery, }; } @@ -258,13 +251,13 @@ function ensureChunk( // the first calling is not retry // 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 const existRetryTimes = callingCounter.count - 1; - let originalSrcUrl: string; + let originalScriptFilename: string; let nextRetryUrl: string; let nextDomain: string; try { const retryResult = nextRetry(chunkId, existRetryTimes); - originalSrcUrl = retryResult.originalSrcUrl; + originalScriptFilename = retryResult.originalScriptFilename; nextRetryUrl = retryResult.nextRetryUrl; nextDomain = retryResult.nextDomain; } catch (e) { @@ -292,7 +285,7 @@ function ensureChunk( if (existRetryTimes >= maxRetries) { error.message = error.message?.includes('retries:') ? error.message - : `Loading chunk ${chunkId} from ${originalSrcUrl} failed after ${maxRetries} retries: "${error.message}"`; + : `Loading chunk ${chunkId} from "${originalScriptFilename}" failed after ${maxRetries} retries: "${error.message}"`; if (typeof config.onFail === 'function') { config.onFail(context); } diff --git a/packages/plugin-assets-retry/src/runtime/initialChunkRetry.ts b/packages/plugin-assets-retry/src/runtime/initialChunkRetry.ts index 7ee8759f8d..1f423e6d5c 100644 --- a/packages/plugin-assets-retry/src/runtime/initialChunkRetry.ts +++ b/packages/plugin-assets-retry/src/runtime/initialChunkRetry.ts @@ -22,6 +22,7 @@ declare global { var __RB_ASYNC_CHUNKS__: Record; } +// this function is the same as async chunk retry function findCurrentDomain(url: string, domainList: string[]) { let domain = ''; for (let i = 0; i < domainList.length; i++) { @@ -33,6 +34,7 @@ function findCurrentDomain(url: string, domainList: string[]) { return domain || window.origin; } +// this function is the same as async chunk retry function findNextDomain(url: string, domainList: string[]) { const currentDomain = findCurrentDomain(url, domainList); const index = domainList.indexOf(currentDomain); @@ -246,6 +248,8 @@ function retry(config: RuntimeRetryOptions, e: Event) { // 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" const originalQuery = target.dataset.rsbuildOriginalQuery ?? getQueryFromUrl(url); + + // this function is the same as async chunk retry function getUrlRetryQuery(existRetryTimes: number): string { if (config.addQuery === true) { return originalQuery !== '' @@ -258,15 +262,26 @@ function retry(config: RuntimeRetryOptions, e: Event) { return ''; } + // this function is the same as async chunk retry + function getNextRetryUrl( + currRetryUrl: string, + domain: string, + nextDomain: string, + existRetryTimes: number, + ) { + return ( + cleanUrl(currRetryUrl.replace(domain, nextDomain)) + + getUrlRetryQuery(existRetryTimes + 1) + ); + } + const isAsync = Boolean(target.dataset.rsbuildAsync) || (target as HTMLScriptElement).async || (target as HTMLScriptElement).defer; const attributes: ScriptElementAttributes = { - url: - cleanUrl(url.replace(domain, nextDomain)) + - getUrlRetryQuery(existRetryTimes + 1), + url: getNextRetryUrl(url, domain, nextDomain, existRetryTimes), times: existRetryTimes + 1, crossOrigin: config.crossOrigin, isAsync,