Skip to content

Commit

Permalink
refactor(console): safely lazy load pages (#6332)
Browse files Browse the repository at this point in the history
* refactor(console): safely lazy load pages

* chore(console): use react-safe-lazy
  • Loading branch information
gao-sun authored Jul 26, 2024
1 parent ac40ef1 commit c45a1a5
Show file tree
Hide file tree
Showing 25 changed files with 257 additions and 164 deletions.
9 changes: 3 additions & 6 deletions packages/console/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
unnamedComponents: 'arrow-function',
},
],
'react/jsx-pascal-case': ['error', { ignore: ['__Internal__*'] }],
'import/no-unused-modules': [
'error',
{
Expand All @@ -30,6 +31,8 @@ module.exports = {
'**/assets/docs/guides/*/index.ts',
'**/assets/docs/guides/*/components/**/*.tsx',
'**/mdx-components*/*/index.tsx',
'*.config.js',
'*.config.ts',
],
rules: {
'import/no-unused-modules': 'off',
Expand All @@ -49,12 +52,6 @@ module.exports = {
],
},
},
{
files: ['*.config.js', '*.config.ts', '*.d.ts'],
rules: {
'import/no-unused-modules': 'off',
},
},
{
files: ['*.d.ts'],
rules: {
Expand Down
1 change: 1 addition & 0 deletions packages/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
"react-modal": "^3.15.1",
"react-paginate": "^8.1.3",
"react-router-dom": "^6.25.1",
"react-safe-lazy": "^0.1.0",
"react-syntax-highlighter": "^15.5.0",
"react-timer-hook": "^3.0.5",
"recharts": "^2.1.13",
Expand Down
5 changes: 3 additions & 2 deletions packages/console/src/containers/ConsoleContent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { lazy, Suspense } from 'react';
import { Suspense } from 'react';
import { useOutletContext, useRoutes } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { isDevFeaturesEnabled } from '@/consts/env';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
Expand All @@ -14,7 +15,7 @@ import { Skeleton } from './Sidebar';
import useTenantScopeListener from './hooks';
import styles from './index.module.scss';

const Sidebar = lazy(async () => import('./Sidebar'));
const Sidebar = safeLazy(async () => import('./Sidebar'));

function ConsoleContent() {
const { scrollableContent } = useOutletContext<AppContentOutletContext>();
Expand Down
65 changes: 38 additions & 27 deletions packages/console/src/containers/ConsoleRoutes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { ossConsolePath } from '@logto/schemas';
import { Suspense } from 'react';
import { Navigate, Outlet, Route, Routes } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';
import { SWRConfig } from 'swr';

import { isCloud } from '@/consts/env';
import AppLoading from '@/components/AppLoading';
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
import AppBoundary from '@/containers/AppBoundary';
import AppContent, { RedirectToFirstItem } from '@/containers/AppContent';
import ConsoleContent from '@/containers/ConsoleContent';
Expand All @@ -12,10 +15,13 @@ import { GlobalRoute } from '@/contexts/TenantsProvider';
import useSwrOptions from '@/hooks/use-swr-options';
import Callback from '@/pages/Callback';
import CheckoutSuccessCallback from '@/pages/CheckoutSuccessCallback';
import Profile from '@/pages/Profile';
import Welcome from '@/pages/Welcome';
import { dropLeadingSlash } from '@/utils/url';

import { __Internal__ImportError } from './internal';

const Welcome = safeLazy(async () => import('@/pages/Welcome'));
const Profile = safeLazy(async () => import('@/pages/Profile'));

function Layout() {
const swrOptions = useSwrOptions();

Expand All @@ -30,32 +36,37 @@ function Layout() {

export function ConsoleRoutes() {
return (
<Routes>
{/**
* OSS doesn't have a tenant concept nor root path handling component, but it may
* navigate to the root path in frontend. In this case, we redirect it to the OSS
* console path to trigger the console routes.
*/}
{!isCloud && <Route path="/" element={<Navigate to={ossConsolePath} />} />}
<Route path="/:tenantId" element={<Layout />}>
<Route path="callback" element={<Callback />} />
<Route path="welcome" element={<Welcome />} />
<Route element={<ProtectedRoutes />}>
<Route path={dropLeadingSlash(GlobalRoute.Profile) + '/*'} element={<Profile />} />
<Route element={<TenantAccess />}>
{isCloud && (
<Route
path={dropLeadingSlash(GlobalRoute.CheckoutSuccessCallback)}
element={<CheckoutSuccessCallback />}
/>
)}
<Route element={<AppContent />}>
<Route index element={<RedirectToFirstItem />} />
<Route path="*" element={<ConsoleContent />} />
<Suspense fallback={<AppLoading />}>
<Routes>
{/**
* OSS doesn't have a tenant concept nor root path handling component, but it may
* navigate to the root path in frontend. In this case, we redirect it to the OSS
* console path to trigger the console routes.
*/}
{!isCloud && <Route path="/" element={<Navigate to={ossConsolePath} />} />}
<Route path="/:tenantId" element={<Layout />}>
<Route path="callback" element={<Callback />} />
<Route path="welcome" element={<Welcome />} />
{isDevFeaturesEnabled && (
<Route path="__internal__/import-error" element={<__Internal__ImportError />} />
)}
<Route element={<ProtectedRoutes />}>
<Route path={dropLeadingSlash(GlobalRoute.Profile) + '/*'} element={<Profile />} />
<Route element={<TenantAccess />}>
{isCloud && (
<Route
path={dropLeadingSlash(GlobalRoute.CheckoutSuccessCallback)}
element={<CheckoutSuccessCallback />}
/>
)}
<Route element={<AppContent />}>
<Route index element={<RedirectToFirstItem />} />
<Route path="*" element={<ConsoleContent />} />
</Route>
</Route>
</Route>
</Route>
</Route>
</Routes>
</Routes>
</Suspense>
);
}
14 changes: 14 additions & 0 deletions packages/console/src/containers/ConsoleRoutes/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { safeLazy } from 'react-safe-lazy';

/**
* An internal module that is used to test the lazy loading failure in the console. Normally, this
* module should not involve any production code.
*/
export const __Internal__ImportError = safeLazy(async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const module = await import(
/* @vite-ignore */ `${window.location.origin}/some-non-existing-path`
);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return module;
});
9 changes: 5 additions & 4 deletions packages/console/src/hooks/use-console-routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { condArray } from '@silverhand/essentials';
import { lazy, useMemo } from 'react';
import { useMemo } from 'react';
import { type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { isCloud } from '@/consts/env';
import NotFound from '@/pages/NotFound';
Expand All @@ -20,9 +21,9 @@ import { useTenantSettings } from './routes/tenant-settings';
import { users } from './routes/users';
import { webhooks } from './routes/webhooks';

const Dashboard = lazy(async () => import('@/pages/Dashboard'));
const GetStarted = lazy(async () => import('@/pages/GetStarted'));
const SigningKeys = lazy(async () => import('@/pages/SigningKeys'));
const Dashboard = safeLazy(async () => import('@/pages/Dashboard'));
const GetStarted = safeLazy(async () => import('@/pages/GetStarted'));
const SigningKeys = safeLazy(async () => import('@/pages/SigningKeys'));

export const useConsoleRoutes = () => {
const tenantSettings = useTenantSettings();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { lazy } from 'react';
import { Navigate, type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { ApiResourceDetailsTabs } from '@/consts';

const ApiResources = lazy(async () => import('@/pages/ApiResources'));
const ApiResourceDetails = lazy(async () => import('@/pages/ApiResourceDetails'));
const ApiResourcePermissions = lazy(
const ApiResources = safeLazy(async () => import('@/pages/ApiResources'));
const ApiResourceDetails = safeLazy(async () => import('@/pages/ApiResourceDetails'));
const ApiResourcePermissions = safeLazy(
async () => import('@/pages/ApiResourceDetails/ApiResourcePermissions')
);
const ApiResourceSettings = lazy(
const ApiResourceSettings = safeLazy(
async () => import('@/pages/ApiResourceDetails/ApiResourceSettings')
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { lazy } from 'react';
import { Navigate, type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { ApplicationDetailsTabs } from '@/consts';

const Applications = lazy(async () => import('@/pages/Applications'));
const ApplicationDetails = lazy(async () => import('@/pages/ApplicationDetails'));
const AuditLogDetails = lazy(async () => import('@/pages/AuditLogDetails'));
const Applications = safeLazy(async () => import('@/pages/Applications'));
const ApplicationDetails = safeLazy(async () => import('@/pages/ApplicationDetails'));
const AuditLogDetails = safeLazy(async () => import('@/pages/AuditLogDetails'));

export const applications: RouteObject = {
path: 'applications',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { lazy } from 'react';
import { type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

const AuditLogs = lazy(async () => import('@/pages/AuditLogs'));
const AuditLogDetails = lazy(async () => import('@/pages/AuditLogDetails'));
const AuditLogs = safeLazy(async () => import('@/pages/AuditLogs'));
const AuditLogDetails = safeLazy(async () => import('@/pages/AuditLogDetails'));

export const auditLogs: RouteObject = {
path: 'audit-logs',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { lazy } from 'react';
import { Navigate } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { ConnectorsTabs } from '@/consts';

const Connectors = lazy(async () => import('@/pages/Connectors'));
const ConnectorDetails = lazy(async () => import('@/pages/ConnectorDetails'));
const Connectors = safeLazy(async () => import('@/pages/Connectors'));
const ConnectorDetails = safeLazy(async () => import('@/pages/ConnectorDetails'));

export const connectors = {
path: 'connectors',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { lazy } from 'react';
import { type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

const CustomizeJwt = lazy(async () => import('@/pages/CustomizeJwt'));
const CustomizeJwtDetails = lazy(async () => import('@/pages/CustomizeJwtDetails'));
const CustomizeJwt = safeLazy(async () => import('@/pages/CustomizeJwt'));
const CustomizeJwtDetails = safeLazy(async () => import('@/pages/CustomizeJwtDetails'));

export const customizeJwt: RouteObject = {
path: 'customize-jwt',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { lazy } from 'react';
import { Navigate, type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { EnterpriseSsoDetailsTabs } from '@/consts/page-tabs';

const EnterpriseSso = lazy(async () => import('@/pages/EnterpriseSso'));
const EnterpriseSsoDetails = lazy(async () => import('@/pages/EnterpriseSsoDetails'));
const EnterpriseSso = safeLazy(async () => import('@/pages/EnterpriseSso'));
const EnterpriseSsoDetails = safeLazy(async () => import('@/pages/EnterpriseSsoDetails'));

export const enterpriseSso: RouteObject = {
path: 'enterprise-sso',
Expand Down
4 changes: 2 additions & 2 deletions packages/console/src/hooks/use-console-routes/routes/mfa.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { lazy } from 'react';
import { type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

const Mfa = lazy(async () => import('@/pages/Mfa'));
const Mfa = safeLazy(async () => import('@/pages/Mfa'));

export const mfa: RouteObject = { path: 'mfa', element: <Mfa /> };
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { lazy } from 'react';
import { Navigate, type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { OrganizationRoleDetailsTabs, OrganizationTemplateTabs } from '@/consts';

const OrganizationTemplate = lazy(async () => import('@/pages/OrganizationTemplate'));
const OrganizationRoles = lazy(
const OrganizationTemplate = safeLazy(async () => import('@/pages/OrganizationTemplate'));
const OrganizationRoles = safeLazy(
async () => import('@/pages/OrganizationTemplate/OrganizationRoles')
);
const OrganizationPermissions = lazy(
const OrganizationPermissions = safeLazy(
async () => import('@/pages/OrganizationTemplate/OrganizationPermissions')
);
const OrganizationRoleDetails = lazy(async () => import('@/pages/OrganizationRoleDetails'));
const Permissions = lazy(async () => import('@/pages/OrganizationRoleDetails/Permissions'));
const Settings = lazy(async () => import('@/pages/OrganizationRoleDetails/Settings'));
const OrganizationRoleDetails = safeLazy(async () => import('@/pages/OrganizationRoleDetails'));
const Permissions = safeLazy(async () => import('@/pages/OrganizationRoleDetails/Permissions'));
const Settings = safeLazy(async () => import('@/pages/OrganizationRoleDetails/Settings'));

export const organizationTemplate: RouteObject[] = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { condArray } from '@silverhand/essentials';
import { lazy } from 'react';
import { Navigate, type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { OrganizationDetailsTabs } from '@/pages/OrganizationDetails/types';

const Organizations = lazy(async () => import('@/pages/Organizations'));
const OrganizationDetails = lazy(async () => import('@/pages/OrganizationDetails'));
const MachineToMachine = lazy(async () => import('@/pages/OrganizationDetails/MachineToMachine'));
const Members = lazy(async () => import('@/pages/OrganizationDetails/Members'));
const Settings = lazy(async () => import('@/pages/OrganizationDetails/Settings'));
const Organizations = safeLazy(async () => import('@/pages/Organizations'));
const OrganizationDetails = safeLazy(async () => import('@/pages/OrganizationDetails'));
const MachineToMachine = safeLazy(
async () => import('@/pages/OrganizationDetails/MachineToMachine')
);
const Members = safeLazy(async () => import('@/pages/OrganizationDetails/Members'));
const Settings = safeLazy(async () => import('@/pages/OrganizationDetails/Settings'));

export const organizations: RouteObject = {
path: 'organizations',
Expand Down
10 changes: 5 additions & 5 deletions packages/console/src/hooks/use-console-routes/routes/profile.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { lazy } from 'react';
import { type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

const ChangePasswordModal = lazy(
const ChangePasswordModal = safeLazy(
async () => import('@/pages/Profile/containers/ChangePasswordModal')
);
const LinkEmailModal = lazy(async () => import('@/pages/Profile/containers/LinkEmailModal'));
const VerificationCodeModal = lazy(
const LinkEmailModal = safeLazy(async () => import('@/pages/Profile/containers/LinkEmailModal'));
const VerificationCodeModal = safeLazy(
async () => import('@/pages/Profile/containers/VerificationCodeModal')
);
const VerifyPasswordModal = lazy(
const VerifyPasswordModal = safeLazy(
async () => import('@/pages/Profile/containers/VerifyPasswordModal')
);

Expand Down
14 changes: 7 additions & 7 deletions packages/console/src/hooks/use-console-routes/routes/roles.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { lazy } from 'react';
import { Navigate, type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { RoleDetailsTabs } from '@/consts/page-tabs';

const Roles = lazy(async () => import('@/pages/Roles'));
const RoleDetails = lazy(async () => import('@/pages/RoleDetails'));
const RolePermissions = lazy(async () => import('@/pages/RoleDetails/RolePermissions'));
const RoleSettings = lazy(async () => import('@/pages/RoleDetails/RoleSettings'));
const RoleUsers = lazy(async () => import('@/pages/RoleDetails/RoleUsers'));
const RoleApplications = lazy(async () => import('@/pages/RoleDetails/RoleApplications'));
const Roles = safeLazy(async () => import('@/pages/Roles'));
const RoleDetails = safeLazy(async () => import('@/pages/RoleDetails'));
const RolePermissions = safeLazy(async () => import('@/pages/RoleDetails/RolePermissions'));
const RoleSettings = safeLazy(async () => import('@/pages/RoleDetails/RoleSettings'));
const RoleUsers = safeLazy(async () => import('@/pages/RoleDetails/RoleUsers'));
const RoleApplications = safeLazy(async () => import('@/pages/RoleDetails/RoleApplications'));

export const roles: RouteObject = {
path: 'roles',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { lazy } from 'react';
import { Navigate, type RouteObject } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';

import { SignInExperienceTab } from '@/pages/SignInExperience/types';

const SignInExperience = lazy(async () => import('@/pages/SignInExperience'));
const SignInExperience = safeLazy(async () => import('@/pages/SignInExperience'));

export const signInExperience: RouteObject = {
path: 'sign-in-experience',
Expand Down
Loading

0 comments on commit c45a1a5

Please sign in to comment.