Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
682 changes: 21 additions & 661 deletions LICENSE

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ cd web
pnpm install
pnpm dev
```
Visit `http://localhost:3000` to enter the Cyan Universe!
Visit `http://localhost:2000` to enter the Cyan Universe!

## Project Structure

Expand Down Expand Up @@ -119,7 +119,7 @@ Asagity/
Asagity is currently in its early development phase. PRs, issues, and feature requests are highly welcome!

## License
This project is licensed under the [AGPL-3.0 License](LICENSE).
This project is licensed under the [MIT License](LICENSE).

---
<div align="center">
Expand Down
2 changes: 1 addition & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Asagity/
Asagity 目前正处于火热的早期开发阶段。无论你是擅长 Go 的硬核极客,还是精通 React 的 UI 魔法师,我们都极其欢迎你的 PR 和 Issue!

## 📜 开源协议
本项目采用 [AGPL-3.0 协议](LICENSE) 开源。
本项目采用 [MIT 协议](LICENSE) 开源。

---
<div align="center">
Expand Down
48 changes: 48 additions & 0 deletions docs/roadmap/ReduceMemUse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 前端内存优化计划

## 一、构建期内存优化 (Build-time)

| # | 措施 | 说明 | 风险 |
|---|------|------|------|
| 1 | `experimental.webpackMemoryOptimizations: true` | Next.js 15+ 内置,降低构建峰值内存,代价是编译时间略增 | 低 |
| 2 | `experimental.preloadEntriesOnStart: false` | 禁用启动时预加载所有页面 JS 模块,改为按需加载 | 低 |
| 3 | 关闭生产 Source Maps | `productionBrowserSourceMaps: false` + `enablePrerenderSourceMaps: false`,减少构建期内存分配 | 低 |
| 4 | `pnpm analyze` 审计依赖 | 用 Bundle Analyzer 找出未使用/过重的包(如 `echarts` 全量引入、`@dnd-kit` 未使用部分) | 无 |

## 二、运行期内存优化 (Runtime)

| # | 措施 | 说明 | 风险 |
|---|------|------|------|
| 5 | 路由级代码分割 | 当前 `page.tsx` 已用 `next/dynamic`,但 `MainLayout` 内的子组件(`ContextMenu`、`NetworkStatus`、`SplashScreen`)未懒加载 — 改为 `dynamic` 导入 | 低 |
| 6 | Zustand store 瘦身 | `useMusicStore` 持久化整个 playlist(含 ArrayBuffer 音频数据),应 `partialize` 排除大数据字段;`useFreeWindowStore` 的 `currentPost`/`currentUser` 在关闭窗口后不清空 — 添加 `close()` 时重置 | 中 |
| 7 | React.memo 防级联渲染 | `PostItem`、`Icon`、`Sidebar` 等高频渲染组件用 `React.memo` 包裹,避免父组件 state 变化导致整棵树重绘 | 低 |
| 8 | 事件监听器清理 | `Welcome.tsx` 的 `mousemove`、`MainLayout.tsx` 的 `mousedown` 已有 cleanup,但需检查 `system.ts` 的 `setInterval` 心跳在组件卸载后是否仍在运行 — 应在 `MainLayout` 的 `useEffect` return 中调用 `stopHeartbeat` | 中 |
| 9 | AbortController 取消请求 | `fetchHostInfoWithTimeout` 已有 AbortController,但 `fetchMe`、`refreshAccessToken` 等未使用 — 统一添加信号取消 | 中 |

## 三、Bundle 体积瘦身

| # | 措施 | 说明 | 风险 |
|---|------|------|------|
| 10 | ECharts 按需引入 | 当前 `import * as echarts from "echarts"` 全量引入约 800KB,改为 `echarts/core` + 按需注册组件 | 中 |
| 11 | `@fluentui/react-icons` tree-shaking | 当前 100+ 图标全量注册到 `iconMap`,每个图标独立导入会增加 module 数量 — 评估是否改用 `@iconify/react` 按需加载 | 中 |
| 12 | 移除未使用依赖 | `package.json` 中 `@dnd-kit/core`、`@dnd-kit/sortable`、`@dnd-kit/utilities`、`lucide-react`、`@iconify/react` 可能未实际使用 — `pnpm analyze` 后清理 | 低 |

## 四、监控与持续优化

| # | 措施 | 说明 |
|---|------|------|
| 13 | 添加 `bundlesize` 配置 | 在 `package.json` 设置 JS/CSS 大小预算(如 JS < 300KB, CSS < 50KB) |
| 14 | 生产构建内存监控 | `next build --experimental-debug-memory-usage` 记录构建期 heap 使用 |
| 15 | Chrome DevTools heap snapshot | 定期在生产环境用 `--inspect` 抓取 heap profile,定位内存泄漏 |

## 五、执行优先级

```
Phase 1 (低风险立即做): #1, #2, #3, #5, #12
Phase 2 (需测试验证): #6, #7, #8, #9
Phase 3 (需重构): #4, #10, #11
Phase 4 (持续): #13, #14, #15
```

---
*Created by CyaniAgent - 2026-07-06*
2 changes: 1 addition & 1 deletion web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

```bash
pnpm install
pnpm dev # http://localhost:3000
pnpm dev # http://localhost:2000
```

## Commands
Expand Down
11 changes: 10 additions & 1 deletion web/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === "true",
});

const isDev = process.env.NODE_ENV !== "production";

const securityHeaders = [
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""}`,
"style-src 'self' 'unsafe-inline'",
"font-src 'self' data:",
"img-src 'self' data: blob: https:",
Expand Down Expand Up @@ -47,6 +49,13 @@ const securityHeaders = [
];

const nextConfig: NextConfig = {
experimental: {
webpackMemoryOptimizations: true,
preloadEntriesOnStart: false,
},

productionBrowserSourceMaps: false,

async rewrites() {
return [
{
Expand Down
10 changes: 3 additions & 7 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"dev": "next dev --turbopack --port 2000 --hostname ::",
"build": "next build",
"start": "next start",
"start": "next start --port 2000 --hostname ::",
"lint": "next lint --fix",
"lint:check": "next lint",
"typecheck": "tsc --noEmit",
Expand All @@ -16,19 +16,14 @@
"test:e2e:ui": "playwright test --ui"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fluentui/react-icons": "^2.0.316",
"@iconify/react": "^6.0.2",
"@tanstack/react-query": "^5.101.2",
"clsx": "^2.1.1",
"date-fns": "^4.4.0",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"framer-motion": "^12.42.2",
"lrc-kit": "^1.2.1",
"lucide-react": "^1.22.0",
"mfm-js": "^0.26.0",
"music-metadata": "^11.13.0",
"next": "16.2.9",
Expand All @@ -37,6 +32,7 @@
"react-dom": "19.2.4",
"react-resizable-panels": "^4.12.0",
"react-rnd": "^10.5.3",
"react-window": "^2.2.7",
"recharts": "^3.9.1",
"tailwind-merge": "^3.6.0",
"ua-parser-js": "^2.0.10",
Expand Down
100 changes: 14 additions & 86 deletions web/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions web/pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
allowBuilds:
'@parcel/watcher': true
'@swc/core': true
sharp: true
ignoredBuiltDependencies:
- sharp
- unrs-resolver
56 changes: 56 additions & 0 deletions web/src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import dynamic from "next/dynamic";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useI18n } from "@/components/providers/I18nProvider";

const MainLayout = dynamic(
() => import("@/components/layout/MainLayout").then((m) => m.MainLayout),
{ ssr: false }
);

export default function NotFound() {
const { t } = useI18n();
const router = useRouter();

return (
<MainLayout notFound>
<div className="flex-1 flex items-center justify-center p-8">
<div className="flex items-center gap-10 max-w-xl">
<Image
src="/images/system/404.png"
alt="404"
width={200}
height={200}
className="w-[200px] h-[200px] object-contain shrink-0"
priority
/>
<div className="flex flex-col gap-3">
<h1 className="text-7xl font-bold text-gray-900 dark:text-white tracking-tight">
404
</h1>
<p className="text-base text-gray-500 dark:text-gray-400 leading-relaxed whitespace-pre-line">
{t("notFound.message")}
</p>
<div className="flex items-center gap-3 mt-2">
<button
onClick={() => router.back()}
className="px-4 py-2 text-sm font-medium text-cyan-600 dark:text-cyan-400 bg-cyan-500/10 hover:bg-cyan-500/20 rounded-xl transition-colors"
>
{t("notFound.back")}
</button>
<Link
href="/"
className="px-4 py-2 text-sm font-medium text-gray-600 dark:text-gray-300 bg-white/50 dark:bg-white/5 hover:bg-white/80 dark:hover:bg-white/10 rounded-xl transition-colors"
>
{t("notFound.goHome")}
</Link>
</div>
</div>
</div>
</div>
</MainLayout>
);
}
Loading