perf(web/icons): per-icon lucide imports via centralised alias

Route all lucide-react icon usage through a single src/icons.ts
re-export that imports each icon from its own per-icon module
(lucide-react/dist/esm/icons/<name>) instead of the barrel.

Bundle-size impact: none (29kB icons chunk unchanged — tree-shaking
was already effective with sideEffects:false). Dev-experience win:
Vite transforms 247 modules instead of 1848 because the dep
optimiser no longer pre-bundles the full lucide barrel — faster
cold start and HMR.

Ambient d.ts declares the wildcard module so TS accepts per-icon
imports; lucide ships .d.ts only for the barrel.

Seven icons were renamed upstream and still work through the barrel
via aliases (AlertTriangle -> triangle-alert, BarChart3 -> chart-column,
CheckCircle -> circle-check-big, Filter -> funnel, PlusCircle ->
circle-plus, Sliders -> sliders-vertical, UploadCloud -> cloud-upload,
Fingerprint -> fingerprint-pattern). Component call sites stay on
the legacy names; the renames live only in icons.ts.
This commit is contained in:
2026-04-24 18:41:33 -04:00
parent 52cbb01555
commit c973ded2fc
32 changed files with 150 additions and 30 deletions

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import { X, Download, AlertTriangle } from 'lucide-react';
import { X, Download, AlertTriangle } from '../icons';
import api from '../utils/api';
import { useEscapeKey } from '../hooks/useEscapeKey';
import { useFocusTrap } from '../hooks/useFocusTrap';

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Activity, ArrowLeft, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Crosshair, Fingerprint, Shield, Clock, Wifi, Lock, FileKey, Radio, Timer, Paperclip, Terminal, Package, FileText, Mail, AtSign } from 'lucide-react';
import { Activity, ArrowLeft, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Crosshair, Fingerprint, Shield, Clock, Wifi, Lock, FileKey, Radio, Timer, Paperclip, Terminal, Package, FileText, Mail, AtSign } from '../icons';
import api from '../utils/api';
import ArtifactDrawer from './ArtifactDrawer';
import MailDrawer from './MailDrawer';

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { Search, ChevronLeft, ChevronRight, Users } from 'lucide-react';
import { Search, ChevronLeft, ChevronRight, Users } from '../icons';
import api from '../utils/api';
import EmptyState from './EmptyState/EmptyState';
import { useFocusSearch } from '../hooks/useFocusSearch';

View File

@@ -3,7 +3,7 @@ import { useSearchParams, useNavigate } from 'react-router-dom';
import {
Archive, Search, ChevronLeft, ChevronRight, Filter, Key, Package, ChevronRight as ChevR,
Target,
} from 'lucide-react';
} from '../icons';
import api from '../utils/api';
import BountyInspector from './BountyInspector';
import EmptyState from './EmptyState/EmptyState';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { X, Key, Package, Copy, Send, Ban } from 'lucide-react';
import { X, Key, Package, Copy, Send, Ban } from '../icons';
import { useToast } from './Toasts/useToast';
interface BountyEntry {

View File

@@ -3,7 +3,7 @@ import {
LayoutDashboard, Server, Network, Terminal, Archive, Crosshair,
PlusCircle, Pause, RefreshCw, Download, HardDrive, Package, Settings,
SearchX, Keyboard, Webhook,
} from 'lucide-react';
} from '../../icons';
import EmptyState from '../EmptyState/EmptyState';
import './CommandPalette.css';

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import api from '../utils/api';
import { Settings, Users, Sliders, Trash2, UserPlus, Key, Save, Shield, AlertTriangle, Palette, Activity, Square, RefreshCw, Play } from 'lucide-react';
import { Settings, Users, Sliders, Trash2, UserPlus, Key, Save, Shield, AlertTriangle, Palette, Activity, Square, RefreshCw, Play } from '../icons';
import { useToast } from './Toasts/useToast';
import './Dashboard.css';
import './Config.css';

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import './Dashboard.css';
import { Shield, Users, Activity, Clock, Paperclip, Crosshair, Flame, Archive, ShieldOff, Server } from 'lucide-react';
import { Shield, Users, Activity, Clock, Paperclip, Crosshair, Flame, Archive, ShieldOff, Server } from '../icons';
import { parseEventBody } from '../utils/parseEventBody';
import ArtifactDrawer from './ArtifactDrawer';
import EmptyState from './EmptyState/EmptyState';

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Cpu, Database, Globe, Monitor, Network, PlusCircle, PowerOff,
RefreshCw, Server, Shield, Terminal,
} from 'lucide-react';
} from '../icons';
import api from '../utils/api';
import { ARCHETYPES as FALLBACK_ARCHETYPES, DEFAULT_SERVICES } from './MazeNET/data';
import { useToast } from './Toasts/useToast';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import type { LucideIcon } from 'lucide-react';
import type { LucideIcon } from '../../icons';
import './EmptyState.css';
interface CTA {

View File

@@ -4,7 +4,7 @@ import {
Menu, X, Search, Activity, LayoutDashboard, Terminal, Settings, LogOut,
Server, Archive, Package, Network, ChevronDown, ChevronRight, HardDrive,
ShieldAlert, Bell, Webhook,
} from 'lucide-react';
} from '../icons';
import { prefetchRoute } from '../routePrefetch';
import './Layout.css';

