1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-10 17:53:36 +02:00

consistent lazy-load components

This commit is contained in:
Tymoteusz Czech 2023-06-05 16:51:30 +02:00
parent 378d22156a
commit 4679e9f48e
No known key found for this signature in database
GPG Key ID: 133555230D88D75F
24 changed files with 228 additions and 159 deletions

View File

@ -10,7 +10,7 @@ import NotFound from 'component/common/NotFound/NotFound';
import { ProtectedRoute } from 'component/common/ProtectedRoute/ProtectedRoute';
import { SWRProvider } from 'component/providers/SWRProvider/SWRProvider';
import { PlausibleProvider } from 'component/providers/PlausibleProvider/PlausibleProvider';
import { routes } from 'component/menu/routes';
import { preload, routes } from 'component/menu/routes';
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPageRedirect';
@ -49,6 +49,14 @@ export const App = () => {
}
}, [authDetails, user]);
useEffect(() => {
const timeout = setTimeout(() => {
preload();
}, 3_000);
return () => clearTimeout(timeout);
});
return (
<ErrorBoundary FallbackComponent={Error}>
<PlausibleProvider>

View File

@ -1,3 +0,0 @@
import { lazy } from 'react';
export const LazyAdmin = lazy(() => import('./LazyAdminExport'));

View File

@ -1,7 +0,0 @@
// Lazy loading only supports default export. We have an ADR
// that says we should only use named exports, because
// it makes it harder to diverge from the name of the component when
// importing it in other files.
import { Admin } from './Admin';
export default Admin;

View File

@ -1,8 +1,13 @@
import React, { forwardRef, Fragment, useImperativeHandle } from 'react';
import React, {
forwardRef,
Fragment,
lazy,
Suspense,
useImperativeHandle,
} from 'react';
import { Button, styled, Tooltip } from '@mui/material';
import { HelpOutline } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy';
import { ConstraintAccordion } from 'component/common/ConstraintAccordion/ConstraintAccordion';
import produce from 'immer';
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
import { useWeakMap } from 'hooks/useWeakMap';
@ -32,6 +37,12 @@ interface IConstraintAccordionListItemState {
editing?: boolean;
}
const ConstraintAccordion = lazy(() =>
import('component/common/ConstraintAccordion/ConstraintAccordion').then(
({ ConstraintAccordion }) => ({ default: ConstraintAccordion })
)
);
export const constraintAccordionListId = 'constraintAccordionListId';
const StyledContainer = styled('div')({
@ -153,15 +164,21 @@ export const ConstraintAccordionList = forwardRef<
condition={index > 0}
show={<StrategySeparator text="AND" />}
/>
<ConstraintAccordion
constraint={constraint}
onEdit={onEdit && onEdit.bind(null, constraint)}
onCancel={onCancel.bind(null, index)}
onDelete={onRemove && onRemove.bind(null, index)}
onSave={onSave && onSave.bind(null, index)}
editing={Boolean(state.get(constraint)?.editing)}
compact
/>
<Suspense fallback={null}>
<ConstraintAccordion
constraint={constraint}
onEdit={onEdit && onEdit.bind(null, constraint)}
onCancel={onCancel.bind(null, index)}
onDelete={
onRemove && onRemove.bind(null, index)
}
onSave={onSave && onSave.bind(null, index)}
editing={Boolean(
state.get(constraint)?.editing
)}
compact
/>
</Suspense>
</Fragment>
))}
<ConditionallyRender

View File

