perf: lazy-load seasonal overlays as their own chunks (#167)
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
All 11 seasonal overlays (particles + vanilla-extract keyframes) were in the main JS and CSS for everyone year-round. Each is now a React.lazy chunk fetched only when that season is active (auto date or override) or previewed in Settings → Appearance; the schedule/override decision stays in the main bundle. check-bundle-size: largest chunk 332.4 kB → 320.1 kB gzip; main CSS 68.4 kB → 56.5 kB; total gzip 1551.6 kB → 1561.3 kB (per-chunk overhead, only paid when a season is on). Verified headless: no theme chunk requested at startup with 'auto' out of season; the Settings grid fetches the 11 previews; picking Halloween renders the full-screen overlay (20 particle nodes). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -1,21 +1,49 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { Suspense, useEffect, useMemo, useState } from 'react';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
import { useReducedMotion } from '../../hooks/useReducedMotion';
|
||||
import { zIndices } from '../../styles/zIndex';
|
||||
import { SeasonTheme } from './types';
|
||||
import { resolveSeasonTheme } from './seasonSchedule';
|
||||
import { HalloweenOverlay } from './themes/Halloween';
|
||||
import { ChristmasOverlay } from './themes/Christmas';
|
||||
import { NewYearOverlay } from './themes/NewYear';
|
||||
import { AutumnOverlay } from './themes/Autumn';
|
||||
import { AprilFoolsOverlay } from './themes/AprilFools';
|
||||
import { LunarNewYearOverlay } from './themes/LunarNewYear';
|
||||
import { ValentinesOverlay } from './themes/Valentines';
|
||||
import { StPatricksOverlay } from './themes/StPatricks';
|
||||
import { EarthDayOverlay } from './themes/EarthDay';
|
||||
import { DeepSpaceOverlay } from './themes/DeepSpace';
|
||||
import { ArcadeOverlay } from './themes/Arcade';
|
||||
|
||||
// [Gitea #167] Each overlay (its particles + keyframes) is its own chunk,
|
||||
// fetched only when that season is active or previewed in Settings — the
|
||||
// date/override decision stays in the main bundle.
|
||||
const lazyOverlay = (load: () => Promise<{ default: React.ComponentType<{ reduced: boolean }> }>) =>
|
||||
React.lazy(load);
|
||||
const HalloweenOverlay = lazyOverlay(() =>
|
||||
import('./themes/Halloween').then((m) => ({ default: m.HalloweenOverlay })),
|
||||
);
|
||||
const ChristmasOverlay = lazyOverlay(() =>
|
||||
import('./themes/Christmas').then((m) => ({ default: m.ChristmasOverlay })),
|
||||
);
|
||||
const NewYearOverlay = lazyOverlay(() =>
|
||||
import('./themes/NewYear').then((m) => ({ default: m.NewYearOverlay })),
|
||||
);
|
||||
const AutumnOverlay = lazyOverlay(() =>
|
||||
import('./themes/Autumn').then((m) => ({ default: m.AutumnOverlay })),
|
||||
);
|
||||
const AprilFoolsOverlay = lazyOverlay(() =>
|
||||
import('./themes/AprilFools').then((m) => ({ default: m.AprilFoolsOverlay })),
|
||||
);
|
||||
const LunarNewYearOverlay = lazyOverlay(() =>
|
||||
import('./themes/LunarNewYear').then((m) => ({ default: m.LunarNewYearOverlay })),
|
||||
);
|
||||
const ValentinesOverlay = lazyOverlay(() =>
|
||||
import('./themes/Valentines').then((m) => ({ default: m.ValentinesOverlay })),
|
||||
);
|
||||
const StPatricksOverlay = lazyOverlay(() =>
|
||||
import('./themes/StPatricks').then((m) => ({ default: m.StPatricksOverlay })),
|
||||
);
|
||||
const EarthDayOverlay = lazyOverlay(() =>
|
||||
import('./themes/EarthDay').then((m) => ({ default: m.EarthDayOverlay })),
|
||||
);
|
||||
const DeepSpaceOverlay = lazyOverlay(() =>
|
||||
import('./themes/DeepSpace').then((m) => ({ default: m.DeepSpaceOverlay })),
|
||||
);
|
||||
const ArcadeOverlay = lazyOverlay(() =>
|
||||
import('./themes/Arcade').then((m) => ({ default: m.ArcadeOverlay })),
|
||||
);
|
||||
|
||||
// SeasonTheme + the date-window logic now live in leaf modules (single source
|
||||
// of truth, shared with the settings UI). Re-exported here for existing
|
||||
@@ -69,7 +97,7 @@ function SeasonalOverlay({ theme, reduced }: { theme: SeasonTheme; reduced: bool
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{buildOverlayContent(theme, reduced)}
|
||||
<Suspense fallback={null}>{buildOverlayContent(theme, reduced)}</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -94,7 +122,7 @@ export function SeasonalPreview({ theme }: { theme: SeasonTheme }) {
|
||||
containerType: 'inline-size',
|
||||
}}
|
||||
>
|
||||
{buildOverlayContent(theme, true)}
|
||||
<Suspense fallback={null}>{buildOverlayContent(theme, true)}</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user