Skip to content

Instantly share code, notes, and snippets.

@AliRazaviDeveloper
Forked from mberneti/retryDynamicImport.ts
Last active October 25, 2024 15:56
Show Gist options
  • Save AliRazaviDeveloper/831fb98f468dac5c031ba25e50844c02 to your computer and use it in GitHub Desktop.
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…
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