Lazy load app routes
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
"@emotion/react": "11.11.4",
|
||||
"@emotion/styled": "11.11.5",
|
||||
"@fontsource/roboto": "5.0.13",
|
||||
"@loadable/component": "^5.16.4",
|
||||
"@mui/icons-material": "5.15.15",
|
||||
"@mui/material": "5.15.15",
|
||||
"@mui/x-date-pickers": "7.3.1",
|
||||
@@ -52,6 +53,7 @@
|
||||
"react-device-detect": "2.2.3",
|
||||
"react-dom": "18.3.0",
|
||||
"react-i18next": "14.1.1",
|
||||
"react-lazily": "^0.9.2",
|
||||
"react-router-dom": "6.23.0",
|
||||
"react-virtuoso": "4.7.10",
|
||||
"stylis": "4.3.2",
|
||||
|
||||
142
src/App.tsx
142
src/App.tsx
@@ -11,35 +11,39 @@ import CssBaseline from '@mui/material/CssBaseline';
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { loadErrorMessages, loadDevMessages } from '@apollo/client/dev';
|
||||
import { loadable } from 'react-lazily/loadable';
|
||||
import { AppContext } from '@/components/context/AppContext';
|
||||
import { Browse } from '@/screens/Browse';
|
||||
import { DownloadQueue } from '@/screens/DownloadQueue';
|
||||
import { Library } from '@/screens/Library';
|
||||
import { Manga } from '@/screens/Manga';
|
||||
import { Reader } from '@/screens/Reader';
|
||||
import { SearchAll } from '@/screens/SearchAll';
|
||||
import { Settings } from '@/screens/Settings';
|
||||
import { About } from '@/screens/settings/About';
|
||||
import { Backup } from '@/screens/settings/Backup';
|
||||
import { Categories } from '@/screens/settings/Categories';
|
||||
import { DefaultReaderSettings } from '@/screens/settings/DefaultReaderSettings';
|
||||
import { SourceConfigure } from '@/screens/SourceConfigure';
|
||||
import { SourceMangas } from '@/screens/SourceMangas';
|
||||
import { Updates } from '@/screens/Updates';
|
||||
import '@/i18n';
|
||||
import { LibrarySettings } from '@/screens/settings/LibrarySettings';
|
||||
import { DefaultNavBar } from '@/components/navbar/DefaultNavBar';
|
||||
import { DownloadSettings } from '@/screens/settings/DownloadSettings.tsx';
|
||||
import { ServerSettings } from '@/screens/settings/ServerSettings.tsx';
|
||||
import { ServerUpdateChecker } from '@/components/util/ServerUpdateChecker.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { BrowseSettings } from '@/screens/settings/BrowseSettings.tsx';
|
||||
import { WebUISettings } from '@/screens/settings/WebUISettings.tsx';
|
||||
import { Migrate } from '@/screens/Migrate.tsx';
|
||||
import { DeviceSetting } from '@/components/settings/DeviceSetting.tsx';
|
||||
import { TrackingSettings } from '@/screens/settings/TrackingSettings.tsx';
|
||||
import { TrackerOAuthLogin } from '@/screens/TrackerOAuthLogin.tsx';
|
||||
import { WebUIUpdateChecker } from '@/components/util/WebUIUpdateChecker.tsx';
|
||||
import { ServerUpdateChecker } from '@/components/util/ServerUpdateChecker.tsx';
|
||||
import { lazyLoadFallback } from '@/util/LazyLoad.tsx';
|
||||
import { ErrorBoundary } from '@/util/ErrorBoundary.tsx';
|
||||
|
||||
const { Browse } = loadable(() => import('@/screens/Browse'), lazyLoadFallback);
|
||||
const { DownloadQueue } = loadable(() => import('@/screens/DownloadQueue'), lazyLoadFallback);
|
||||
const { Library } = loadable(() => import('@/screens/Library'), lazyLoadFallback);
|
||||
const { Manga } = loadable(() => import('@/screens/Manga'), lazyLoadFallback);
|
||||
const { Reader } = loadable(() => import('@/screens/Reader'), lazyLoadFallback);
|
||||
const { SearchAll } = loadable(() => import('@/screens/SearchAll'), lazyLoadFallback);
|
||||
const { Settings } = loadable(() => import('@/screens/Settings'), lazyLoadFallback);
|
||||
const { About } = loadable(() => import('@/screens/settings/About'), lazyLoadFallback);
|
||||
const { Backup } = loadable(() => import('@/screens/settings/Backup'), lazyLoadFallback);
|
||||
const { Categories } = loadable(() => import('@/screens/settings/Categories'), lazyLoadFallback);
|
||||
const { DefaultReaderSettings } = loadable(() => import('@/screens/settings/DefaultReaderSettings'), lazyLoadFallback);
|
||||
const { SourceConfigure } = loadable(() => import('@/screens/SourceConfigure'), lazyLoadFallback);
|
||||
const { SourceMangas } = loadable(() => import('@/screens/SourceMangas'), lazyLoadFallback);
|
||||
const { Updates } = loadable(() => import('@/screens/Updates'), lazyLoadFallback);
|
||||
const { LibrarySettings } = loadable(() => import('@/screens/settings/LibrarySettings'), lazyLoadFallback);
|
||||
const { DownloadSettings } = loadable(() => import('@/screens/settings/DownloadSettings.tsx'), lazyLoadFallback);
|
||||
const { ServerSettings } = loadable(() => import('@/screens/settings/ServerSettings.tsx'), lazyLoadFallback);
|
||||
const { BrowseSettings } = loadable(() => import('@/screens/settings/BrowseSettings.tsx'), lazyLoadFallback);
|
||||
const { WebUISettings } = loadable(() => import('@/screens/settings/WebUISettings.tsx'), lazyLoadFallback);
|
||||
const { Migrate } = loadable(() => import('@/screens/Migrate.tsx'), lazyLoadFallback);
|
||||
const { DeviceSetting } = loadable(() => import('@/components/settings/DeviceSetting.tsx'), lazyLoadFallback);
|
||||
const { TrackingSettings } = loadable(() => import('@/screens/settings/TrackingSettings.tsx'), lazyLoadFallback);
|
||||
const { TrackerOAuthLogin } = loadable(() => import('@/screens/TrackerOAuthLogin.tsx'), lazyLoadFallback);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// Adds messages only in a dev environment
|
||||
@@ -91,51 +95,55 @@ export const App: React.FC = () => (
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Routes>
|
||||
{/* General Routes */}
|
||||
<Route path="/" element={<Navigate to="/library" replace />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
<Route path="settings">
|
||||
<Route index element={<Settings />} />
|
||||
<Route path="about" element={<About />} />
|
||||
<Route path="categories" element={<Categories />} />
|
||||
<Route path="defaultReaderSettings" element={<DefaultReaderSettings />} />
|
||||
<Route path="librarySettings" element={<LibrarySettings />} />
|
||||
<Route path="downloadSettings" element={<DownloadSettings />} />
|
||||
<Route path="backup" element={<Backup />} />
|
||||
<Route path="server" element={<ServerSettings />} />
|
||||
<Route path="webUI" element={<WebUISettings />} />
|
||||
<Route path="browseSettings" element={<BrowseSettings />} />
|
||||
<Route path="device" element={<DeviceSetting />} />
|
||||
<Route path="trackingSettings" element={<TrackingSettings />} />
|
||||
</Route>
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
{/* General Routes */}
|
||||
<Route path="/" element={<Navigate to="/library" replace />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
<Route path="settings">
|
||||
<Route index element={<Settings />} />
|
||||
<Route path="about" element={<About />} />
|
||||
<Route path="categories" element={<Categories />} />
|
||||
<Route path="defaultReaderSettings" element={<DefaultReaderSettings />} />
|
||||
<Route path="librarySettings" element={<LibrarySettings />} />
|
||||
<Route path="downloadSettings" element={<DownloadSettings />} />
|
||||
<Route path="backup" element={<Backup />} />
|
||||
<Route path="server" element={<ServerSettings />} />
|
||||
<Route path="webUI" element={<WebUISettings />} />
|
||||
<Route path="browseSettings" element={<BrowseSettings />} />
|
||||
<Route path="device" element={<DeviceSetting />} />
|
||||
<Route path="trackingSettings" element={<TrackingSettings />} />
|
||||
</Route>
|
||||
|
||||
{/* Manga Routes */}
|
||||
{/* Manga Routes */}
|
||||
|
||||
<Route path="sources">
|
||||
<Route index element={<Navigate to="/" replace />} />
|
||||
<Route path=":sourceId" element={<SourceMangas />} />
|
||||
<Route path=":sourceId/configure/" element={<SourceConfigure />} />
|
||||
<Route path="all/search/" element={<SearchAll />} />
|
||||
</Route>
|
||||
<Route path="downloads" element={<DownloadQueue />} />
|
||||
<Route path="manga/:id">
|
||||
<Route path="chapter/:chapterNum" element={null} />
|
||||
<Route index element={<Manga />} />
|
||||
</Route>
|
||||
<Route path="library" element={<Library />} />
|
||||
<Route path="updates" element={<Updates />} />
|
||||
<Route path="browse" element={<Browse />} />
|
||||
<Route path="migrate/source/:sourceId">
|
||||
<Route index element={<Migrate />} />
|
||||
<Route path="manga/:mangaId/search" element={<SearchAll />} />
|
||||
</Route>
|
||||
<Route path="tracker/login/oauth" element={<TrackerOAuthLogin />} />
|
||||
</Routes>
|
||||
<Route path="sources">
|
||||
<Route index element={<Navigate to="/" replace />} />
|
||||
<Route path=":sourceId" element={<SourceMangas />} />
|
||||
<Route path=":sourceId/configure/" element={<SourceConfigure />} />
|
||||
<Route path="all/search/" element={<SearchAll />} />
|
||||
</Route>
|
||||
<Route path="downloads" element={<DownloadQueue />} />
|
||||
<Route path="manga/:id">
|
||||
<Route path="chapter/:chapterNum" element={null} />
|
||||
<Route index element={<Manga />} />
|
||||
</Route>
|
||||
<Route path="library" element={<Library />} />
|
||||
<Route path="updates" element={<Updates />} />
|
||||
<Route path="browse" element={<Browse />} />
|
||||
<Route path="migrate/source/:sourceId">
|
||||
<Route index element={<Migrate />} />
|
||||
<Route path="manga/:mangaId/search" element={<SearchAll />} />
|
||||
</Route>
|
||||
<Route path="tracker/login/oauth" element={<TrackerOAuthLogin />} />
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
</Container>
|
||||
<Routes>
|
||||
<Route path="manga/:mangaId/chapter/:chapterIndex" element={<Reader />} />
|
||||
<Route path="*" element={null} />
|
||||
</Routes>
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
<Route path="manga/:mangaId/chapter/:chapterIndex" element={<Reader />} />
|
||||
<Route path="*" element={null} />
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
</AppContext>
|
||||
);
|
||||
|
||||
86
src/util/ErrorBoundary.tsx
Normal file
86
src/util/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, ErrorInfo, ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
interface Props {
|
||||
children?: ReactNode;
|
||||
setTrackPathChange: (change: boolean) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
error: any;
|
||||
}
|
||||
|
||||
class RealErrorBoundary extends Component<Props, State> {
|
||||
// eslint-disable-next-line react/state-in-constructor
|
||||
public state: State = { error: null };
|
||||
|
||||
private prevPath: string = '';
|
||||
|
||||
public static getDerivedStateFromError(error: any): State {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { error };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.prevPath = window.location.pathname;
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (window.location.pathname !== this.prevPath) {
|
||||
this.setState({ error: null });
|
||||
}
|
||||
|
||||
this.prevPath = window.location.pathname;
|
||||
}
|
||||
|
||||
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error('Uncaught error:', error, errorInfo);
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
this.props.setTrackPathChange(true);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { error } = this.state;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<>
|
||||
<h1>Something went wrong.</h1>
|
||||
<p>{error.message ?? JSON.stringify(error)}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const { children } = this.props;
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
export const ErrorBoundary = ({ children }: { children: React.ReactNode }) => {
|
||||
const [key, setKey] = useState(0);
|
||||
const { pathname } = useLocation();
|
||||
const previousPathnameRef = useRef(pathname);
|
||||
const [trackPathChange, setTrackPathChange] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (trackPathChange && previousPathnameRef.current !== pathname) {
|
||||
previousPathnameRef.current = pathname;
|
||||
setKey((currentKey) => (currentKey + 1) % 999999);
|
||||
setTrackPathChange(false);
|
||||
}
|
||||
}, [pathname, previousPathnameRef.current, trackPathChange]);
|
||||
|
||||
return (
|
||||
<RealErrorBoundary key={key} setTrackPathChange={setTrackPathChange}>
|
||||
{children}
|
||||
</RealErrorBoundary>
|
||||
);
|
||||
};
|
||||
11
src/util/LazyLoad.tsx
Normal file
11
src/util/LazyLoad.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx';
|
||||
|
||||
export const lazyLoadFallback = { fallback: <LoadingPlaceholder /> };
|
||||
18
yarn.lock
18
yarn.lock
@@ -1123,7 +1123,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.8.4":
|
||||
"@babel/runtime@^7.12.18", "@babel/runtime@^7.8.4":
|
||||
version "7.24.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd"
|
||||
integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==
|
||||
@@ -2049,6 +2049,15 @@
|
||||
resolved "https://registry.yarnpkg.com/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz#9d68877a489107411b953c54ea65d0658b515809"
|
||||
integrity sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==
|
||||
|
||||
"@loadable/component@^5.16.4":
|
||||
version "5.16.4"
|
||||
resolved "https://registry.yarnpkg.com/@loadable/component/-/component-5.16.4.tgz#583cb65945a5061ca3c6b90eb34835c154283ff4"
|
||||
integrity sha512-fJWxx9b5WHX90QKmizo9B+es2so8DnBthI1mbflwCoOyvzEwxiZ/SVDCTtXEnHG72/kGBdzr297SSIekYtzSOQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.18"
|
||||
hoist-non-react-statics "^3.3.1"
|
||||
react-is "^16.12.0"
|
||||
|
||||
"@mui/base@5.0.0-beta.40", "@mui/base@^5.0.0-beta.40":
|
||||
version "5.0.0-beta.40"
|
||||
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2"
|
||||
@@ -5894,7 +5903,7 @@ react-i18next@14.1.1:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
html-parse-stringify "^3.0.1"
|
||||
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
@@ -5909,6 +5918,11 @@ react-is@^18.2.0:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-lazily@^0.9.2:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/react-lazily/-/react-lazily-0.9.2.tgz#74596dbde43c8e0f607445da5c4839cf6cc48ab5"
|
||||
integrity sha512-oBVRDQ+SuMPWenBO/0Kq+iZk34lOYJEmjiTto4bYRufndf8pux3E50BT3mJZbsq0vBsAVbX3fpQjlUvsXgDVag==
|
||||
|
||||
react-property@2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6"
|
||||
|
||||
Reference in New Issue
Block a user