Refactor AIPage and EQPage components to use lazy loading with PageSuspense for improved performance; update vite.config.ts to adjust chunking strategy and increase chunk size warning limit for better handling of large libraries.
This commit is contained in:
+19
-4
@@ -1,15 +1,16 @@
|
|||||||
|
import { lazy } from "react";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { Route, Switch, Router } from "wouter";
|
import { Route, Switch, Router } from "wouter";
|
||||||
import { useHashLocation } from "wouter/use-hash-location";
|
import { useHashLocation } from "wouter/use-hash-location";
|
||||||
import ErrorBoundary from "./components/ErrorBoundary";
|
import ErrorBoundary from "./components/ErrorBoundary";
|
||||||
|
import PageSuspense from "./components/PageSuspense";
|
||||||
import { ThemeProvider } from "./contexts/ThemeContext";
|
import { ThemeProvider } from "./contexts/ThemeContext";
|
||||||
import { DeviceProvider } from "./contexts/DeviceContext";
|
import { DeviceProvider } from "./contexts/DeviceContext";
|
||||||
import Home from "./pages/Home";
|
import Home from "./pages/Home";
|
||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
import AudioPage from "./pages/AudioPage";
|
import AudioPage from "./pages/AudioPage";
|
||||||
import EQPage from "./pages/EQPage";
|
|
||||||
import EffectsPage from "./pages/EffectsPage";
|
import EffectsPage from "./pages/EffectsPage";
|
||||||
import BluetoothPage from "./pages/BluetoothPage";
|
import BluetoothPage from "./pages/BluetoothPage";
|
||||||
import DspPage from "./pages/DspPage";
|
import DspPage from "./pages/DspPage";
|
||||||
@@ -18,14 +19,28 @@ import SystemPage from "./pages/SystemPage";
|
|||||||
import IOPage from "./pages/IOPage";
|
import IOPage from "./pages/IOPage";
|
||||||
import VUPage from "./pages/VUPage";
|
import VUPage from "./pages/VUPage";
|
||||||
import SelectPage from "./pages/SelectPage";
|
import SelectPage from "./pages/SelectPage";
|
||||||
import AIPage from "./pages/AIPage";
|
|
||||||
import CommunityPage from "./pages/CommunityPage";
|
import CommunityPage from "./pages/CommunityPage";
|
||||||
import { AIDrawerProvider } from "./contexts/AIDrawerContext";
|
import { AIDrawerProvider } from "./contexts/AIDrawerContext";
|
||||||
import DesktopAIShell from "./components/DesktopAIShell";
|
import DesktopAIShell from "./components/DesktopAIShell";
|
||||||
import { appShellColumnClass, appShellGutterClass } from "./lib/appShell";
|
import { appShellColumnClass, appShellGutterClass } from "./lib/appShell";
|
||||||
|
|
||||||
|
const EQPage = lazy(() => import("./pages/EQPage"));
|
||||||
|
const AIPage = lazy(() => import("./pages/AIPage"));
|
||||||
|
|
||||||
function AIPageRoute() {
|
function AIPageRoute() {
|
||||||
return <AIPage />;
|
return (
|
||||||
|
<PageSuspense>
|
||||||
|
<AIPage />
|
||||||
|
</PageSuspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EQPageRoute() {
|
||||||
|
return (
|
||||||
|
<PageSuspense>
|
||||||
|
<EQPage />
|
||||||
|
</PageSuspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AppRouter() {
|
function AppRouter() {
|
||||||
@@ -34,7 +49,7 @@ function AppRouter() {
|
|||||||
<Route path="/" component={Home} />
|
<Route path="/" component={Home} />
|
||||||
<Route path="/dashboard" component={Dashboard} />
|
<Route path="/dashboard" component={Dashboard} />
|
||||||
<Route path="/audio" component={AudioPage} />
|
<Route path="/audio" component={AudioPage} />
|
||||||
<Route path="/eq" component={EQPage} />
|
<Route path="/eq" component={EQPageRoute} />
|
||||||
<Route path="/effects" component={EffectsPage} />
|
<Route path="/effects" component={EffectsPage} />
|
||||||
<Route path="/bluetooth" component={BluetoothPage} />
|
<Route path="/bluetooth" component={BluetoothPage} />
|
||||||
<Route path="/dsp" component={DspPage} />
|
<Route path="/dsp" component={DspPage} />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import AIPage from "@/pages/AIPage";
|
import PageSuspense from "@/components/PageSuspense";
|
||||||
import { useAIDrawer } from "@/contexts/AIDrawerContext";
|
import { useAIDrawer } from "@/contexts/AIDrawerContext";
|
||||||
import { useIsLgUp } from "@/hooks/useIsLgUp";
|
import { useIsLgUp } from "@/hooks/useIsLgUp";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -7,10 +7,13 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
|
lazy,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
type RefObject,
|
type RefObject,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
|
const AIPage = lazy(() => import("@/pages/AIPage"));
|
||||||
|
|
||||||
function SplitDivider({
|
function SplitDivider({
|
||||||
containerRef,
|
containerRef,
|
||||||
onResize,
|
onResize,
|
||||||
@@ -114,7 +117,9 @@ export default function DesktopAIShell({ children }: { children: ReactNode }) {
|
|||||||
className="flex h-full min-h-0 min-w-0 flex-col overflow-hidden border-l border-white/[0.08] bg-[#0d1117]"
|
className="flex h-full min-h-0 min-w-0 flex-col overflow-hidden border-l border-white/[0.08] bg-[#0d1117]"
|
||||||
style={{ flex: `${splitPercent} 1 0%` }}
|
style={{ flex: `${splitPercent} 1 0%` }}
|
||||||
>
|
>
|
||||||
<AIPage variant="split" onClose={() => setOpen(false)} />
|
<PageSuspense>
|
||||||
|
<AIPage variant="split" onClose={() => setOpen(false)} />
|
||||||
|
</PageSuspense>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Suspense, type ReactNode } from "react";
|
||||||
|
|
||||||
|
function PageFallback() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-dvh items-center justify-center bg-[#0d1117]">
|
||||||
|
<span className="text-sm text-white/40">Loading…</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PageSuspense({ children }: { children: ReactNode }) {
|
||||||
|
return <Suspense fallback={<PageFallback />}>{children}</Suspense>;
|
||||||
|
}
|
||||||
+19
-18
@@ -378,6 +378,25 @@ export default function Home() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const volumePassthroughActive =
|
||||||
|
(deviceState?.dacVolumeDirect ?? 0) === 1 || (deviceState?.dacVolumeDirect ?? 0) === 2;
|
||||||
|
const volumeControlsLocked =
|
||||||
|
volumePassthroughActive &&
|
||||||
|
(deviceState?.output ?? 0) !== HEADPHONE_OUTPUT_INDEX;
|
||||||
|
|
||||||
|
const volumePassthroughHint =
|
||||||
|
homeText.volumePassthroughLockedHint ??
|
||||||
|
homeText.volumePassthroughLockedToast ??
|
||||||
|
"当前为音量直通模式,无法调节音量";
|
||||||
|
|
||||||
|
const guardVolumeChange = useCallback(
|
||||||
|
(action: () => void) => {
|
||||||
|
if (volumeControlsLocked) return;
|
||||||
|
action();
|
||||||
|
},
|
||||||
|
[volumeControlsLocked],
|
||||||
|
);
|
||||||
|
|
||||||
if (!isConnected) return <ConnectionPlaceholder />;
|
if (!isConnected) return <ConnectionPlaceholder />;
|
||||||
|
|
||||||
const ds = deviceState!;
|
const ds = deviceState!;
|
||||||
@@ -393,24 +412,6 @@ export default function Home() {
|
|||||||
// 换算为 volume 单位:0.5/1/2/3 dB -> 1/2/4/6
|
// 换算为 volume 单位:0.5/1/2/3 dB -> 1/2/4/6
|
||||||
const fineVolumeStep = [1, 2, 4, 6][ds.soundStep ?? 1] ?? 1;
|
const fineVolumeStep = [1, 2, 4, 6][ds.soundStep ?? 1] ?? 1;
|
||||||
|
|
||||||
const volumePassthroughActive =
|
|
||||||
(ds.dacVolumeDirect ?? 0) === 1 || (ds.dacVolumeDirect ?? 0) === 2;
|
|
||||||
const volumeControlsLocked =
|
|
||||||
volumePassthroughActive && ds.output !== HEADPHONE_OUTPUT_INDEX;
|
|
||||||
|
|
||||||
const volumePassthroughHint =
|
|
||||||
homeText.volumePassthroughLockedHint ??
|
|
||||||
homeText.volumePassthroughLockedToast ??
|
|
||||||
"当前为音量直通模式,无法调节音量";
|
|
||||||
|
|
||||||
const guardVolumeChange = useCallback(
|
|
||||||
(action: () => void) => {
|
|
||||||
if (volumeControlsLocked) return;
|
|
||||||
action();
|
|
||||||
},
|
|
||||||
[volumeControlsLocked],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Slider fill % (0-200 → 0-100%)
|
// Slider fill % (0-200 → 0-100%)
|
||||||
const fillPct = (localVol / 200) * 100;
|
const fillPct = (localVol / 200) * 100;
|
||||||
// Knob angle (map 0..200 -> -135..+135 degrees)
|
// Knob angle (map 0..200 -> -135..+135 degrees)
|
||||||
|
|||||||
+12
-20
@@ -208,21 +208,27 @@ export default defineConfig({
|
|||||||
manualChunks(id) {
|
manualChunks(id) {
|
||||||
if (!id.includes("node_modules")) return;
|
if (!id.includes("node_modules")) return;
|
||||||
|
|
||||||
// Keep core runtime dependencies stable for better long-term caching.
|
|
||||||
if (id.includes("node_modules/react/") || id.includes("node_modules/react-dom/")) {
|
if (id.includes("node_modules/react/") || id.includes("node_modules/react-dom/")) {
|
||||||
return "react-vendor";
|
return "react-vendor";
|
||||||
}
|
}
|
||||||
if (id.includes("node_modules/wouter/")) {
|
if (id.includes("node_modules/wouter/")) {
|
||||||
return "router-vendor";
|
return "router-vendor";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split large libraries into their own chunks to avoid a single huge entry bundle.
|
|
||||||
if (id.includes("node_modules/echarts/")) return "echarts-vendor";
|
if (id.includes("node_modules/echarts/")) return "echarts-vendor";
|
||||||
if (id.includes("node_modules/recharts/")) return "recharts-vendor";
|
if (id.includes("node_modules/recharts/")) return "recharts-vendor";
|
||||||
if (id.includes("node_modules/framer-motion/")) return "motion-vendor";
|
if (id.includes("node_modules/framer-motion/")) return "motion-vendor";
|
||||||
if (id.includes("node_modules/@radix-ui/")) return "radix-vendor";
|
if (id.includes("node_modules/@radix-ui/")) return "radix-vendor";
|
||||||
|
|
||||||
// Group frequently used small UI helpers.
|
// streamdown + shiki + mermaid (lazy-loaded with AIPage)
|
||||||
|
if (
|
||||||
|
id.includes("streamdown") ||
|
||||||
|
id.includes("@shikijs/") ||
|
||||||
|
id.includes("/shiki/") ||
|
||||||
|
id.includes("mermaid")
|
||||||
|
) {
|
||||||
|
return "ai-markdown-vendor";
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
id.includes("node_modules/lucide-react/") ||
|
id.includes("node_modules/lucide-react/") ||
|
||||||
id.includes("node_modules/class-variance-authority/") ||
|
id.includes("node_modules/class-variance-authority/") ||
|
||||||
@@ -231,25 +237,11 @@ export default defineConfig({
|
|||||||
) {
|
) {
|
||||||
return "ui-vendor";
|
return "ui-vendor";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to per-package vendor chunks.
|
|
||||||
const packageMatch = id.match(
|
|
||||||
/node_modules[\\/](?:\.pnpm[\\/][^\\/]+[\\/]node_modules[\\/])?(@?[^\\/]+(?:[\\/][^\\/]+)?)/,
|
|
||||||
);
|
|
||||||
const packagePath = packageMatch?.[1];
|
|
||||||
if (!packagePath) return "vendor";
|
|
||||||
|
|
||||||
const packageName = packagePath.startsWith("@")
|
|
||||||
? packagePath.split(/[\\/]/).slice(0, 2).join("_")
|
|
||||||
: packagePath.split(/[\\/]/)[0];
|
|
||||||
|
|
||||||
return `vendor-${packageName}`;
|
|
||||||
},
|
},
|
||||||
// Prevent Rollup from merging manual chunks back into larger bundles.
|
|
||||||
onlyExplicitManualChunks: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chunkSizeWarningLimit: 500,
|
// streamdown pulls full @shikijs/langs (~7 MB) + themes + mermaid; lazy-loaded with AIPage.
|
||||||
|
chunkSizeWarningLimit: 15000,
|
||||||
cssCodeSplit: true,
|
cssCodeSplit: true,
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
Reference in New Issue
Block a user