Improve reliability by retrying failed imports (#5408)
This commit is contained in:
@@ -15,6 +15,7 @@ import type { Editor as TEditor } from "~/editor";
|
|||||||
import usePolicy from "~/hooks/usePolicy";
|
import usePolicy from "~/hooks/usePolicy";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import history from "~/utils/history";
|
import history from "~/utils/history";
|
||||||
|
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||||
import {
|
import {
|
||||||
searchPath,
|
searchPath,
|
||||||
newDocumentPath,
|
newDocumentPath,
|
||||||
@@ -25,16 +26,16 @@ import {
|
|||||||
} from "~/utils/routeHelpers";
|
} from "~/utils/routeHelpers";
|
||||||
import Fade from "./Fade";
|
import Fade from "./Fade";
|
||||||
|
|
||||||
const DocumentComments = React.lazy(
|
const DocumentComments = lazyWithRetry(
|
||||||
() => import("~/scenes/Document/components/Comments")
|
() => import("~/scenes/Document/components/Comments")
|
||||||
);
|
);
|
||||||
const DocumentHistory = React.lazy(
|
const DocumentHistory = lazyWithRetry(
|
||||||
() => import("~/scenes/Document/components/History")
|
() => import("~/scenes/Document/components/History")
|
||||||
);
|
);
|
||||||
const DocumentInsights = React.lazy(
|
const DocumentInsights = lazyWithRetry(
|
||||||
() => import("~/scenes/Document/components/Insights")
|
() => import("~/scenes/Document/components/Insights")
|
||||||
);
|
);
|
||||||
const CommandBar = React.lazy(() => import("~/components/CommandBar"));
|
const CommandBar = lazyWithRetry(() => import("~/components/CommandBar"));
|
||||||
|
|
||||||
const AuthenticatedLayout: React.FC = ({ children }) => {
|
const AuthenticatedLayout: React.FC = ({ children }) => {
|
||||||
const { ui, auth } = useStores();
|
const { ui, auth } = useStores();
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ import useToasts from "~/hooks/useToasts";
|
|||||||
import { NotFoundError } from "~/utils/errors";
|
import { NotFoundError } from "~/utils/errors";
|
||||||
import { uploadFile } from "~/utils/files";
|
import { uploadFile } from "~/utils/files";
|
||||||
import { isModKey } from "~/utils/keyboard";
|
import { isModKey } from "~/utils/keyboard";
|
||||||
|
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||||
import { sharedDocumentPath } from "~/utils/routeHelpers";
|
import { sharedDocumentPath } from "~/utils/routeHelpers";
|
||||||
import { isHash } from "~/utils/urls";
|
import { isHash } from "~/utils/urls";
|
||||||
import DocumentBreadcrumb from "./DocumentBreadcrumb";
|
import DocumentBreadcrumb from "./DocumentBreadcrumb";
|
||||||
|
|
||||||
const LazyLoadedEditor = React.lazy(() => import("~/editor"));
|
const LazyLoadedEditor = lazyWithRetry(() => import("~/editor"));
|
||||||
|
|
||||||
export type Props = Optional<
|
export type Props = Optional<
|
||||||
EditorProps,
|
EditorProps,
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import Flex from "~/components/Flex";
|
|||||||
import { LabelText } from "~/components/Input";
|
import { LabelText } from "~/components/Input";
|
||||||
import NudeButton from "~/components/NudeButton";
|
import NudeButton from "~/components/NudeButton";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
|
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||||
import DelayedMount from "./DelayedMount";
|
import DelayedMount from "./DelayedMount";
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
@@ -54,7 +55,7 @@ const style = {
|
|||||||
height: 30,
|
height: 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TwitterPicker = React.lazy(
|
const TwitterPicker = lazyWithRetry(
|
||||||
() => import("react-color/lib/components/twitter/Twitter")
|
() => import("react-color/lib/components/twitter/Twitter")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { MenuButton, useMenuState } from "reakit/Menu";
|
import { MenuButton, useMenuState } from "reakit/Menu";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { s } from "@shared/styles";
|
import { s } from "@shared/styles";
|
||||||
|
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||||
import ContextMenu from "./ContextMenu";
|
import ContextMenu from "./ContextMenu";
|
||||||
import DelayedMount from "./DelayedMount";
|
import DelayedMount from "./DelayedMount";
|
||||||
import Input, { Props as InputProps } from "./Input";
|
import Input, { Props as InputProps } from "./Input";
|
||||||
@@ -68,7 +69,7 @@ const SwatchButton = styled(NudeButton)<{ $background: string | undefined }>`
|
|||||||
right: 6px;
|
right: 6px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ColorPicker = React.lazy(
|
const ColorPicker = lazyWithRetry(
|
||||||
() => import("react-color/lib/components/chrome/Chrome")
|
() => import("react-color/lib/components/chrome/Chrome")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import * as React from "react";
|
|||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||||
import useQuery from "~/hooks/useQuery";
|
import useQuery from "~/hooks/useQuery";
|
||||||
|
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||||
import type { Props } from "./Table";
|
import type { Props } from "./Table";
|
||||||
|
|
||||||
const Table = React.lazy(() => import("~/components/Table"));
|
const Table = lazyWithRetry(() => import("~/components/Table"));
|
||||||
|
|
||||||
const TableFromParams = (
|
const TableFromParams = (
|
||||||
props: Omit<Props, "onChangeSort" | "onChangePage" | "topRef">
|
props: Omit<Props, "onChangeSort" | "onChangePage" | "topRef">
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||||
|
|
||||||
const LocaleTime = React.lazy(() => import("~/components/LocaleTime"));
|
const LocaleTime = lazyWithRetry(() => import("~/components/LocaleTime"));
|
||||||
|
|
||||||
type Props = React.ComponentProps<typeof LocaleTime> & {
|
type Props = React.ComponentProps<typeof LocaleTime> & {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Switch, Redirect, RouteComponentProps } from "react-router-dom";
|
import { Switch, Redirect, RouteComponentProps } from "react-router-dom";
|
||||||
import Archive from "~/scenes/Archive";
|
|
||||||
import DocumentNew from "~/scenes/DocumentNew";
|
import DocumentNew from "~/scenes/DocumentNew";
|
||||||
import Drafts from "~/scenes/Drafts";
|
|
||||||
import Error404 from "~/scenes/Error404";
|
import Error404 from "~/scenes/Error404";
|
||||||
import Templates from "~/scenes/Templates";
|
|
||||||
import Trash from "~/scenes/Trash";
|
|
||||||
import AuthenticatedLayout from "~/components/AuthenticatedLayout";
|
import AuthenticatedLayout from "~/components/AuthenticatedLayout";
|
||||||
import CenteredContent from "~/components/CenteredContent";
|
import CenteredContent from "~/components/CenteredContent";
|
||||||
import PlaceholderDocument from "~/components/PlaceholderDocument";
|
import PlaceholderDocument from "~/components/PlaceholderDocument";
|
||||||
@@ -14,13 +10,18 @@ import Route from "~/components/ProfiledRoute";
|
|||||||
import WebsocketProvider from "~/components/WebsocketProvider";
|
import WebsocketProvider from "~/components/WebsocketProvider";
|
||||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||||
import usePolicy from "~/hooks/usePolicy";
|
import usePolicy from "~/hooks/usePolicy";
|
||||||
|
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||||
import { matchDocumentSlug as slug } from "~/utils/routeHelpers";
|
import { matchDocumentSlug as slug } from "~/utils/routeHelpers";
|
||||||
|
|
||||||
const SettingsRoutes = React.lazy(() => import("./settings"));
|
const SettingsRoutes = lazyWithRetry(() => import("./settings"));
|
||||||
const Document = React.lazy(() => import("~/scenes/Document"));
|
const Archive = lazyWithRetry(() => import("~/scenes/Archive"));
|
||||||
const Collection = React.lazy(() => import("~/scenes/Collection"));
|
const Collection = lazyWithRetry(() => import("~/scenes/Collection"));
|
||||||
const Home = React.lazy(() => import("~/scenes/Home"));
|
const Document = lazyWithRetry(() => import("~/scenes/Document"));
|
||||||
const Search = React.lazy(() => import("~/scenes/Search"));
|
const Drafts = lazyWithRetry(() => import("~/scenes/Drafts"));
|
||||||
|
const Home = lazyWithRetry(() => import("~/scenes/Home"));
|
||||||
|
const Templates = lazyWithRetry(() => import("~/scenes/Templates"));
|
||||||
|
const Search = lazyWithRetry(() => import("~/scenes/Search"));
|
||||||
|
const Trash = lazyWithRetry(() => import("~/scenes/Trash"));
|
||||||
|
|
||||||
const RedirectDocument = ({
|
const RedirectDocument = ({
|
||||||
match,
|
match,
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import DesktopRedirect from "~/scenes/DesktopRedirect";
|
|||||||
import DelayedMount from "~/components/DelayedMount";
|
import DelayedMount from "~/components/DelayedMount";
|
||||||
import FullscreenLoading from "~/components/FullscreenLoading";
|
import FullscreenLoading from "~/components/FullscreenLoading";
|
||||||
import Route from "~/components/ProfiledRoute";
|
import Route from "~/components/ProfiledRoute";
|
||||||
|
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||||
import { matchDocumentSlug as slug } from "~/utils/routeHelpers";
|
import { matchDocumentSlug as slug } from "~/utils/routeHelpers";
|
||||||
|
|
||||||
const Authenticated = React.lazy(() => import("~/components/Authenticated"));
|
const Authenticated = lazyWithRetry(() => import("~/components/Authenticated"));
|
||||||
const AuthenticatedRoutes = React.lazy(() => import("./authenticated"));
|
const AuthenticatedRoutes = lazyWithRetry(() => import("./authenticated"));
|
||||||
const SharedDocument = React.lazy(() => import("~/scenes/Document/Shared"));
|
const SharedDocument = lazyWithRetry(() => import("~/scenes/Document/Shared"));
|
||||||
const Login = React.lazy(() => import("~/scenes/Login"));
|
const Login = lazyWithRetry(() => import("~/scenes/Login"));
|
||||||
const Logout = React.lazy(() => import("~/scenes/Logout"));
|
const Logout = lazyWithRetry(() => import("~/scenes/Logout"));
|
||||||
|
|
||||||
export default function Routes() {
|
export default function Routes() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from "react";
|
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||||
|
|
||||||
const MultiplayerEditor = React.lazy(() => import("./MultiplayerEditor"));
|
const MultiplayerEditor = lazyWithRetry(() => import("./MultiplayerEditor"));
|
||||||
|
|
||||||
export default MultiplayerEditor;
|
export default MultiplayerEditor;
|
||||||
|
|||||||
41
app/utils/lazyWithRetry.ts
Normal file
41
app/utils/lazyWithRetry.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
type ComponentPromise<T extends React.ComponentType<any>> = Promise<{
|
||||||
|
default: T;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy load a component with automatic retry on failure.
|
||||||
|
*
|
||||||
|
* @param component A function that returns a promise of a component.
|
||||||
|
* @param retries The number of retries, defaults to 3.
|
||||||
|
* @param interval The interval between retries in milliseconds, defaults to 1000.
|
||||||
|
* @returns A lazy component.
|
||||||
|
*/
|
||||||
|
export default function lazyWithRetry<T extends React.ComponentType<any>>(
|
||||||
|
component: () => ComponentPromise<T>,
|
||||||
|
retries?: number,
|
||||||
|
interval?: number
|
||||||
|
): React.LazyExoticComponent<T> {
|
||||||
|
return React.lazy(() => retry(component, retries, interval));
|
||||||
|
}
|
||||||
|
|
||||||
|
function retry<T extends React.ComponentType<any>>(
|
||||||
|
fn: () => ComponentPromise<T>,
|
||||||
|
retriesLeft = 3,
|
||||||
|
interval = 1000
|
||||||
|
): ComponentPromise<T> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fn()
|
||||||
|
.then(resolve)
|
||||||
|
.catch((error) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (retriesLeft === 1) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
retry(fn, retriesLeft - 1, interval).then(resolve, reject);
|
||||||
|
}, interval);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user