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

@@ -44,7 +44,8 @@
"react-router": "^7.1.5",
"relay-runtime": "^18.2.0",
"tailwind-merge": "^3.0.2",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"@probo/react-lazy": "workspace:*"
},
"devDependencies": {
"@babel/core": "^7.26.7",

View File

@@ -1,6 +1,7 @@
import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
import { lazy, StrictMode, Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { StrictMode, Suspense } from "react";
import { createRoot } from "react-dom/client";
import { HelmetProvider } from "react-helmet-async";
import { RelayEnvironmentProvider } from "react-relay";

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "./ErrorBoundary";

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "./ErrorBoundary";

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";

View File

@@ -1,7 +1,8 @@
import { lazy, Suspense } from "react";
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
import { useLocation } from "react-router";
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { Card, CardContent } from "@/components/ui/card";
const FrameworkListView = lazy(() => import("./FrameworkListView"));

View File

@@ -1,8 +1,9 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { Card, CardContent } from "@/components/ui/card";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
import { lazy } from "@probo/react-lazy";
const FrameworkView = lazy(() => import("./FrameworkView"));

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";

View File

@@ -1,6 +1,7 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { Card, CardContent } from "@/components/ui/card";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../../ErrorBoundary";

View File

@@ -1,7 +1,8 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { ErrorBoundaryWithLocation } from "../../ErrorBoundary";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { useLocation } from "react-router";
import { lazy } from "@probo/react-lazy";
const CreateControlView = lazy(() => import("./CreateControlView"));

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../../ErrorBoundary";

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";

View File

@@ -1,7 +1,8 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
import { lazy } from "@probo/react-lazy";
const PeopleListView = lazy(() => import("./PeopleListView"));

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";

View File

@@ -1,7 +1,8 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
import { lazy } from "@probo/react-lazy";
const CreatePolicyView = lazy(() => import("./CreatePolicyView"));

View File

@@ -1,8 +1,9 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
import { lazy } from "@probo/react-lazy";
const PolicyListView = lazy(() => import("./PolicyListView"));

View File

@@ -1,8 +1,9 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { Card, CardContent } from "@/components/ui/card";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
import { lazy } from "@probo/react-lazy";
const PolicyView = lazy(() => import("./PolicyView"));

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";

View File

@@ -1,5 +1,6 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { lazy, Suspense } from "react";
import { Suspense } from "react";
import { lazy } from "@probo/react-lazy";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";

12
package-lock.json generated
View File

@@ -2807,6 +2807,10 @@
"resolved": "packages/esbuild-plugin-postcss",
"link": true
},
"node_modules/@probo/react-lazy": {
"resolved": "packages/react-lazy",
"link": true
},
"node_modules/@probo/tsconfig": {
"resolved": "packages/tsconfig",
"link": true
@@ -10384,6 +10388,14 @@
"esbuild": "^0.25.0"
}
},
"packages/react-lazy": {
"name": "@probo/react-lazy",
"version": "0.1.0",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0"
}
},
"packages/tsconfig": {
"name": "@probo/tsconfig",
"version": "0.0.1",

View File

@@ -0,0 +1,14 @@
{
"name": "@probo/react-lazy",
"version": "0.1.0",
"description": "Enhanced React lazy loading with retries and error handling",
"private": true,
"main": "src/index.ts",
"exports": {
".": "./src/index.ts"
},
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0"
}
}

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,
});
}

View File

@@ -0,0 +1,5 @@
{
"extends": "../tsconfig/library.json",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}