Use @probo/react-lazy instead of native React.lazy

close #58

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-03-25 16:16:11 +01:00
parent f93ee928a8
commit c4e4b16300
24 changed files with 239 additions and 21 deletions

View File

@@ -0,0 +1,167 @@
import {
lazy as reactLazy,
type LazyExoticComponent,
type ComponentType,
type FC,
} from "react";
export const DEFAULT_STORAGE_KEY = "reactLazyReloadedFunctions";
export class ReloadStorage {
constructor(public readonly storageKey = DEFAULT_STORAGE_KEY) {}
getMap(): Map<string, number> {
const stored = sessionStorage.getItem(this.storageKey);
try {
const parsed: unknown = stored ? JSON.parse(stored) : [];
return new Map(
Array.isArray(parsed)
? parsed.filter(
(value) =>
Array.isArray(value) &&
typeof value[0] === "string" &&
typeof value[1] === "number"
)
: []
);
} catch (error) {
console.error("Error parsing react-lazy storage:", error);
return new Map();
}
}
addFunction(functionName: string): void {
const stored = this.getMap();
stored.set(functionName, (stored.get(functionName) ?? 0) + 1);
this.save(stored);
}
removeFunction(functionName: string): void {
const stored = this.getMap();
stored.delete(functionName);
this.save(stored);
}
protected save(stored: Map<string, number>): void {
sessionStorage.setItem(
this.storageKey,
JSON.stringify([...stored.entries()])
);
}
}
export interface ForceReloadConfig {
maxRetries: number;
storageKey?: string;
}
export interface LazyConfigInit {
forceReload?: false | Partial<ForceReloadConfig>;
importRetries?: number;
retryDelay?: number;
onImportError?: (error: unknown) => void;
}
export interface LazyConfig {
forceReload: ForceReloadConfig;
importRetries: number;
retryDelay: number;
onImportError?: (error: unknown) => void;
}
const createConfig = (init: LazyConfigInit = {}): LazyConfig => ({
forceReload: {
maxRetries: 1,
storageKey: DEFAULT_STORAGE_KEY,
...(typeof init.forceReload === "object"
? init.forceReload
: init.forceReload === false
? { maxRetries: 0 }
: {}),
},
importRetries:
typeof init.importRetries === "number"
? Math.max(0, init.importRetries)
: 0,
retryDelay:
typeof init.retryDelay === "number" ? Math.max(0, init.retryDelay) : 300,
onImportError: init.onImportError,
});
export function createLazy<P = {}>(config: LazyConfigInit = {}) {
const { forceReload, importRetries, retryDelay, onImportError } =
createConfig(config);
const reloadStorage = new ReloadStorage(forceReload.storageKey);
function enhancedLazy<T = P>(
importFunction: () => Promise<{ default: ComponentType<T> }>
): LazyExoticComponent<ComponentType<T>> {
const functionString = importFunction.toString();
const importWithRetry = async () => {
let retryCount = 0;
const tryImport = async (): Promise<{ default: ComponentType<T> }> => {
try {
return await importFunction();
} catch (error) {
if (onImportError) {
onImportError(error);
} else {
console.error("React-lazy import error:", error);
}
if (retryCount < importRetries) {
retryCount++;
if (retryDelay > 0) {
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
return tryImport();
}
throw error;
}
};
return tryImport();
};
return reactLazy(async () => {
try {
const component = await importWithRetry();
if (forceReload.maxRetries > 0) {
reloadStorage.removeFunction(functionString);
}
return component;
} catch (error) {
if (
forceReload.maxRetries > 0 &&
(reloadStorage.getMap().get(functionString) ?? 0) <
forceReload.maxRetries
) {
reloadStorage.addFunction(functionString);
window.location.reload();
const EmptyComponent: FC<T> = () => null;
return { default: EmptyComponent };
}
throw error;
}
});
}
return enhancedLazy;
}
export const lazy = createLazy();
export function createRetryLazy(retries = 3, delay = 300) {
return createLazy({
forceReload: false,
importRetries: retries,
retryDelay: delay,
});
}