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

11 KiB
Raw Blame History

🚀 聊天室项目 — 前端加载优化详细方案

项目路径: /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.js982行同步导入了所有 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):

// 当前:同步导入
import { bindBaccaratPanelControls, baccaratPanel } from "./baccarat-panel.js";
import { bindGomokuPanelControls, gomokuPanel } from "./gomoku-panel.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.js308KB → ~150KB(减半)
  • 各游戏模块按需加载(20~50KB 每次)
  • 首次交互速度提升 50%+

🔥 方案 B:游戏事件监听延迟注册

问题: 游戏的 WebSocket 事件监听(赛马进度、百家乐开局等)在 chat.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 命名:

// 在工具栏按钮点击处理器中
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 只做了基础配置,可以增加以下优化:

// 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" 非可视区域图片不加载
图片预压缩 上传头像时生成多尺寸缩略图 减少列表页加载时间
// 在 message-renderer.js 中的图片渲染部分
// 当前:
<img src="${thumbUrl}">
// 改为:
<img src="${thumbUrl}" loading="lazy" decoding="async">

方案 FCSS 优化

当前 CSS 很小(532行),但可以进一步优化:

// chat-decorations.css384行)中检查是否有未使用的样式
// 建议:将 chat.css 也通过 Vite 导入,而非放在 public/css/ 中

💡 方案 GWebSocket 连接延迟

当前: chat.js 在页面加载时立即建立 WebSocket Presence Channel 连接。 优化: 核心消息通道保持立即连接,但游戏专属事件监听(如赛马进度)延迟注册。

// 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

四、验证方法

优化后验证以下指标:

# 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)的懒加载。