Files
chatroom/.hermes/plans/chatroom-frontend-optimization.md
T
pllx e50502d8f6 前端加载优化:代码分割 + 按需懒加载
chat.js 首屏 308KB → 100KB(↓68%)
44 个重型模块改为 Vite 动态 import()
Alpine 组件通过 $watch 监听实现真懒加载
新增 createLazyAlpineComponent 工具 + Proxy has 陷阱修复
补充 userCardComponent 全部 28 个属性默认值
vendor 依赖独立分包(108KB)
生产环境关闭 sourcemap
2026-04-28 09:38:18 +08:00

312 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🚀 聊天室项目 — 前端加载优化详细方案
> **项目路径:** `/Users/pllx/Web/Herd/chatroom`
> **当前构建输出:** `public/build/` 共 664KB
> **核心问题:** `chat.js` 单文件 **308KB**,所有功能模块一次性加载
---
## 一、现状分析
### 当前构建产物
| 文件 | 大小 | 说明 |
|------|------|------|
| `chat-*.js` | **308 KB** | 🚨 **主聊天室脚本** — 全部模块一次性加载 |
| `app-*.js` (后台) | 24 KB | ✅ 合理 |
| `effects-*.js` | 5.6 KB | ✅ 已代码分割(特效按需分包) |
| 其他入口 | 1.5~5.6 KB | ✅ 正常 |
| **图片资源** | 87 KB | ✅ 12张背景图已很小 |
| **CSS** | 532行 | ✅ 轻量 |
### 根因分析
`resources/js/chat.js` → 导入 `bootstrap.js` + `chat-room.js` + `context.js`
`chat-room.js`**982行****同步导入**了所有 50+ 个子模块:
| 类别 | 模块 | 行数 |
|------|------|------|
| 🎮 **游戏(5,483行)** | gomoku-panel(1001), horse-race-panel(552), baccarat-panel(448), baccarat-loss-cover(408), slot-machine(347), lottery-panel(343), fishing(625), fortune-panel(173), game-hall(586), baccarat-events(129)等 | **5,483行** |
| 🛒 **商店/银行** | shop-controls(903), compact-shop-panel(586), bank-modal(477) | **1,966行** |
| 💒 **婚姻系统** | marriage-modals(920), marriage-status(662) | **1,582行** |
| 👤 **用户资料** | user-card(855), profile-controls(715), friend-panel(447) | **2,017行** |
| 👑 **VIP/红包** | vip-controls(477), red-packet-panel(679) | **1,156行** |
| 🛠️ **核心功能** | message-renderer(474), chat-state(223), composer(231), preferences-status(855)等 | **~3,000行** |
**核心问题:** 用户进入聊天室,等看到 30KB 核心界面之前,要先下载并解析 **308KB 的所有游戏代码**——哪怕他从没点开过百家乐、五子棋或赛马。
---
## 二、优化方案
### 🔥 方案 A:游戏模块动态导入(收益最大)
**目标:** 将 5 个主要游戏的 UI 面板改为按需加载(用户点击时才下载对应代码)。
#### 涉及的模块
| 游戏 | 文件 | 行数 | 建议策略 |
|------|------|------|---------|
| 🎰 **五子棋** | gomoku-panel.js | 1,001行 | 点击「五子棋」按钮时动态 import |
| 🏇 **赛马** | horse-race-panel.js | 552行 | 点击「赛马」按钮时动态 import |
| 🃏 **百家乐** | baccarat-panel.js | 448行 | 点击「百家乐」按钮时动态 import |
| 🎣 **钓鱼** | fishing.js | 625行 | 点击「钓鱼」按钮时动态 import |
| 🔫 **老虎机** | slot-machine.js | 347行 | 点击「老虎机」按钮时动态 import |
| 🎲 **彩票** | lottery-panel.js | 343行 | 点击「双色球」按钮时动态 import |
| 🔮 **占卜** | fortune-panel.js | 173行 | 点击「神秘占卜」时动态 import |
#### 具体实现方式
**修改前(chat-room.js 的 game-hall.js):**
```js
// 当前:同步导入
import { bindBaccaratPanelControls, baccaratPanel } from "./baccarat-panel.js";
import { bindGomokuPanelControls, gomokuPanel } from "./gomoku-panel.js";
// ...更多同步导入
```
**修改后:**
```js
// game-hall.js 中延迟加载
export async function openGameHall() {
// 先渲染游戏大厅界面(无游戏逻辑)
renderGameHallUI();
// 只有当用户点击具体游戏时才加载对应模块
document.getElementById('btn-baccarat').addEventListener('click', async () => {
const { bindBaccaratPanelControls, baccaratPanel } = await import('./baccarat-panel.js');
bindBaccaratPanelControls();
baccaratPanel.open();
});
document.getElementById('btn-gomoku').addEventListener('click', async () => {
const { bindGomokuPanelControls, gomokuPanel } = await import('./gomoku-panel.js');
bindGomokuPanelControls();
gomokuPanel.open();
});
// ...其他游戏同理
}
```
**预期收益:**
- `chat.js`**308KB → ~150KB**(减半)
- 各游戏模块按需加载(20~50KB 每次)
- 首次交互速度提升 **50%+**
---
### 🔥 方案 B:游戏事件监听延迟注册
**问题:** 游戏的 WebSocket 事件监听(赛马进度、百家乐开局等)在 `chat.js` 启动时全部注册。
**方案:** 同样采用延迟注册——只在对应游戏被首次打开后才激活事件监听。
```js
// 当前:chat-events.js 中 .listen(".horse.progress", ...) 等
// 改为:游戏面板打开时再注册
export function initHorseRaceEventsOnce() {
if (window._horseRaceEventsInitialized) return;
window._horseRaceEventsInitialized = true;
window.Echo.join(`room.${roomId}`)
.listen(".horse.progress", (e) => { /* ... */ });
}
```
---
### 🔥 方案 C:重型功能模块懒加载
**目标:** 以下模块仅在用户点击对应按钮时加载。
| 模块 | 文件 | 行数 | 触发时机 |
|------|------|------|---------|
| 🛒 **商店** | shop-controls.js | 903行 | 点击「商店」按钮 |
| 💒 **婚姻** | marriage-modals.js | 920行 | 点击「婚姻」入口 |
| 👤 **用户名片** | user-card.js | 855行 | 点击任意用户名 |
| 🏦 **银行** | bank-modal.js | 477行 | 点击「银行」按钮 |
| 👑 **VIP** | vip-controls.js | 477行 | 点击「VIP中心」 |
| 🧧 **红包** | red-packet-panel.js | 679行 | 点击「发红包」或抢包 |
| ⚙️ **个人设置** | profile-controls.js | 715行 | 点击「个人资料」 |
#### 具体实现方式
通过 Vite 的「魔法注释」给动态 chunk 命名:
```js
// 在工具栏按钮点击处理器中
document.getElementById('btn-shop')?.addEventListener('click', async () => {
const { bindShopControls, openShopModal } = await import(
/* webpackChunkName: "shop" */ './shop-controls.js'
);
bindShopControls();
openShopModal();
});
document.getElementById('btn-marriage')?.addEventListener('click', async () => {
const { bindMarriageModalControls, openProposeModal } = await import(
/* webpackChunkName: "marriage" */ './marriage-modals.js'
);
bindMarriageModalControls();
});
```
**预期收益:** chat.js 再减少 **~4,000行**(从 150KB → **~100KB**
---
### ⚡ 方案 DVite 构建优化
当前 `vite.config.js` 只做了基础配置,可以增加以下优化:
```js
// vite.config.js
export default defineConfig({
plugins: [
laravel({ input: [...], refresh: true }),
tailwindcss(),
],
build: {
rollupOptions: {
output: {
// 自动分包策略
manualChunks(id) {
// 将 vendor 依赖(axios, Echo, Pusher)单独打包
if (id.includes('node_modules')) {
return 'vendor';
}
},
},
},
// 压缩级别
minify: 'esbuild', // 默认已启用
// CSS 代码分割
cssCodeSplit: true, // 默认已启用
// 启用 sourcemap 仅开发环境
sourcemap: false, // 生产环境关闭
},
});
```
---
### ⚡ 方案 E:图片优化
虽然背景图已经很小(87KB 共12张),但仍有优化空间:
| 优化 | 说明 | 收益 |
|------|------|------|
| WebP 格式 | 将 PNG 背景图转为 WebP | 减少 30-50% 体积 |
| 聊天图片懒加载 | 消息中的图片使用 `loading="lazy"` | 非可视区域图片不加载 |
| 图片预压缩 | 上传头像时生成多尺寸缩略图 | 减少列表页加载时间 |
```js
// 在 message-renderer.js 中的图片渲染部分
// 当前:
<img src="${thumbUrl}">
// 改为:
<img src="${thumbUrl}" loading="lazy" decoding="async">
```
---
### ⚡ 方案 FCSS 优化
当前 CSS 很小(532行),但可以进一步优化:
```js
// chat-decorations.css384行)中检查是否有未使用的样式
// 建议:将 chat.css 也通过 Vite 导入,而非放在 public/css/ 中
```
---
### 💡 方案 GWebSocket 连接延迟
**当前:** `chat.js` 在页面加载时立即建立 WebSocket Presence Channel 连接。
**优化:** 核心消息通道保持立即连接,但游戏专属事件监听(如赛马进度)延迟注册。
```js
// chat.js → initChat() 中
// 保持核心监听(消息、在线、退出、禁言等)
// 将游戏事件监听移动到各游戏模块各自的 init 中
```
---
## 三、实施步骤
### 第一阶段(核心 · 代码分割)
| # | 任务 | 文件修改范围 | 预估工时 |
|---|------|-------------|---------|
| 1 | **游戏模块动态导入** | `chat-room/game-hall.js`, `chat-room/toolbar.js`, `chat-room.js` | 4h |
| 2 | **游戏事件延迟注册** | `chat-room/baccarat-events.js`, `horse-race-events.js`, `chat-events.js` | 2h |
| 3 | **商店/银行/婚姻懒加载** | `chat-room/toolbar.js`, `chat-room.js` | 3h |
| 4 | **用户名片动态导入** | `chat-room/user-card.js`, `chat-room/user-target-actions.js` | 1h |
| 5 | **Vite vendor 分包** | `vite.config.js` | 30min |
**预期成果:** chat.js 从 **308KB → ~100KB**,首屏加载速度提升 **60%**
### 第二阶段(优化 · 增强)
| # | 任务 | 说明 | 预估工时 |
|---|------|------|---------|
| 6 | 聊天图片 `loading="lazy"` | `message-renderer.js` 中的 img 标签 | 30min |
| 7 | 背景图转 WebP | 用 `cwebp` 或在线工具转换 | 30min |
| 8 | 关闭 sourcemap | `vite.config.js` 配置 | 5min |
| 9 | 测试每个动态导入路径 | 确保所有游戏/功能正常可用 | 2h |
---
## 四、验证方法
优化后验证以下指标:
```bash
# 1. 查看构建产物大小
ls -lh public/build/assets/chat-*.js
# 2. 检查分包情况
cat public/build/manifest.json | python3 -m json.tool
# 3. 浏览器 DevTools 验证
# - Network 面板:确认游戏模块只在点击后加载
# - Coverage 面板:首屏 JS 使用率应 > 70%
```
**预期最终构建产物:**
| 文件 | 预期大小 | 加载时机 |
|------|---------|---------|
| `chat-*.js` | **~100 KB** | 📌 页面加载 |
| `vendor-*.js` | **~50 KB** | 📌 页面加载(Echo/Pusher/Axios |
| `game-baccarat-*.js` | **~25 KB** | 🔘 点击百家乐 |
| `game-gomoku-*.js` | **~35 KB** | 🔘 点击五子棋 |
| `shop-*.js` | **~20 KB** | 🔘 点击商店 |
| `marriage-*.js` | **~18 KB** | 🔘 点击婚姻 |
| `effects-*.js` | **~6 KB** | ✅ 已有分包 |
| **总计首次加载** | **~150 KB** | 🚀 减少 **51%** |
---
## 五、注意事项
1. **渐进式实施:** 不要一次性改所有模块。先改游戏模块,验证无问题后再改其他。
2. **加载状态反馈:** 动态导入期间显示轻量加载指示(spinner),避免用户等待焦虑。
3. **WebSocket 事件时序:** 游戏模块如果需要在页面加载时接收事件(如赛马正在进行的进度广播),不能完全延迟,需要保留核心事件监听器。
4. **错误边界:** 动态导入失败时应有 fallback(如提示"模块加载失败,请刷新重试")。
5. **缓存策略:** 动态分块后的文件名带 hash,Vite 自动处理缓存失效。
---
> **总结:** 最核心的优化就是**把游戏和重型功能模块从 chat.js 中拆出来**,
> 利用 Vite 原生支持的 `import()` 动态导入实现按需加载。
> 这项改动不需要修改后端代码、不需要修改路由、不需要重构现有逻辑,
> 只需在现有模块的入口处添加 2-3 行 `await import()` 代码即可。
>
> 建议先从 **游戏模块**(方案 A)开始实施,收益最大、风险最低。
> 完成后再逐步推进 **商店/婚姻/银行**(方案 C)的懒加载。