@ -25,7 +25,7 @@ import { Adjust } from '@mui/icons-material';
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
import { Search } from 'component/common/Search/Search';
const ContextList: VFC = () => {
export const ContextList: VFC = () => {
const [showDelDialogue, setShowDelDialogue] = useState(false);
const [name, setName] = useState<string>();
const { context, refetchUnleashContext, loading } = useUnleashContext();
@ -227,5 +227,3 @@ const ContextList: VFC = () => {
</PageContent>
);
};
export default ContextList;

View File

@ -6,7 +6,6 @@ import { TOPICS } from './demo-topics';
import { DemoDialogWelcome } from './DemoDialog/DemoDialogWelcome/DemoDialogWelcome';
import { DemoDialogFinish } from './DemoDialog/DemoDialogFinish/DemoDialogFinish';
import { DemoDialogPlans } from './DemoDialog/DemoDialogPlans/DemoDialogPlans';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { DemoBanner } from './DemoBanner/DemoBanner';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useMediaQuery } from '@mui/material';
@ -26,7 +25,6 @@ interface IDemoProps {
}
export const Demo = ({ children }: IDemoProps): JSX.Element => {
const { uiConfig } = useUiConfig();
const isSmallScreen = useMediaQuery(theme.breakpoints.down(768));
const { trackEvent } = usePlausibleTracker();
@ -80,8 +78,6 @@ export const Demo = ({ children }: IDemoProps): JSX.Element => {
setStep(0);
};
if (!uiConfig.flags.demo) return children;
return (
<>
<DemoBanner

View File

@ -1,4 +1,6 @@
import { FC, Suspense, lazy } from 'react';
import Loader from 'component/common/Loader/Loader';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
const LazyDemoComponent = lazy(() =>
import('./Demo').then(module => ({
@ -6,10 +8,16 @@ const LazyDemoComponent = lazy(() =>
}))
);
export const LazyDemo: FC = ({ children }) => (
<Suspense fallback={<>{children}</>}>
<LazyDemoComponent>
<>{children}</>
</LazyDemoComponent>
</Suspense>
);
export const LazyDemo: FC = ({ children }) => {
const { uiConfig } = useUiConfig();
if (!uiConfig.flags.demo) return <>{children}</>;
return (
<Suspense fallback={<Loader />}>
<LazyDemoComponent>
<>{children}</>
</LazyDemoComponent>
</Suspense>
);
};

View File

@ -18,7 +18,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { GO_BACK } from 'constants/navigate';
import { ENV_LIMIT } from 'constants/values';
const CreateEnvironment = () => {
export const CreateEnvironment = () => {
const { setToastApiError, setToastData } = useToast();
const { uiConfig } = useUiConfig();
const navigate = useNavigate();
@ -134,5 +134,3 @@ const CreateEnvironment = () => {
/>
);
};
export default CreateEnvironment;

View File

@ -13,7 +13,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { GO_BACK } from 'constants/navigate';
const EditEnvironment = () => {
export const EditEnvironment = () => {
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const id = useRequiredPathParam('id');
@ -93,5 +93,3 @@ const EditEnvironment = () => {
</FormTemplate>
);
};
export default EditEnvironment;

View File

@ -13,7 +13,7 @@ import { CF_CREATE_BTN_ID } from 'utils/testIds';
import { formatUnknownError } from 'utils/formatUnknownError';
import { GO_BACK } from 'constants/navigate';
const CreateFeature = () => {
export const CreateFeature = () => {
const { setToastData, setToastApiError } = useToast();
const { setShowFeedback } = useContext(UIContext);
const { uiConfig } = useUiConfig();
@ -112,5 +112,3 @@ const CreateFeature = () => {
</FormTemplate>
);
};
export default CreateFeature;

View File

@ -13,7 +13,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { GO_BACK } from 'constants/navigate';
const EditFeature = () => {
export const EditFeature = () => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { setToastData, setToastApiError } = useToast();
@ -110,5 +110,3 @@ const EditFeature = () => {
</FormTemplate>
);
};
export default EditFeature;

View File

@ -1,3 +0,0 @@
import { lazy } from 'react';
export const LazyFeatureView = lazy(() => import('./FeatureViewLazyExport'));

View File

@ -1,175 +1,264 @@
import { lazy } from 'react';
import { ComponentType, LazyExoticComponent, lazy } from 'react';
import { EEA, P, RE, SE, UG } from 'component/common/flags';
import Login from 'component/user/Login/Login';
import { INavigationMenuItem, IRoute } from 'interfaces/route';
import { SplashPage } from 'component/splash/SplashPage/SplashPage';
import { LazyPlayground } from 'component/playground/Playground/LazyPlayground';
import { LazyCreateProject } from 'component/project/Project/CreateProject/LazyCreateProject';
import { LazyFeatureView } from 'component/feature/FeatureView/LazyFeatureView';
import { LazyAdmin } from 'component/admin/LazyAdmin';
import { LazyProject } from 'component/project/Project/LazyProject';
import { AdminRedirect } from 'component/admin/AdminRedirect';
import RedirectFeatureView from 'component/feature/RedirectFeatureView/RedirectFeatureView';
import RedirectArchive from 'component/archive/RedirectArchive';
const ResetPassword = lazy(
function lazyWithPreload<T extends ComponentType<any>>(
factory: () => Promise<{ default: T }>
) {
const Component: LazyExoticComponent<T> & { preload?: () => void } =
lazy(factory);
Component.preload = factory;
return Component;
}
const ForgottenPassword = lazyWithPreload(() =>
import('component/user/ForgottenPassword/ForgottenPassword').then(
module => ({ default: module.ForgottenPassword })
)
);
const CreateEnvironment = lazyWithPreload(() =>
import('component/environments/CreateEnvironment/CreateEnvironment').then(
module => ({ default: module.CreateEnvironment })
)
);
const EditEnvironment = lazyWithPreload(() =>
import('component/environments/EditEnvironment/EditEnvironment').then(
module => ({ default: module.EditEnvironment })
)
);
const EditTagType = lazyWithPreload(() =>
import('component/tags/EditTagType/EditTagType').then(module => ({
default: module.EditTagType,
}))
);
const CreateTagType = lazyWithPreload(() =>
import('component/tags/CreateTagType/CreateTagType').then(module => ({
default: module.CreateTagType,
}))
);
const EditProject = lazyWithPreload(() =>
import('component/project/Project/EditProject/EditProject').then(
module => ({ default: module.EditProject })
)
);
const CreateFeature = lazyWithPreload(() =>
import('component/feature/CreateFeature/CreateFeature').then(module => ({
default: module.CreateFeature,
}))
);
const EditFeature = lazyWithPreload(() =>
import('component/feature/EditFeature/EditFeature').then(module => ({
default: module.EditFeature,
}))
);
const ContextList = lazyWithPreload(() =>
import('component/context/ContextList/ContextList').then(module => ({
default: module.ContextList,
}))
);
const Playground = lazyWithPreload(() =>
import('component/playground/Playground/Playground').then(module => ({
default: module.Playground,
}))
);
const FeatureView = lazyWithPreload(() =>
import('component/feature/FeatureView/FeatureView').then(module => ({
default: module.FeatureView,
}))
);
const ResetPassword = lazyWithPreload(
() => import('component/user/ResetPassword/ResetPassword')
);
const FeatureToggleListTable = lazy(() =>
const FeatureToggleListTable = lazyWithPreload(() =>
import('component/feature/FeatureToggleList/FeatureToggleListTable').then(
module => ({ default: module.FeatureToggleListTable })
)
);
const ForgottenPassword = lazy(
() => import('component/user/ForgottenPassword/ForgottenPassword')
);
const RedirectArchive = lazy(() => import('component/archive/RedirectArchive'));
const CreateEnvironment = lazy(
() => import('component/environments/CreateEnvironment/CreateEnvironment')
);
const EditEnvironment = lazy(
() => import('component/environments/EditEnvironment/EditEnvironment')
);
const EditTagType = lazy(
() => import('component/tags/EditTagType/EditTagType')
);
const CreateTagType = lazy(
() => import('component/tags/CreateTagType/CreateTagType')
);
const EditProject = lazy(
() => import('component/project/Project/EditProject/EditProject')
);
const CreateFeature = lazy(
() => import('component/feature/CreateFeature/CreateFeature')
);
const EditFeature = lazy(
() => import('component/feature/EditFeature/EditFeature')
);
const ContextList = lazy(
() => import('component/context/ContextList/ContextList')
);
const RedirectFeatureView = lazy(
() => import('component/feature/RedirectFeatureView/RedirectFeatureView')
);
const StrategyView = lazy(() =>
const StrategyView = lazyWithPreload(() =>
import('component/strategies/StrategyView/StrategyView').then(module => ({
default: module.StrategyView,
}))
);
const StrategiesList = lazy(() =>
const StrategiesList = lazyWithPreload(() =>
import('component/strategies/StrategiesList/StrategiesList').then(
module => ({ default: module.StrategiesList })
module => ({ default: module.StrategiesList, preload: true })
)
);
const TagTypeList = lazy(() =>
const TagTypeList = lazyWithPreload(() =>
import('component/tags/TagTypeList/TagTypeList').then(module => ({
default: module.TagTypeList,
}))
);
const AddonList = lazy(() =>
const AddonList = lazyWithPreload(() =>
import('component/addons/AddonList/AddonList').then(module => ({
default: module.AddonList,
}))
);
const NewUser = lazy(() =>
const NewUser = lazyWithPreload(() =>
import('component/user/NewUser/NewUser').then(module => ({
default: module.NewUser,
}))
);
const ProjectListNew = lazy(() =>
const ProjectList = lazyWithPreload(() =>
import('component/project/ProjectList/ProjectList').then(module => ({
default: module.ProjectListNew,
default: module.ProjectList,
}))
);
const EditContext = lazy(() =>
const EditContext = lazyWithPreload(() =>
import('component/context/EditContext/EditContext').then(module => ({
default: module.EditContext,
}))
);
const ApplicationEdit = lazy(() =>
const ApplicationEdit = lazyWithPreload(() =>
import('component/application/ApplicationEdit/ApplicationEdit').then(
module => ({ default: module.ApplicationEdit })
)
);
const ApplicationList = lazy(() =>
const ApplicationList = lazyWithPreload(() =>
import('component/application/ApplicationList/ApplicationList').then(
module => ({ default: module.ApplicationList })
)
);
const CreateAddon = lazy(() =>
const CreateAddon = lazyWithPreload(() =>
import('component/addons/CreateAddon/CreateAddon').then(module => ({
default: module.CreateAddon,
}))
);
const EditAddon = lazy(() =>
const EditAddon = lazyWithPreload(() =>
import('component/addons/EditAddon/EditAddon').then(module => ({
default: module.EditAddon,
}))
);
const CopyFeatureToggle = lazy(() =>
const CopyFeatureToggle = lazyWithPreload(() =>
import('component/feature/CopyFeature/CopyFeature').then(module => ({
default: module.CopyFeatureToggle,
}))
);
const EventPage = lazy(() =>
const EventPage = lazyWithPreload(() =>
import('component/events/EventPage/EventPage').then(module => ({
default: module.EventPage,
}))
);
const CreateStrategy = lazy(() =>
const CreateStrategy = lazyWithPreload(() =>
import('component/strategies/CreateStrategy/CreateStrategy').then(
module => ({ default: module.CreateStrategy })
)
);
const EditStrategy = lazy(() =>
const EditStrategy = lazyWithPreload(() =>
import('component/strategies/EditStrategy/EditStrategy').then(module => ({
default: module.EditStrategy,
}))
);
const CreateUnleashContextPage = lazy(() =>
const CreateUnleashContextPage = lazyWithPreload(() =>
import(
'component/context/CreateUnleashContext/CreateUnleashContextPage'
).then(module => ({ default: module.CreateUnleashContextPage }))
);
const CreateSegment = lazy(() =>
const CreateSegment = lazyWithPreload(() =>
import('component/segments/CreateSegment/CreateSegment').then(module => ({
default: module.CreateSegment,
}))
);
const EditSegment = lazy(() =>
const EditSegment = lazyWithPreload(() =>
import('component/segments/EditSegment/EditSegment').then(module => ({
default: module.EditSegment,
}))
);
const EnvironmentTable = lazy(() =>
const EnvironmentTable = lazyWithPreload(() =>
import('component/environments/EnvironmentTable/EnvironmentTable').then(
module => ({ default: module.EnvironmentTable })
)
);
const SegmentTable = lazy(() =>
const SegmentTable = lazyWithPreload(() =>
import('../segments/SegmentTable/SegmentTable').then(module => ({
default: module.SegmentTable,
}))
);
const FeaturesArchiveTable = lazy(() =>
const FeaturesArchiveTable = lazyWithPreload(() =>
import('../archive/FeaturesArchiveTable').then(module => ({
default: module.FeaturesArchiveTable,
}))
);
const Profile = lazy(() =>
const Profile = lazyWithPreload(() =>
import('component/user/Profile/Profile').then(module => ({
default: module.Profile,
}))
);
const AdminRedirect = lazy(() =>
import('component/admin/AdminRedirect').then(module => ({
default: module.AdminRedirect,
}))
);
const LoginHistory = lazy(() =>
const LoginHistory = lazyWithPreload(() =>
import('component/loginHistory/LoginHistory').then(module => ({
default: module.LoginHistory,
preload: true,
}))
);
const CreateProject = lazyWithPreload(() =>
import('component/project/Project/CreateProject/CreateProject').then(
module => ({ default: module.CreateProject })
)
);
const LazyAdmin = lazyWithPreload(() =>
import('component/admin/Admin').then(module => ({
default: module.Admin,
}))
);
const Project = lazyWithPreload(() =>
import('component/project/Project/Project').then(module => ({
default: module.Project,
}))
);
export const preload = () => {
const components = [
ForgottenPassword,
CreateEnvironment,
EditEnvironment,
EditTagType,
CreateTagType,
EditProject,
CreateFeature,
EditFeature,
ContextList,
Playground,
FeatureView,
ResetPassword,
FeatureToggleListTable,
StrategyView,
StrategiesList,
TagTypeList,
AddonList,
NewUser,
ProjectList,
EditContext,
ApplicationEdit,
ApplicationList,
CreateAddon,
EditAddon,
CopyFeatureToggle,
EventPage,
CreateStrategy,
EditStrategy,
CreateUnleashContextPage,
CreateSegment,
EditSegment,
EnvironmentTable,
SegmentTable,
FeaturesArchiveTable,
Profile,
LoginHistory,
CreateProject,
LazyAdmin,
Project,
];
components.forEach(component => {
if (component.preload) {
component.preload();
}
});
};
export const routes: IRoute[] = [
// Splash
@ -187,7 +276,7 @@ export const routes: IRoute[] = [
path: '/projects/create',
parent: '/projects',
title: 'Create',
component: LazyCreateProject,
component: CreateProject,
type: 'protected',
enterprise: true,
menu: {},
@ -229,7 +318,7 @@ export const routes: IRoute[] = [
path: '/projects/:projectId/features/:featureId/*',
parent: '/projects',
title: 'FeatureView',
component: LazyFeatureView,
component: FeatureView,
type: 'protected',
menu: {},
},
@ -253,7 +342,7 @@ export const routes: IRoute[] = [
path: '/projects/:projectId/*',
parent: '/projects',
title: ':projectId',
component: LazyProject,
component: Project,
flag: P,
type: 'protected',
menu: {},
@ -261,7 +350,7 @@ export const routes: IRoute[] = [
{
path: '/projects',
title: 'Projects',
component: ProjectListNew,
component: ProjectList,
type: 'protected',
menu: { mobile: true },
},
@ -287,7 +376,7 @@ export const routes: IRoute[] = [
{
path: '/playground',
title: 'Playground',
component: LazyPlayground,
component: Playground,
hidden: false,
type: 'protected',
menu: { mobile: true },

View File

@ -1,3 +0,0 @@
import { lazy } from 'react';
export const LazyPlayground = lazy(() => import('./Playground'));

View File

@ -219,5 +219,3 @@ export const Playground: VFC<{}> = () => {
</PageContent>
);
};
export default Playground;

View File

@ -16,7 +16,7 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
const CREATE_PROJECT_BTN = 'CREATE_PROJECT_BTN';
const CreateProject = () => {
export const CreateProject = () => {
const { setToastData, setToastApiError } = useToast();
const { refetchUser } = useAuthUser();
const { uiConfig } = useUiConfig();
@ -119,5 +119,3 @@ const CreateProject = () => {
</FormTemplate>
);
};
export default CreateProject;

View File

@ -1,3 +0,0 @@
import { lazy } from 'react';
export const LazyCreateProject = lazy(() => import('./CreateProject'));

View File

@ -21,7 +21,7 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
const EDIT_PROJECT_BTN = 'EDIT_PROJECT_BTN';
const EditProject = () => {
export const EditProject = () => {
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const { hasAccess } = useContext(AccessContext);
@ -138,5 +138,3 @@ const EditProject = () => {
</FormTemplate>
);
};
export default EditProject;

View File

@ -1,3 +0,0 @@
import { lazy } from 'react';
export const LazyProject = lazy(() => import('./LazyProjectExport'));

View File

@ -1,7 +0,0 @@
// Lazy loading only supports default export. We have an ADR
// that says we should only use named exports, because
// it makes it harder to diverge from the name of the component when
// importing it in other files.
import { Project } from './Project';
export default Project;

View File

@ -99,7 +99,7 @@ function resolveCreateButtonData(
}
}
export const ProjectListNew = () => {
export const ProjectList = () => {
const { hasAccess } = useContext(AccessContext);
const navigate = useNavigate();
const { projects, loading, error, refetch } = useProjects();

View File

@ -10,7 +10,7 @@ import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { GO_BACK } from 'constants/navigate';
const CreateTagType = () => {
export const CreateTagType = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const navigate = useNavigate();

View File

@ -12,7 +12,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { GO_BACK } from 'constants/navigate';
const EditTagType = () => {
export const EditTagType = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const navigate = useNavigate();
@ -83,5 +83,3 @@ const EditTagType = () => {
</FormTemplate>
);
};
export default EditTagType;

View File

@ -48,7 +48,7 @@ const StyledTypography = styled(Typography)(({ theme }) => ({
...textCenter,
}));
const ForgottenPassword = () => {
export const ForgottenPassword = () => {
const [email, setEmail] = useState('');
const [attempted, setAttempted] = useState(false);
const [loading, setLoading] = useState(false);
@ -141,5 +141,3 @@ const ForgottenPassword = () => {
</StandaloneLayout>
);
};
export default ForgottenPassword;