perf+fix: sprint B — async I/O, before-quit, immutable getState, lucide tree-shake
#2 atomicWrite spin-loop → async setTimeout. Раньше при retry на EBUSY/EPERM (антивирус, OneDrive) main process замораживался на 50/200/800ms × до 3 итераций ≈ секунда залипания UI. Сейчас async sleep — event-loop живёт. Сохранён atomicWriteSync для flushNow (вызывается из before-quit когда event-loop уже умирает). Аналогичный фикс в games/steam-launch-options.ts. #5 before-quit теперь дожидается stopGamesRegistry через e.preventDefault() + app.exit(0). Раньше GSI HTTP server не успевал closeAllConnections до exit, и следующий запуск получал EADDRINUSE на port 4701 (TIME_WAIT) — GSI молча не работал. #10 IPC.getState возвращает поверхностную копию settings вместо мутации кэша. Раньше startWithWindows писалось напрямую в state.settings, разъезжаясь с persisted-disk-значением до следующего mutation. #19 lib/icon.tsx: `import * as Lucide` (wildcard, ~500KB в bundle, 1500+ иконок) → explicit named imports + ICON_MAP. В bundle остаются только 18 ICON_CHOICES.
This commit is contained in:
@@ -1,11 +1,51 @@
|
||||
import * as Lucide from 'lucide-react'
|
||||
// Explicit-named imports — НЕ wildcard. Wildcard `* as Lucide` ломает
|
||||
// tree-shaking: в bundle попадает вся библиотека (~500KB minified, 1500+
|
||||
// иконок). Сейчас в bundle только 18 ICON_CHOICES.
|
||||
import {
|
||||
Activity,
|
||||
Dumbbell,
|
||||
StretchHorizontal,
|
||||
PersonStanding,
|
||||
Heart,
|
||||
Footprints,
|
||||
Hand,
|
||||
Eye,
|
||||
Brain,
|
||||
Bike,
|
||||
Waves,
|
||||
Wind,
|
||||
Sun,
|
||||
Coffee,
|
||||
Apple,
|
||||
GlassWater,
|
||||
BookOpen,
|
||||
Sparkles
|
||||
} from 'lucide-react'
|
||||
import type { LucideProps } from 'lucide-react'
|
||||
import { ICON_CHOICES, type IconName } from './icon-choices'
|
||||
|
||||
// Re-export для обратной совместимости с импортёрами icon.tsx.
|
||||
export { ICON_CHOICES, type IconName }
|
||||
|
||||
const ICON_SET = new Set<string>(ICON_CHOICES)
|
||||
const ICON_MAP: Record<IconName, React.ComponentType<LucideProps>> = {
|
||||
Activity,
|
||||
Dumbbell,
|
||||
StretchHorizontal,
|
||||
PersonStanding,
|
||||
Heart,
|
||||
Footprints,
|
||||
Hand,
|
||||
Eye,
|
||||
Brain,
|
||||
Bike,
|
||||
Waves,
|
||||
Wind,
|
||||
Sun,
|
||||
Coffee,
|
||||
Apple,
|
||||
GlassWater,
|
||||
BookOpen,
|
||||
Sparkles
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a Lucide icon by name. Restricted to the curated ICON_CHOICES set —
|
||||
@@ -17,15 +57,12 @@ export function Icon({
|
||||
name,
|
||||
...props
|
||||
}: { name: string } & LucideProps): JSX.Element {
|
||||
if (!ICON_SET.has(name)) {
|
||||
const Cmp = ICON_MAP[name as IconName]
|
||||
if (!Cmp) {
|
||||
if (import.meta.env.DEV) {
|
||||
console.warn(`[Icon] unknown icon name "${name}" — falling back`)
|
||||
}
|
||||
return <Lucide.Activity {...props} />
|
||||
return <Activity {...props} />
|
||||
}
|
||||
const Cmp = (
|
||||
Lucide as unknown as Record<string, React.ComponentType<LucideProps>>
|
||||
)[name]
|
||||
if (!Cmp) return <Lucide.Activity {...props} />
|
||||
return <Cmp {...props} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user