171 lines
7.6 KiB
Markdown
171 lines
7.6 KiB
Markdown
# npm 依赖瘦身经验
|
||
|
||
更新日期:2026-04-28
|
||
|
||
## 背景
|
||
|
||
本轮目标是减少安装体积、构建解析压力和异步 chunk 体积,同时不降低现有体验。实践证明,依赖瘦身不能只看包大小或 direct import,要结合源码、锁文件、真实安装树和生产构建一起判断。
|
||
|
||
## 本轮结论
|
||
|
||
### 1. 高收益低风险项优先做
|
||
|
||
已安全替换或移除:
|
||
|
||
- `ahooks`:只用到 `useEventListener`,改成本地轻量 hook。
|
||
- `mobile-detect`:只用于移动端判断,改为 `matchMedia`、pointer、touch 能力检测。
|
||
- `@llamaindex/chat-ui`:主要使用聊天展示组件和类型,改成本地轻量类型与消息区组件。
|
||
- `@sentry/react`:异常监控已由 PostHog 承担,删除 Sentry 初始化与依赖。
|
||
- `prismjs`:不再作为直接依赖保留。
|
||
- `tdesign-icons-react`:统一到 `0.6.x`,避免 `0.5.x` 和 `0.6.x` 双版本。
|
||
|
||
效果最明显的是聊天区:`ChatMessagesArea` 从原先约 405KB gzip 降到约 5KB gzip。
|
||
|
||
### 2. direct dependency 不等于一定能删
|
||
|
||
`@plait/layouts` 是这轮的反例。
|
||
|
||
源码里没有直接 import 它,但 `@plait/mind` 的构建产物会 import `@plait/layouts`。只做 `pnpm install --lockfile-only` 时旧的 `node_modules` 还在,构建会假通过;真正 prune 安装树后,`vite build` 会报:
|
||
|
||
```text
|
||
Rollup failed to resolve import "@plait/layouts" from "@plait/mind"
|
||
```
|
||
|
||
经验:
|
||
|
||
- “项目源码没用”不等于“运行时依赖图没用”。
|
||
- 如果上游包把必要依赖漏写到自己的 `dependencies`,应用层仍要显式保留。
|
||
- 删除依赖后必须跑一次真实 `pnpm install --frozen-lockfile`,不能只更新锁文件。
|
||
|
||
### 3. 大包不一定适合直接替换
|
||
|
||
暂不硬替的包:
|
||
|
||
- `mermaid`:功能承载重,且存在转换器带来的版本/分块问题;优先治理加载和去重。
|
||
- `xlsx`:已经动态导入,保留 Excel 体验时不应强行降级成 CSV。
|
||
- `tdesign-react`:使用面广,直接替换成本高;应先考虑按需导入或 UI 适配层。
|
||
- `jszip`、`viewerjs`、`rxjs`、`localforage`:收益和风险不匹配,不作为第一批目标。
|
||
|
||
经验:
|
||
|
||
- 大包优先看“是否在首屏”“是否动态导入”“是否承载核心体验”。
|
||
- 低频功能的大包,先保证懒加载;高频窄用法的小依赖,优先本地替代。
|
||
|
||
### 4. 图标库替换属于视觉能力变更,不应混入无损瘦身
|
||
|
||
2026-04-28 的二次瘦身中,曾尝试把 `tdesign-icons-react` 通过本地兼容层映射到 `lucide-react`。虽然可以补齐 TDesign 内部导出的图标名并通过类型检查、dev server 预构建,但实际 UI 出现了明显视觉回归:
|
||
|
||
- 图标形态与原 TDesign 图标不一致,例如画布管理、搜索、文件夹、图层等入口。
|
||
- `filled`、描边粗细、对齐、尺寸和设计语言很难靠简单 alias 等价。
|
||
- TDesign 组件内部也会 import 大量图标名,补齐导出只能解决构建错误,不能保证视觉一致。
|
||
|
||
最终结论:
|
||
|
||
- 保留 `tdesign-icons-react@0.6.x`,只做版本去重,不做跨图标库替换。
|
||
- 如需替换为 `lucide-react`,必须单独立项,逐个组件改 import,并做截图/交互回归。
|
||
- “能编译”和“视觉无损”是两件事;依赖瘦身主线优先避免视觉语义变化。
|
||
|
||
### 5. 只读 Markdown 可轻量化,编辑器路径必须保留
|
||
|
||
只读聊天消息、工作流消息和卡片展示不需要加载完整 Milkdown 编辑器。更稳的做法是新增轻量 `MarkdownReadonly`:
|
||
|
||
- 只覆盖只读展示路径。
|
||
- 知识库编辑器继续使用 Milkdown,保留编辑体验。
|
||
- 链接和图片 URL 做 scheme 过滤,避免因为轻量实现引入注入风险。
|
||
- Mermaid 仍通过代码块回调延迟加载渲染器,不进入普通只读 Markdown 核心路径。
|
||
|
||
经验:
|
||
|
||
- 编辑器依赖不要“一刀切”替换;先拆读写路径。
|
||
- 轻量 Markdown 组件必须避免 `dangerouslySetInnerHTML`,并对链接、图片、素材引用做安全边界。
|
||
|
||
### 6. size-limit 守护 chunk 时不要默认跑 JS 执行耗时
|
||
|
||
`@size-limit/preset-app` 默认包含 `@size-limit/time`,会启动 headless Chrome 测量 JS 运行耗时。用于检查构建后 chunk gzip 预算时,这一步可能卡住或对非入口 chunk 产生噪音。
|
||
|
||
更稳的配置:
|
||
|
||
- `.size-limit.json` 指向真实 chunk 命名,例如 `startup-app-*`、`ai-chat-*`、`diagram-engines-*`。
|
||
- 每项设置 `"gzip": true` 和 `"running": false`。
|
||
- `pnpm size` 只作为产物预算守护,不承担浏览器执行性能测试。
|
||
|
||
### 7. 本地 dev 的 SW 与 CSP 要和生产策略分开
|
||
|
||
本轮还暴露了本地 dev 特有问题:旧 Service Worker 可能拦截 Vite 的 `/@fs/...` 依赖资源,造成 503;同时 dev CSP 如果只允许 `https:`/`wss:`,会阻止 Vite HMR 的 `ws://localhost:*`。
|
||
|
||
处理原则:
|
||
|
||
- 本地 `localhost`/`127.0.0.1` 默认启用 Service Worker,但默认 dev 命令必须同步 watch 最新 `sw.js`;需要绕过时用 `?sw=0` 显式关闭并清理旧注册。
|
||
- dev server CSP 放行 `http:` 和 `ws:`,生产/preview 保持更严格策略。
|
||
- 修这类问题后用 `curl -I /`、`curl -I /src/main.tsx`、`curl -I /@fs/...` 做轻量验证,不必反复生产构建。
|
||
|
||
## 验证清单
|
||
|
||
### 1. 残留搜索
|
||
|
||
删除依赖后要同时查源码、package、锁文件:
|
||
|
||
```bash
|
||
rg -n "@llamaindex/chat-ui|mobile-detect|from 'ahooks'|@sentry/react|prismjs|tdesign-icons-react@0\\.5" package.json packages/*/package.json apps packages pnpm-lock.yaml -g '!**/node_modules/**' -g '!**/dist/**'
|
||
```
|
||
|
||
### 2. 真实安装树验证
|
||
|
||
锁文件更新后必须同步本地安装树:
|
||
|
||
```bash
|
||
NPM_TOKEN=${NPM_TOKEN:-dummy} pnpm install --frozen-lockfile
|
||
```
|
||
|
||
然后用 `pnpm list` 看是否仍有旧包:
|
||
|
||
```bash
|
||
NPM_TOKEN=${NPM_TOKEN:-dummy} pnpm list @sentry/react @llamaindex/chat-ui ahooks mobile-detect tdesign-icons-react --depth 10 --json
|
||
```
|
||
|
||
### 3. 类型和构建
|
||
|
||
基础验证:
|
||
|
||
```bash
|
||
NPM_TOKEN=${NPM_TOKEN:-dummy} pnpm typecheck
|
||
```
|
||
|
||
关键批次后跑生产构建:
|
||
|
||
```bash
|
||
NPM_TOKEN=${NPM_TOKEN:-dummy} pnpm exec nx build web
|
||
```
|
||
|
||
经验:
|
||
|
||
- `typecheck` 能验证 API 和类型迁移。
|
||
- `build web` 能暴露真实依赖图、动态导入、CSS、SW 构建问题。
|
||
- `pnpm build:web` 会更新 `version.json`,只做 bundle 验证时优先用 `pnpm exec nx build web`。
|
||
- dev 资源 503 或 HMR CSP 问题优先检查本地 SW 和 dev CSP,不要直接怀疑依赖安装树。
|
||
|
||
## Web Vitals 去向
|
||
|
||
`web-vitals` 没有单独上报到第三方性能平台,而是动态导入后发到 PostHog:
|
||
|
||
- 服务:`packages/drawnix/src/services/web-vitals-service.ts`
|
||
- 事件名:`$web_vitals`
|
||
- 指标:`CLS`、`FCP`、`LCP`、`TTFB`、`INP`
|
||
- 上报入口:`analytics.track('$web_vitals', eventProperties)`
|
||
|
||
因此删除 Sentry 后,异常监控和 Web Vitals 仍统一留在 PostHog 体系内。
|
||
|
||
## 实施建议
|
||
|
||
1. 先替换窄用法依赖,再动大组件库。
|
||
2. 本地替代要保持行为等价,尤其注意事件监听清理、SSR/window 判断、移动端能力检测。
|
||
3. 聊天 UI 替换要回归普通消息、图片消息、工作流消息、错误消息和 Mermaid markdown。
|
||
4. 锁文件验证后必须 prune 安装树,否则容易被旧 `node_modules` 误导。
|
||
5. 大包治理优先做懒加载、去重、manual chunk 分析,避免用低体验替代换体积。
|
||
6. 图标库、UI 组件库替换要单独走视觉回归,不放进“无损瘦身”批次。
|
||
7. size-limit 检查 chunk gzip 预算时关闭 `running`,避免 headless Chrome 执行测量卡住。
|
||
|
||
## 一句话结论
|
||
|
||
依赖瘦身最稳的路径不是“看到大包就删”,而是先做窄用法替换,再用真实安装树和生产构建验证依赖图。✅
|