Improve reliability by retrying failed imports (#5408)

This commit is contained in:
Tom Moor
2023-06-03 14:17:36 -04:00
committed by GitHub
parent a8a506af3e
commit d18994b14e
10 changed files with 74 additions and 25 deletions

View File

@@ -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();

View File

@@ -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,

View File

@@ -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")
); );

View File

@@ -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")
); );

View File

@@ -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">

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 (

View File

@@ -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;

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