View File

@@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom';
import {
Terminal, Search, BarChart3, ChevronLeft, ChevronRight,
Play, Pause, Paperclip, Download, Radio, X as XIcon,
} from 'lucide-react';
} from '../icons';
import api from '../utils/api';
import { parseEventBody } from '../utils/parseEventBody';
import ArtifactDrawer from './ArtifactDrawer';

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import api from '../utils/api';
import './Login.css';
import { Activity } from 'lucide-react';
import { Activity } from '../icons';
interface LoginProps {
onLogin: (token: string) => void;

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import { X, Download, AlertTriangle, Paperclip } from 'lucide-react';
import { X, Download, AlertTriangle, Paperclip } from '../icons';
import api from '../utils/api';
import { useEscapeKey } from '../hooks/useEscapeKey';
import { useFocusTrap } from '../hooks/useFocusTrap';

View File

@@ -1,5 +1,5 @@
import React, { forwardRef, useMemo } from 'react';
import { RotateCcw, LayoutGrid, ZoomIn, ZoomOut } from 'lucide-react';
import { RotateCcw, LayoutGrid, ZoomIn, ZoomOut } from '../../icons';
import NetBox from './NetBox';
import NodeCard from './NodeCard';
import type { Net, MazeNode, Edge } from './types';

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import { ChevronRight } from 'lucide-react';
import { ChevronRight } from '../../icons';
export interface MenuItem {
label: string;

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
import {
ArrowLeft, ArrowRight, Crosshair, Globe, GitMerge, MousePointer2, Plus,
Server, Trash2, X, Shield,
} from 'lucide-react';
} from '../../icons';
import type { Net, MazeNode, Edge } from './types';
import { DEFAULT_SERVICES } from './data';

View File

@@ -4,7 +4,7 @@ import {
PanelRightOpen, PanelRightClose, PanelLeftOpen, PanelLeftClose,
Maximize2, Minimize2, RotateCcw, UploadCloud, ArrowLeft,
Plus, Trash2, Zap, Copy, Eye, ShieldAlert, GitMerge, Server,
} from 'lucide-react';
} from '../../icons';
import './MazeNET.css';
import axios from '../../utils/api';
import Palette from './Palette';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Globe, GitMerge, ShieldAlert } from 'lucide-react';
import { Globe, GitMerge, ShieldAlert } from '../../icons';
import type { Net } from './types';
import type { ResizeHandle } from './useMazeInteraction';

View File

