perf(v2): 8 optimizations — 24% smaller bundle, fewer re-renders
1. React.memo on WindowRenderer — prevents re-renders when parent state changes but no windows are affected 2. Coordinate display via direct DOM ref — no React state updates on mouse move (was triggering re-renders on every pixel) 3. useDeferredValue for sidebar vitals + player list — React prioritizes map interactions over stat text updates 4. Chat messages in ref — stores in useRef instead of useState, only bumps a version counter for re-render. Eliminates a new Map() allocation on every chat message. 5. Lazy-load 8 window components — InventoryWindow, CharacterWindow, RadarWindow, CombatStatsWindow, IssuesWindow, VitalSharingWindow, StatsWindow, CombatPickerWindow all loaded on first open. Main bundle dropped from 278KB to 211KB (24% reduction). 6. Preload critical assets — dereth.png, backpack icon, dungeon_tiles.json via <link rel="preload"> in index.html for instant map render. 7. Bundle splitting — React runtime extracted to separate 12KB chunk (cached independently). Window components split into 8 chunks. Total: 13 chunks vs previous 2. 8. Service worker — caches map images, icon sprites, and dungeon tiles. Icon images cached on first fetch. Repeat page loads serve from cache instantly. Auto-cleans old cache versions. Net result: - Initial load: 211KB main + 17KB CSS (was 278KB + 17KB) - React cached separately: 12KB - Windows load on demand: 1-15KB each - Dashboard with Recharts: 425KB (unchanged, still lazy) - Map images/icons: cached by service worker after first load Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
19d95a370f
commit
69678a9426
22 changed files with 264 additions and 84 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
|
||||
import { useWebSocket } from './useWebSocket';
|
||||
import { getLive, getCombatStats, getServerHealth, getTotalRares, getTotalKills } from '../api/endpoints';
|
||||
import type {
|
||||
|
|
@ -23,7 +23,10 @@ export function useLiveData(): DashboardState {
|
|||
const [totalRares, setTotalRares] = useState(0);
|
||||
const [totalKills, setTotalKills] = useState(0);
|
||||
const [recentRares, setRecentRares] = useState<RareMessage[]>([]);
|
||||
const [chatMessages, setChatMessages] = useState<Map<string, Array<{ text: string; color?: number; timestamp: string }>>>(new Map());
|
||||
// Chat messages stored in ref to avoid re-renders on every message.
|
||||
// A counter state triggers re-render only when needed.
|
||||
const chatMessagesRef = useRef(new Map<string, Array<{ text: string; color?: number; timestamp: string }>>());
|
||||
const [chatVersion, setChatVersion] = useState(0);
|
||||
const [nearbyObjects, setNearbyObjects] = useState<Map<string, any>>(new Map());
|
||||
const charsRef = useRef(characters);
|
||||
charsRef.current = characters;
|
||||
|
|
@ -77,13 +80,12 @@ export function useLiveData(): DashboardState {
|
|||
}
|
||||
} else if (msg.type === 'chat') {
|
||||
const m = msg as unknown as { character_name: string; text: string; color?: number; timestamp: string };
|
||||
setChatMessages(prev => {
|
||||
const next = new Map(prev);
|
||||
const arr = [...(next.get(m.character_name) ?? []), { text: m.text, color: m.color, timestamp: m.timestamp }];
|
||||
if (arr.length > 1000) arr.splice(0, arr.length - 1000);
|
||||
next.set(m.character_name, arr);
|
||||
return next;
|
||||
});
|
||||
const arr = chatMessagesRef.current.get(m.character_name) ?? [];
|
||||
arr.push({ text: m.text, color: m.color, timestamp: m.timestamp });
|
||||
if (arr.length > 1000) arr.splice(0, arr.length - 1000);
|
||||
chatMessagesRef.current.set(m.character_name, arr);
|
||||
// Bump version to notify open chat windows (batched by React)
|
||||
setChatVersion(v => v + 1);
|
||||
}
|
||||
}, [updateChar]);
|
||||
|
||||
|
|
@ -164,5 +166,8 @@ export function useLiveData(): DashboardState {
|
|||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const chatMessages = useMemo(() => chatMessagesRef.current, [chatVersion]);
|
||||
|
||||
return { characters, serverHealth, totalRares, totalKills, recentRares, chatMessages, nearbyObjects, socketRef };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue