-
-
Save AliRazaviDeveloper/831fb98f468dac5c031ba25e50844c02 to your computer and use it in GitHub Desktop.
This utility function retryDynamicImport enhances React’s lazy loading mechanism by adding retry logic with a versioned query parameter. It retries importing a component multiple times in case of failure, which can be useful for bypassing browser cache or dealing with intermittent network issues. It can be used as a drop-in replacement for React…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ComponentType, lazy } from "react"; | |
interface RetryOptions { | |
maxRetryCount?: number; | |
retryDelayMs?: number; | |
} | |
const DEFAULT_RETRY_OPTIONS: Required<RetryOptions> = { | |
maxRetryCount: 15, | |
retryDelayMs: 500, | |
}; | |
const uriOrRelativePathRegex = /import\(["']([^)]+)['"]\)/; | |
const getRouteComponentUrl = ( | |
originalImport: () => Promise<any> | |
): string | null => { | |
try { | |
const fnString = originalImport.toString(); | |
return fnString.match(uriOrRelativePathRegex)?.[1] || null; | |
} catch (e) { | |
console.error("Error extracting component URL:", e); | |
return null; | |
} | |
}; | |
const getRetryImportFunction = ( | |
originalImport: () => Promise<any>, | |
retryCount: number | |
): (() => Promise<any>) => { | |
const importUrl = getRouteComponentUrl(originalImport); | |
if (!importUrl || retryCount === 0) { | |
return originalImport; | |
} | |
const importUrlWithVersionQuery = importUrl.includes("?") | |
? `${importUrl}&v=${retryCount}-${Math.random().toString(36).substring(2)}` | |
: `${importUrl}?v=${retryCount}-${Math.random().toString(36).substring(2)}`; | |
return () => import(/* @vite-ignore */ importUrlWithVersionQuery); | |
}; | |
export function retryDynamicImport<T extends ComponentType<any>>( | |
importFunction: () => Promise<{ default: T }>, | |
options: RetryOptions = {} | |
): React.LazyExoticComponent<T> { | |
const { maxRetryCount, retryDelayMs } = { | |
...DEFAULT_RETRY_OPTIONS, | |
...options, | |
}; | |
let retryCount = 0; | |
const loadComponent = (): Promise<{ default: T }> => | |
new Promise((resolve, reject) => { | |
function tryLoadComponent() { | |
const retryImport = getRetryImportFunction(importFunction, retryCount); | |
retryImport() | |
.then((module) => { | |
if (retryCount > 0) { | |
console.log( | |
`Component loaded successfully after ${retryCount} ${ | |
retryCount === 1 ? "retry" : "retries" | |
}.` | |
); | |
} | |
resolve(module); | |
}) | |
.catch((error) => { | |
retryCount += 1; | |
if (retryCount <= maxRetryCount) { | |
const delay = retryDelayMs * Math.pow(2, retryCount - 1); // Exponential backoff | |
console.warn( | |
`Retry attempt ${retryCount} failed, retrying in ${delay}ms...` | |
); | |
setTimeout(() => tryLoadComponent(), delay); | |
} else { | |
console.error( | |
"Failed to load component after maximum retries. Reloading the page..." | |
); | |
reject(error); | |
window.location.reload(); | |
} | |
}); | |
} | |
tryLoadComponent(); | |
}); | |
return lazy(() => loadComponent()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment