perf: load the Settings dialog on demand; entry chunk 351.8 → 277.1 kB gzip
CI / Build & Quality Checks (pull_request) Canceled after 0s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
CI / Secret scan (gitleaks) (pull_request) Canceled after 0s
CI / Docker image build & smoke test (pull_request) Canceled after 0s
CI / Playwright smoke (e2e) (pull_request) Canceled after 0s
CI / Build & Quality Checks (pull_request) Canceled after 0s
CI / Trigger Desktop Build (pull_request) Canceled after 0s
CI / Secret scan (gitleaks) (pull_request) Canceled after 0s
CI / Docker image build & smoke test (pull_request) Canceled after 0s
CI / Playwright smoke (e2e) (pull_request) Canceled after 0s
Settings (every page, pickers, the denoise tester) was in the entry chunk though it only renders when opened. Load it lazily behind a focusable spinner (it opens inside a FocusTrap), and warm the chunk when the app goes idle. SettingsPages and the status localStorage keys move to tiny modules so callers don't pull the dialog back in. sanitize-html (+postcss, htmlparser2) and chroma-js get their own vendor chunks; with Settings gone the chunk graph had dropped them into the entry chunk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d7bc6d94bb
commit
8b6464c8b9
@@ -33,16 +33,9 @@ import { About } from './about';
|
|||||||
import { UseStateProvider } from '../../components/UseStateProvider';
|
import { UseStateProvider } from '../../components/UseStateProvider';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { LogoutDialog } from '../../components/LogoutDialog';
|
import { LogoutDialog } from '../../components/LogoutDialog';
|
||||||
|
import { SettingsPages } from './pages';
|
||||||
|
|
||||||
export enum SettingsPages {
|
export { SettingsPages };
|
||||||
GeneralPage,
|
|
||||||
AccountPage,
|
|
||||||
NotificationPage,
|
|
||||||
DevicesPage,
|
|
||||||
EmojisStickersPage,
|
|
||||||
DeveloperToolsPage,
|
|
||||||
AboutPage,
|
|
||||||
}
|
|
||||||
|
|
||||||
type SettingsMenuItem = {
|
type SettingsMenuItem = {
|
||||||
page: SettingsPages;
|
page: SettingsPages;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React, { lazy, Suspense } from 'react';
|
||||||
|
import { Box, Spinner, config } from 'folds';
|
||||||
|
import type { SettingsPages } from './pages';
|
||||||
|
|
||||||
|
// Settings is a large, rarely-open dialog (all its pages, pickers, the
|
||||||
|
// denoise tester…). Load it on first open instead of in the entry chunk, and
|
||||||
|
// warm the chunk once the app is idle so opening it rarely waits.
|
||||||
|
const loadSettings = () => import('./Settings');
|
||||||
|
const SettingsDialog = lazy(() => loadSettings().then((m) => ({ default: m.Settings })));
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const warm = () => {
|
||||||
|
loadSettings().catch(() => undefined);
|
||||||
|
};
|
||||||
|
if ('requestIdleCallback' in window) window.requestIdleCallback(warm, { timeout: 10_000 });
|
||||||
|
else setTimeout(warm, 5_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingsProps = {
|
||||||
|
initialPage?: SettingsPages;
|
||||||
|
requestClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Settings(props: SettingsProps) {
|
||||||
|
return (
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
// Settings opens inside a FocusTrap, which needs a tabbable node.
|
||||||
|
<Box
|
||||||
|
tabIndex={0}
|
||||||
|
role="status"
|
||||||
|
aria-label="Loading settings"
|
||||||
|
alignItems="Center"
|
||||||
|
justifyContent="Center"
|
||||||
|
style={{ padding: config.space.S700, outline: 'none' }}
|
||||||
|
>
|
||||||
|
<Spinner variant="Secondary" size="400" />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SettingsDialog {...props} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -67,6 +67,7 @@ import {
|
|||||||
makePresetId,
|
makePresetId,
|
||||||
normalizeLabel,
|
normalizeLabel,
|
||||||
} from '../../../utils/statusPresets';
|
} from '../../../utils/statusPresets';
|
||||||
|
import { STATUS_EXPIRY_KEY, STATUS_MSG_KEY } from '../../../utils/statusKeys';
|
||||||
|
|
||||||
type ProfileProps = {
|
type ProfileProps = {
|
||||||
profile: UserProfile;
|
profile: UserProfile;
|
||||||
@@ -365,8 +366,7 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const STATUS_EXPIRY_KEY = (id: string) => `lotus-status-expiry-${id}`;
|
export { STATUS_EXPIRY_KEY, STATUS_MSG_KEY };
|
||||||
export const STATUS_MSG_KEY = (id: string) => `lotus-status-msg-${id}`;
|
|
||||||
|
|
||||||
const CLEAR_AFTER_OPTIONS = [
|
const CLEAR_AFTER_OPTIONS = [
|
||||||
{ label: 'Never', value: '0' },
|
{ label: 'Never', value: '0' },
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './Settings';
|
export { SettingsPages } from './pages';
|
||||||
|
export { Settings } from './SettingsLazy';
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/** Settings dialog pages. Kept apart from Settings.tsx so callers can name a
|
||||||
|
* page without pulling the dialog into the entry chunk. */
|
||||||
|
export enum SettingsPages {
|
||||||
|
GeneralPage,
|
||||||
|
AccountPage,
|
||||||
|
NotificationPage,
|
||||||
|
DevicesPage,
|
||||||
|
EmojisStickersPage,
|
||||||
|
DeveloperToolsPage,
|
||||||
|
AboutPage,
|
||||||
|
}
|
||||||
@@ -63,7 +63,7 @@ import {
|
|||||||
migrateLocalMuteTimers,
|
migrateLocalMuteTimers,
|
||||||
unmuteRoom,
|
unmuteRoom,
|
||||||
} from '../../features/room-nav/muteTimers';
|
} from '../../features/room-nav/muteTimers';
|
||||||
import { STATUS_EXPIRY_KEY, STATUS_MSG_KEY } from '../../features/settings/account/Profile';
|
import { STATUS_EXPIRY_KEY, STATUS_MSG_KEY } from '../../utils/statusKeys';
|
||||||
import { noteLocalStatusWrite } from '../../utils/ownStatusSync';
|
import { noteLocalStatusWrite } from '../../utils/ownStatusSync';
|
||||||
import { setPresenceWithRetry } from '../../utils/presenceWrite';
|
import { setPresenceWithRetry } from '../../utils/presenceWrite';
|
||||||
import { useDeepLinkNavigate } from '../../hooks/useDeepLinkNavigate';
|
import { useDeepLinkNavigate } from '../../hooks/useDeepLinkNavigate';
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
/** localStorage keys for this device's copy of the status message and its expiry. */
|
||||||
|
export const STATUS_EXPIRY_KEY = (id: string) => `lotus-status-expiry-${id}`;
|
||||||
|
export const STATUS_MSG_KEY = (id: string) => `lotus-status-msg-${id}`;
|
||||||
@@ -251,6 +251,25 @@ const vendorChunks = (id) => {
|
|||||||
if (id.includes('node_modules/immer')) return 'immer';
|
if (id.includes('node_modules/immer')) return 'immer';
|
||||||
if (id.includes('node_modules/folds')) return 'folds';
|
if (id.includes('node_modules/folds')) return 'folds';
|
||||||
if (id.includes('node_modules/emojibase')) return 'emojibase';
|
if (id.includes('node_modules/emojibase')) return 'emojibase';
|
||||||
|
// HTML sanitising (sanitize-html pulls in postcss + htmlparser2) and colour
|
||||||
|
// maths are needed at startup but are big, stable libraries: keep them in
|
||||||
|
// their own cacheable chunks instead of wherever the chunk graph drops them
|
||||||
|
// (once Settings became lazy they landed in the entry chunk).
|
||||||
|
if (
|
||||||
|
id.includes('node_modules/sanitize-html') ||
|
||||||
|
id.includes('node_modules/postcss') ||
|
||||||
|
id.includes('node_modules/htmlparser2') ||
|
||||||
|
id.includes('node_modules/domhandler') ||
|
||||||
|
id.includes('node_modules/domutils') ||
|
||||||
|
id.includes('node_modules/dom-serializer') ||
|
||||||
|
id.includes('node_modules/entities') ||
|
||||||
|
id.includes('node_modules/parse-srcset') ||
|
||||||
|
id.includes('node_modules/nanoid') ||
|
||||||
|
id.includes('node_modules/picocolors') ||
|
||||||
|
id.includes('node_modules/source-map-js')
|
||||||
|
)
|
||||||
|
return 'sanitize-html';
|
||||||
|
if (id.includes('node_modules/chroma-js')) return 'chroma';
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|||||||
Reference in New Issue
Block a user