@@ -2,7 +2,7 @@ import React from 'react';
import {
Server, Monitor, Shield, Database, Cpu, Globe, Users, HardDrive, Eye,
type LucideIcon,
} from 'lucide-react';
} from '../../icons';
import type { MazeNode } from './types';
import { DEFAULT_SERVICES } from './data';

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { GitMerge, ShieldAlert, Server, Monitor, Shield, Database, Cpu, Globe,
Terminal, Lock, Folder, HardDrive, Users, KeyRound,
Radio, Zap, Wifi, Circle, Mail, Phone, Activity, Box } from 'lucide-react';
Radio, Zap, Wifi, Circle, Mail, Phone, Activity, Box } from '../../icons';
import type { ServiceDef, Archetype, ServiceGroup } from './data';
import { SERVICE_GROUP_ORDER } from './data';
import type { PaletteDrag } from './useMazeInteraction';

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef } from 'react';
import { X, type LucideIcon } from 'lucide-react';
import { X, type LucideIcon } from '../../icons';
import { useEscapeKey } from '../../hooks/useEscapeKey';
import { useFocusTrap } from '../../hooks/useFocusTrap';
import './Modal.css';

View File

@@ -5,7 +5,7 @@ import './Dashboard.css';
import {
Upload, RefreshCw, RotateCcw, Package, AlertTriangle, CheckCircle,
Wifi, WifiOff, Server,
} from 'lucide-react';
} from '../icons';
interface HostRelease {
host_uuid: string;

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import { X, AlertTriangle } from 'lucide-react';
import { X, AlertTriangle } from '../icons';
import api from '../utils/api';
import { useEscapeKey } from '../hooks/useEscapeKey';
import { useFocusTrap } from '../hooks/useFocusTrap';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Keyboard } from 'lucide-react';
import { Keyboard } from '../../icons';
import Modal from '../Modal/Modal';
import './ShortcutsHelp.css';

View File

@@ -8,7 +8,7 @@ import './DeckyFleet.css';
import {
AlertTriangle, Check, Copy, HardDrive, PowerOff, RefreshCw, RotateCcw,
Server, Trash2, UserPlus, Wifi, WifiOff,
} from 'lucide-react';
} from '../icons';
interface SwarmHost {
uuid: string;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import {
CheckCircle, RefreshCw, Download, Upload, Pause, Play, AlertTriangle,
Info, Terminal, Activity, ShieldAlert,
} from 'lucide-react';
} from '../../icons';
import type { Toast } from './toast-context';
import './Toasts.css';

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { X, Server, Cpu, FileText, Sparkles, Check } from 'lucide-react';
import { X, Server, Cpu, FileText, Sparkles, Check } from '../../icons';
import api from '../../utils/api';
import { useEscapeKey } from '../../hooks/useEscapeKey';
import { useFocusTrap } from '../../hooks/useFocusTrap';

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Network, Plus, Power, Trash2, UploadCloud, RefreshCw, Skull } from 'lucide-react';
import { Network, Plus, Power, Trash2, UploadCloud, RefreshCw, Skull } from '../../icons';
import api from '../../utils/api';
import { clearLayout } from '../MazeNET/useMazeLayoutStore';
import CreateTopologyWizard from './CreateTopologyWizard';

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import {
Plus, Trash2, Pencil, Zap, AlertTriangle, Copy, X, Save,
Check, Webhook as WebhookIcon,
} from 'lucide-react';
} from '../icons';
import api from '../utils/api';
import { useToast } from './Toasts/useToast';
import './Dashboard.css';

104
decnet_web/src/icons.ts Normal file
View File

