构建优化
Elftia 主 bundle 已从 1,812 KB 优化至 759 KB (-58%)。本文档记录优化策略和规范,防止退化。
Bundle 大小限制
硬性限制(必须遵守)
| 指标 | 限制 | 当前值 | 检查方式 |
|---|---|---|---|
| 最大单个 chunk | < 1 MB | 759 KB | npm run verify:build |
| 主 bundle (gzipped) | < 300 KB | 221 KB | npm run verify:build |
| Vite 警告 | 0 个 | 0 个 | 构建输出 |
推荐目标
| 指标 | 目标 | 当前值 | 优先级 |
|---|---|---|---|
| 主 bundle (未压缩) | < 500 KB | 759 KB | P1 |
| 总 bundle | < 3 MB | 6.24 MB | P2 |
| 首屏加载 | < 2s | ~2s | P0 |
检查命令
{/* 快速验证(4 项关键指标) */}
npm run verify:build
{/* 详细分析(chunk 分类统计) */}
npm run analyze:build
{/* 可视化分析(生成 stats.html) */}
npm run build:renderer
{/* 打开 dist/stats.html */}
代码分割策略
路由级代码分割(必须)
所有页面组件必须使用 React.lazy 懒加载。lazy/ 是按用途拆分的目录(pages.tsx / workspaces.tsx / inline.tsx / shell.tsx / skeletons.tsx / with-suspense.tsx / index.ts barrel),加新 page 改 lazy/pages.tsx:
{/* packages/renderer/src/app/lazy/pages.tsx */}
export const LazySettingsPage = lazy(() => import('../Settings'));
export const SuspenseSettings = withSuspense(LazySettingsPage, PageSkeleton);
{/* packages/renderer/src/app/App.tsx — 旧 import 路径仍然有效(barrel re-export) */}
import { SuspenseSettings as Settings } from './components/lazy';
<Route path="/settings" element={<Settings />} />
{/* 禁止:直接导入页面组件 */}
import Settings from './components/Settings';
<Route path="/settings" element={<Settings />} />
大型组件懒加载(推荐)
单个组件 > 100 KB 或依赖大型第三方库时使用懒加载。
已懒加载的组件:
| 组件 | 大小 | 依赖 |
|---|---|---|
MermaidDiagram | 451 KB | mermaid |
HtmlPreviewPanel | 198 KB | iframe sandbox |
CodeEditor | 34 KB | CodeMirror |
Shell / StandaloneShell | 9 KB | xterm |
判断标准:
| 条件 | 是否懒加载 |
|---|---|
| 组件 bundle > 100 KB | 是 |
| 依赖大型第三方库 | 是 |
| 非首屏必需 | 是 |
| 使用频率低 | 是 |
| 小型常用组件 (< 10 KB) | 否 |
| 首屏必需核心组件 | 否 |
| 频繁切换的 UI 组件 | 否 |
Context 优化
非全局必需的 Context 应移到页面级组件:
{/* 好:页面级 Context */}
export function Settings() {
return (
<SettingsProvider>
<SettingsContent />
</SettingsProvider>
);
}
{/* 不好:全局加载不必要的 Context */}
<GlobalContext>
<Routes />
</GlobalContext>
注意
移动 Context 前必须分析依赖关系,确保不破坏跨页面状态共享。
Vendor Chunks 配置
当前策略按更新频率和使用频率分组:
{/* vite.config.js — manualChunks */}
{
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
'vendor-ui': ['@radix-ui/react-context-menu', '@radix-ui/react-dialog', ...],
'vendor-icons': ['lucide-react'],
'vendor-utils': ['clsx', 'tailwind-merge', 'class-variance-authority', 'zustand'],
'vendor-markdown': ['react-markdown', 'remark-gfm', 'rehype-highlight'],
'vendor-codemirror': ['@codemirror/state', '@codemirror/view', ...],
'vendor-xterm': ['@xterm/xterm', '@xterm/addon-fit', '@xterm/addon-web-links'],
}
分组原则
| 类型 | 更新频率 | 缓存优先级 | 示例 |
|---|---|---|---|
| 核心框架 | 低 | 最高 | React, React DOM |
| 大型第三方库 | 低 | 高 | CodeMirror, xterm |
| UI 组件库 | 中 | 中 | Radix UI, lucide |
| 工具库 | 中 | 中 | clsx, zustand |
新增依赖检查
添加新的第三方依赖时:
{/* 1. 检查大小 */}
npm info <package> dist.unpackedSize
{/* 2. 如果 > 100 KB,添加到 vendor chunks */}
{/* 3. 如果更新频率高,独立 vendor chunk */}
{/* 4. 确认支持 tree shaking */}
{/* 5. 运行 npm run analyze:build 检查影响 */}
Tree Shaking
正确的导入方式
{/* 好:具名导入(支持 tree shaking) */}
import { Button, Input, Select } from '@/components/ui';
import { Home, Settings, User } from 'lucide-react';
{/* 好:Radix UI 命名空间导入(官方推荐) */}
import * as Dialog from '@radix-ui/react-dialog';
{/* 不好:导入整个库 */}
import * as UI from '@/components/ui';
import * as Icons from 'lucide-react';
检查未使用导入
npm run typecheck # TypeScript 检测未使用导入
npm run lint:eslint # ESLint 警告未使用变量
Vite 生产构建配置
{/* vite.config.js — 关键配置 */}
build: {
minify: 'terser', // terser 比 esbuild 压缩率更高
sourcemap: false, // 生产环境关闭 sourcemap
chunkSizeWarningLimit: 500, // chunk 大小警告阈值 (KB)
terserOptions: {
compress: {
drop_console: true, // 移除 console.log(保留 warn/error)
drop_debugger: true, // 移除 debugger
},
},
}
常见问题与解决方案
主 bundle 过大 (> 500 KB)
排查步骤:
- 运行
npm run build:renderer,打开dist/stats.html - 查看主 bundle 组成,找出最大模块
- 检查是否有未懒加载的大型组件
- 检查是否有不必要的全局导入
解决方案:
- 将大型组件改为懒加载
- 移除未使用的导入
- 将页面组件改为路由级懒加载
Vendor chunks 过大 (> 500 KB)
解决方案:
- 将大型 vendor 拆分成更小的 chunks
- 按更新频率重新分组
- 检查是否有重复打包的依赖
总 bundle 过大 (> 5 MB)
解决方案:
- 按需加载图表库(Mermaid、Cytoscape 等)
- 移除不常用的功能或依赖
- 考虑使用更小的替代库
代码分割失败
排查步骤:
- 检查
lazy/目录中相应 split 文件的懒加载配置(page-level 在lazy/pages.tsx、workspace bodies 在lazy/workspaces.tsx、inline chat 组件在lazy/inline.tsx) - 检查
App.tsx中是否使用了懒加载组件 - 检查 Vite 配置中的
manualChunks
提交前检查清单
新增页面时
- 页面组件已添加到
lazy/pages.tsx(或对应的 split 文件) - 使用
withSuspense包裹并提供 fallback - 在
App.tsx中使用懒加载版本 - 运行
npm run build:renderer验证独立 chunk
新增大型组件时
- 组件大小 > 100 KB → 使用懒加载
- 依赖大型第三方库 → 使用懒加载
- 提供合适的 loading 状态
新增第三方依赖时
- 检查依赖大小
- 依赖 > 100 KB → 添加到 vendor chunks
- 更新频率高 → 独立 vendor chunk
- 支持 tree shaking → 使用具名导入
- 运行
npm run analyze:build检查影响
持续优化建议
每次发版前
npm run verify:build
npm run build:renderer | grep -i "warning"
{/* 如果主 bundle 增加 > 50 KB,需要排查原因 */}
每月
npm run build:renderer
{/* 打开 dist/stats.html 检查可优化模块 */}
npm outdated
npm update
每季度
- 审查所有 vendor chunks 配置
- 评估新的优化策略
- 评估是否需要移除不常用功能
- 更新本规范文档
CI/CD 集成
建议在 CI 中添加 bundle 大小检查:
- name: Build and verify
run: |
npm run build:renderer
npm run verify:build || echo "Warning: Bundle size check failed"
性能指标基准
| 指标 | 基准 | 目标 | 监控方式 |
|---|---|---|---|
| 主 bundle | 759 KB | < 500 KB | verify:build |
| 首屏加载 | ~2s | < 2s | Lighthouse |
| 总 bundle | 6.24 MB | < 3 MB | analyze:build |
| Chunk 数量 | 76 | - | analyze:build |