0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-01 02:41:39 -05:00

Optimised bundle size and configuration in AdminX (#18227)

refs https://github.com/TryGhost/Product/issues/3832

---

### <samp>🤖 Generated by Copilot at eaad533</samp>

Refactored the code for modal components in the admin-x-settings app to
improve performance, readability, type-safety, and compatibility. Used
dynamic import, default import, `import.meta.glob`, and type aliases to
simplify the logic of importing and rendering modal components. Applied
the type alias `RoutingModalProps` to all modal components to ensure
consistent and safe props.
This commit is contained in:
Jono M 2023-09-19 17:54:01 +01:00 committed by GitHub
parent 65838394c3
commit 64d7c73b00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 72 additions and 82 deletions

View file

@ -1,48 +1,7 @@
import React, {useEffect, useState} from 'react';
import React from 'react';
import clsx from 'clsx';
interface UseDynamicSVGImportOptions {
onCompleted?: (
name: string,
SvgIcon: React.FC<React.SVGProps<SVGSVGElement>> | undefined
) => void;
onError?: (err: Error) => void;
}
function useDynamicSVGImport(
name: string,
options: UseDynamicSVGImportOptions = {}
) {
const [loading, setLoading] = useState(false);
const [SvgComponent, setSvgComponent] = useState<React.FC<React.SVGProps<SVGSVGElement>> | null | undefined>(null);
const [error, setError] = useState<Error>();
const {onCompleted, onError} = options;
useEffect(() => {
setLoading(true);
const importIcon = async (): Promise<void> => {
try {
const SvgIcon: React.FC<React.SVGProps<SVGSVGElement>> = (
await import(`../assets/icons/${name}.svg`)
).ReactComponent;
setSvgComponent(() => SvgIcon);
onCompleted?.(name, SvgIcon);
} catch (err) {
if (err instanceof Error) {
onError?.(err);
setError(err);
} else {
throw err;
}
} finally {
setLoading(() => false);
}
};
importIcon();
}, [name, onCompleted, onError]);
return {error, loading, SvgComponent};
}
const icons: Record<string, {ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>}> = import.meta.glob('../assets/icons/*.svg', {eager: true});
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number;
@ -69,7 +28,7 @@ interface IconProps {
* - all strokes must be paths and _NOT_ outlined objects. Stroke width should be set to 1.5px
*/
const Icon: React.FC<IconProps> = ({name, size = 'md', colorClass = '', className = ''}) => {
const {SvgComponent} = useDynamicSVGImport(name);
const {ReactComponent: SvgComponent} = icons[`../assets/icons/${name}.svg`];
let styles = '';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View file

@ -1,6 +1,7 @@
import NiceModal, {NiceModalHocProps} from '@ebay/nice-modal-react';
import NiceModal from '@ebay/nice-modal-react';
import React, {createContext, useCallback, useEffect, useState} from 'react';
import {ScrollSectionProvider} from '../../hooks/useScrollSection';
import type {ModalComponent, ModalName} from './routing/modals';
export type RouteParams = {[key: string]: string}
@ -32,32 +33,7 @@ export type RoutingModalProps = {
params?: Record<string, string>
}
const modals: {[key: string]: () => Promise<{default: React.FC<NiceModalHocProps & RoutingModalProps>}>} = {
AddIntegrationModal: () => import('../settings/advanced/integrations/AddIntegrationModal'),
AddNewsletterModal: () => import('../settings/email/newsletters/AddNewsletterModal'),
AddRecommendationModal: () => import('../settings/site/recommendations/AddRecommendationModal'),
AmpModal: () => import('../settings/advanced/integrations/AmpModal'),
CustomIntegrationModal: () => import('../settings/advanced/integrations/CustomIntegrationModal'),
DesignAndThemeModal: () => import('../settings/site/DesignAndThemeModal'),
EditRecommendationModal: () => import('../settings/site/recommendations/EditRecommendationModal'),
FirstpromoterModal: () => import('../settings/advanced/integrations/FirstPromoterModal'),
HistoryModal: () => import('../settings/advanced/HistoryModal'),
InviteUserModal: () => import('../settings/general/InviteUserModal'),
NavigationModal: () => import('../settings/site/NavigationModal'),
NewsletterDetailModal: () => import('../settings/email/newsletters/NewsletterDetailModal'),
PinturaModal: () => import('../settings/advanced/integrations/PinturaModal'),
PortalModal: () => import('../settings/membership/portal/PortalModal'),
SlackModal: () => import('../settings/advanced/integrations/SlackModal'),
StripeConnectModal: () => import('../settings/membership/stripe/StripeConnectModal'),
TierDetailModal: () => import('../settings/membership/tiers/TierDetailModal'),
UnsplashModal: () => import('../settings/advanced/integrations/UnsplashModal'),
UserDetailModal: () => import('../settings/general/UserDetailModal'),
ZapierModal: () => import('../settings/advanced/integrations/ZapierModal'),
AnnouncementBarModal: () => import('../settings/site/AnnouncementBarModal'),
EmbedSignupFormModal: () => import('../settings/membership/embedSignup/EmbedSignupFormModal')
};
const modalPaths: {[key: string]: keyof typeof modals} = {
const modalPaths: {[key: string]: ModalName} = {
'design/edit/themes': 'DesignAndThemeModal',
'design/edit': 'DesignAndThemeModal',
'navigation/edit': 'NavigationModal',
@ -118,8 +94,8 @@ const handleNavigation = (currentRoute: string | undefined) => {
pathName,
changingModal: modalName && modalName !== currentModalName,
modal: (path && modalName) ?
modals[modalName]().then(({default: component}) => {
NiceModal.show(component, {pathName, params: matchRoute(pathName, path)});
import('./routing/modals').then(({default: modals}) => {
NiceModal.show(modals[modalName] as ModalComponent, {pathName, params: matchRoute(pathName, path)});
}) :
undefined
};
@ -147,7 +123,7 @@ const RoutingProvider: React.FC<RouteProviderProps> = ({externalNavigate, childr
useEffect(() => {
// Preload all the modals after initial render to avoid a delay when opening them
setTimeout(() => {
Object.values(modalPaths).forEach(modal => modals[modal]());
import('./routing/modals');
}, 1000);
}, []);

View file

@ -0,0 +1,55 @@
import type {NiceModalHocProps} from '@ebay/nice-modal-react';
import type {RoutingModalProps} from '../RoutingProvider';
import AddIntegrationModal from '../../settings/advanced/integrations/AddIntegrationModal';
import AddNewsletterModal from '../../settings/email/newsletters/AddNewsletterModal';
import AddRecommendationModal from '../../settings/site/recommendations/AddRecommendationModal';
import AmpModal from '../../settings/advanced/integrations/AmpModal';
import AnnouncementBarModal from '../../settings/site/AnnouncementBarModal';
import CustomIntegrationModal from '../../settings/advanced/integrations/CustomIntegrationModal';
import DesignAndThemeModal from '../../settings/site/DesignAndThemeModal';
import EditRecommendationModal from '../../settings/site/recommendations/EditRecommendationModal';
import EmbedSignupFormModal from '../../settings/membership/embedSignup/EmbedSignupFormModal';
import FirstpromoterModal from '../../settings/advanced/integrations/FirstPromoterModal';
import HistoryModal from '../../settings/advanced/HistoryModal';
import InviteUserModal from '../../settings/general/InviteUserModal';
import NavigationModal from '../../settings/site/NavigationModal';
import NewsletterDetailModal from '../../settings/email/newsletters/NewsletterDetailModal';
import PinturaModal from '../../settings/advanced/integrations/PinturaModal';
import PortalModal from '../../settings/membership/portal/PortalModal';
import SlackModal from '../../settings/advanced/integrations/SlackModal';
import StripeConnectModal from '../../settings/membership/stripe/StripeConnectModal';
import TierDetailModal from '../../settings/membership/tiers/TierDetailModal';
import UnsplashModal from '../../settings/advanced/integrations/UnsplashModal';
import UserDetailModal from '../../settings/general/UserDetailModal';
import ZapierModal from '../../settings/advanced/integrations/ZapierModal';
const modals = {
AddIntegrationModal,
AddNewsletterModal,
AddRecommendationModal,
AmpModal,
CustomIntegrationModal,
DesignAndThemeModal,
EditRecommendationModal,
FirstpromoterModal,
HistoryModal,
InviteUserModal,
NavigationModal,
NewsletterDetailModal,
PinturaModal,
PortalModal,
SlackModal,
StripeConnectModal,
TierDetailModal,
UnsplashModal,
UserDetailModal,
ZapierModal,
AnnouncementBarModal,
EmbedSignupFormModal
} satisfies {[key: string]: ModalComponent};
export default modals;
export type ModalName = keyof typeof modals;
export type ModalComponent = React.FC<NiceModalHocProps & RoutingModalProps>

View file

@ -6,11 +6,10 @@ import React, {useEffect, useState} from 'react';
import TextField from '../../../../admin-x-ds/global/form/TextField';
import useRouting from '../../../../hooks/useRouting';
import {HostLimitError, useLimiter} from '../../../../hooks/useLimiter';
import {RoutingModalProps} from '../../../providers/RoutingProvider';
import {useCreateIntegration} from '../../../../api/integrations';
interface AddIntegrationModalProps {}
const AddIntegrationModal: React.FC<AddIntegrationModalProps> = () => {
const AddIntegrationModal: React.FC<RoutingModalProps> = () => {
const modal = useModal();
const {updateRoute} = useRouting();
const [name, setName] = useState('');

View file

@ -9,14 +9,13 @@ import Toggle from '../../../../admin-x-ds/global/form/Toggle';
import useForm from '../../../../hooks/useForm';
import useRouting from '../../../../hooks/useRouting';
import {HostLimitError, useLimiter} from '../../../../hooks/useLimiter';
import {RoutingModalProps} from '../../../providers/RoutingProvider';
import {showToast} from '../../../../admin-x-ds/global/Toast';
import {toast} from 'react-hot-toast';
import {useAddNewsletter} from '../../../../api/newsletters';
import {useBrowseMembers} from '../../../../api/members';
interface AddNewsletterModalProps {}
const AddNewsletterModal: React.FC<AddNewsletterModalProps> = () => {
const AddNewsletterModal: React.FC<RoutingModalProps> = () => {
const modal = useModal();
const {updateRoute} = useRouting();

View file

@ -7,6 +7,7 @@ import URLTextField from '../../../../admin-x-ds/global/form/URLTextField';
import useForm from '../../../../hooks/useForm';
import useRouting from '../../../../hooks/useRouting';
import {EditOrAddRecommendation, useBrowseRecommendations} from '../../../../api/recommendations';
import {RoutingModalProps} from '../../../providers/RoutingProvider';
import {arePathsEqual, trimSearchAndHash} from '../../../../utils/url';
import {showToast} from '../../../../admin-x-ds/global/Toast';
import {toast} from 'react-hot-toast';
@ -18,7 +19,7 @@ interface AddRecommendationModalProps {
animate?: boolean
}
const AddRecommendationModal: React.FC<AddRecommendationModalProps> = ({recommendation, animate}) => {
const AddRecommendationModal: React.FC<RoutingModalProps & AddRecommendationModalProps> = ({recommendation, animate}) => {
const modal = useModal();
const {updateRoute} = useRouting();
const {query: queryOembed} = useGetOembed();

View file

@ -15,7 +15,7 @@ interface AddRecommendationModalProps {
const AddRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({recommendation, animate}) => {
const modal = useModal();
const {updateRoute} = useRouting();
const {updateRoute, route} = useRouting();
const {mutateAsync: addRecommendation} = useAddRecommendation();
const {formState, updateForm, handleSave, saveState, errors} = useForm({
@ -62,6 +62,7 @@ const AddRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({r
// Switch modal without changing the route, but pass along any changes that were already made
modal.remove();
NiceModal.show(AddRecommendationModal, {
pathName: route,
animate: false,
recommendation: {
...formState