@@ -0,0 +1,104 @@
/* Centralised lucide re-export.
*
* Per-icon paths instead of the 'lucide-react' barrel: Vite's dep
* optimiser would otherwise pre-bundle the full barrel in dev
* (slower HMR), and prod tree-shaking is marginally tighter when
* each icon is its own module. Adding a new icon: add one line
* below, keep sorted. */
export type { LucideIcon } from 'lucide-react';
export { default as Activity } from 'lucide-react/dist/esm/icons/activity';
export { default as AlertTriangle } from 'lucide-react/dist/esm/icons/triangle-alert';
export { default as Archive } from 'lucide-react/dist/esm/icons/archive';
export { default as ArrowLeft } from 'lucide-react/dist/esm/icons/arrow-left';
export { default as ArrowRight } from 'lucide-react/dist/esm/icons/arrow-right';
export { default as AtSign } from 'lucide-react/dist/esm/icons/at-sign';
export { default as Ban } from 'lucide-react/dist/esm/icons/ban';
export { default as BarChart3 } from 'lucide-react/dist/esm/icons/chart-column';
export { default as Bell } from 'lucide-react/dist/esm/icons/bell';
export { default as Box } from 'lucide-react/dist/esm/icons/box';
export { default as Check } from 'lucide-react/dist/esm/icons/check';
export { default as CheckCircle } from 'lucide-react/dist/esm/icons/circle-check-big';
export { default as ChevronDown } from 'lucide-react/dist/esm/icons/chevron-down';
export { default as ChevronLeft } from 'lucide-react/dist/esm/icons/chevron-left';
export { default as ChevronRight } from 'lucide-react/dist/esm/icons/chevron-right';
export { default as ChevronUp } from 'lucide-react/dist/esm/icons/chevron-up';
export { default as Circle } from 'lucide-react/dist/esm/icons/circle';
export { default as Clock } from 'lucide-react/dist/esm/icons/clock';
export { default as Copy } from 'lucide-react/dist/esm/icons/copy';
export { default as Cpu } from 'lucide-react/dist/esm/icons/cpu';
export { default as Crosshair } from 'lucide-react/dist/esm/icons/crosshair';
export { default as Database } from 'lucide-react/dist/esm/icons/database';
export { default as Download } from 'lucide-react/dist/esm/icons/download';
export { default as Eye } from 'lucide-react/dist/esm/icons/eye';
export { default as FileKey } from 'lucide-react/dist/esm/icons/file-key';
export { default as FileText } from 'lucide-react/dist/esm/icons/file-text';
export { default as Filter } from 'lucide-react/dist/esm/icons/funnel';
export { default as Fingerprint } from 'lucide-react/dist/esm/icons/fingerprint-pattern';
export { default as Flame } from 'lucide-react/dist/esm/icons/flame';
export { default as Folder } from 'lucide-react/dist/esm/icons/folder';
export { default as GitMerge } from 'lucide-react/dist/esm/icons/git-merge';
export { default as Globe } from 'lucide-react/dist/esm/icons/globe';
export { default as HardDrive } from 'lucide-react/dist/esm/icons/hard-drive';
export { default as Info } from 'lucide-react/dist/esm/icons/info';
export { default as Key } from 'lucide-react/dist/esm/icons/key';
export { default as KeyRound } from 'lucide-react/dist/esm/icons/key-round';
export { default as Keyboard } from 'lucide-react/dist/esm/icons/keyboard';
export { default as LayoutDashboard } from 'lucide-react/dist/esm/icons/layout-dashboard';
export { default as LayoutGrid } from 'lucide-react/dist/esm/icons/layout-grid';
export { default as Lock } from 'lucide-react/dist/esm/icons/lock';
export { default as LogOut } from 'lucide-react/dist/esm/icons/log-out';
export { default as Mail } from 'lucide-react/dist/esm/icons/mail';
export { default as Maximize2 } from 'lucide-react/dist/esm/icons/maximize-2';
export { default as Menu } from 'lucide-react/dist/esm/icons/menu';
export { default as Minimize2 } from 'lucide-react/dist/esm/icons/minimize-2';
export { default as Monitor } from 'lucide-react/dist/esm/icons/monitor';
export { default as MousePointer2 } from 'lucide-react/dist/esm/icons/mouse-pointer-2';
export { default as Network } from 'lucide-react/dist/esm/icons/network';
export { default as Package } from 'lucide-react/dist/esm/icons/package';
export { default as Palette } from 'lucide-react/dist/esm/icons/palette';
export { default as PanelLeftClose } from 'lucide-react/dist/esm/icons/panel-left-close';
export { default as PanelLeftOpen } from 'lucide-react/dist/esm/icons/panel-left-open';
export { default as PanelRightClose } from 'lucide-react/dist/esm/icons/panel-right-close';
export { default as PanelRightOpen } from 'lucide-react/dist/esm/icons/panel-right-open';
export { default as Paperclip } from 'lucide-react/dist/esm/icons/paperclip';
export { default as Pause } from 'lucide-react/dist/esm/icons/pause';
export { default as Pencil } from 'lucide-react/dist/esm/icons/pencil';
export { default as Phone } from 'lucide-react/dist/esm/icons/phone';
export { default as Play } from 'lucide-react/dist/esm/icons/play';
export { default as Plus } from 'lucide-react/dist/esm/icons/plus';
export { default as PlusCircle } from 'lucide-react/dist/esm/icons/circle-plus';
export { default as Power } from 'lucide-react/dist/esm/icons/power';
export { default as PowerOff } from 'lucide-react/dist/esm/icons/power-off';
export { default as Radio } from 'lucide-react/dist/esm/icons/radio';
export { default as RefreshCw } from 'lucide-react/dist/esm/icons/refresh-cw';
export { default as RotateCcw } from 'lucide-react/dist/esm/icons/rotate-ccw';
export { default as Save } from 'lucide-react/dist/esm/icons/save';
export { default as Search } from 'lucide-react/dist/esm/icons/search';
export { default as SearchX } from 'lucide-react/dist/esm/icons/search-x';
export { default as Send } from 'lucide-react/dist/esm/icons/send';
export { default as Server } from 'lucide-react/dist/esm/icons/server';
export { default as Settings } from 'lucide-react/dist/esm/icons/settings';
export { default as Shield } from 'lucide-react/dist/esm/icons/shield';
export { default as ShieldAlert } from 'lucide-react/dist/esm/icons/shield-alert';
export { default as ShieldOff } from 'lucide-react/dist/esm/icons/shield-off';
export { default as Skull } from 'lucide-react/dist/esm/icons/skull';
export { default as Sliders } from 'lucide-react/dist/esm/icons/sliders-vertical';
export { default as Sparkles } from 'lucide-react/dist/esm/icons/sparkles';
export { default as Square } from 'lucide-react/dist/esm/icons/square';
export { default as Target } from 'lucide-react/dist/esm/icons/target';
export { default as Terminal } from 'lucide-react/dist/esm/icons/terminal';
export { default as Timer } from 'lucide-react/dist/esm/icons/timer';
export { default as Trash2 } from 'lucide-react/dist/esm/icons/trash-2';
export { default as Upload } from 'lucide-react/dist/esm/icons/upload';
export { default as UploadCloud } from 'lucide-react/dist/esm/icons/cloud-upload';
export { default as UserPlus } from 'lucide-react/dist/esm/icons/user-plus';
export { default as Users } from 'lucide-react/dist/esm/icons/users';
export { default as Webhook } from 'lucide-react/dist/esm/icons/webhook';
export { default as Wifi } from 'lucide-react/dist/esm/icons/wifi';
export { default as WifiOff } from 'lucide-react/dist/esm/icons/wifi-off';
export { default as X } from 'lucide-react/dist/esm/icons/x';
export { default as Zap } from 'lucide-react/dist/esm/icons/zap';
export { default as ZoomIn } from 'lucide-react/dist/esm/icons/zoom-in';
export { default as ZoomOut } from 'lucide-react/dist/esm/icons/zoom-out';

16
decnet_web/src/lucide-icons.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
/* Ambient typings for lucide-react's per-icon module paths.
*
* lucide-react ships .d.ts only for the barrel entry point; the
* per-icon files (dist/esm/icons/<name>.js) have no sibling .d.ts.
* We import each icon from its own file to keep the dep-optimiser
* from pre-bundling the whole barrel, so the compiler needs a
* declaration that covers the wildcard path.
*
* Every icon exposes the same default-exported component shape,
* so one module wildcard is enough. */
declare module 'lucide-react/dist/esm/icons/*' {
import type { LucideIcon } from 'lucide-react';
const icon: LucideIcon;
export default icon;
}