Files
TrueGrowth/docs/NPM_DEPENDENCY_SLIMMING_LESSONS.md

171 lines
7.6 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.
# 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 执行测量卡住。
## 一句话结论
依赖瘦身最稳的路径不是“看到大包就删”,而是先做窄用法替换,再用真实安装树和生产构建验证依赖图。✅