十个月,给 LobeHub 换掉 Ant Design:向 Base UI 与 StyleX 的渐进式迁移

💡 原文中文,约11100字,阅读约需27分钟。
📝

内容提要

LobeHub 用一年时间将 lobe-ui 从 antd 迁移到 Base UI,样式方案从运行时 emotion 改为编译期 StyleX。迁移采用新旧组件同包并存、按组件逐个推进的方式,完成后通过 ESLint 和提交钩子锁定旧导入,存量进入只减不增的豁免名单。业务全程不停航,lobe-chat 直接引用 antd 的文件从峰值 485 个降至 0,单个 Button 的 JS 体积从约 130KB 降至约 32KB。作者认为大型迁移的瓶颈已从改写代码转向设计并存门禁,以及用机器自动证明行为与样式无退化。

🔎

延伸解读

迁移节奏为何在8月后突然加快

文章将8月后的提速归因于模型能力变化:6月9日Claude Fable 5、7月24日Claude Opus 5发布,8月lobe-chat提交署名模型从Opus 4.8换成Opus 5。迁移流程本身未变,但Drawer、Alert、Skeleton等组件从合入到禁用旧导入缩短到数天。这说明在大型重构中,工具链的迭代速度会直接影响迁移窗口,而非仅靠流程设计。

并存期脚手架与最终拆除的代价

v5通过@lobehub/ui/base-ui子路径让新旧组件同包并存,业务按组件逐个切换,避免长期分支。但并存本身是过渡层:v6的lobe-ui#702把src/base-ui并回根目录、去掉子路径。文章也提到过渡层会出问题,如ImperativeModal wrapper在退出动画前卸载内容,最终改为命令式createModal。并存降低了切换风险,但过渡代码需要被当作临时设施管理。

行为差异比外观差异更隐蔽

表单迁移后,antd Form与base-ui Form提交时返回值不同:前者只提交当前挂载的Form.Item值,后者会带上被条件隐藏的字段和initialValues多出的key。这导致插件鉴权从bearer切到none后旧token仍被保存,传输方式从http切到stdio后旧url残留。文章用lobehub#20644加pruneHiddenFields修复。外观截图能发现视觉差异,但数据提交语义的变化往往到用户保存时才暴露。

视觉对比的基线选择决定回归能否被发现

文章指出,lobe-ui#705的视觉对比工具拿v6预览版当基线,而.anticon样式在更早的lobe-ui#699删antd时就没了,基线本身已带回归,对比结果自然一致。图标偏移最终靠肉眼在Tree组件发现,补上与antd时代lobe-chat构建对比后,73个图标垂直偏移全部对上。基线应取用户最后看到的版本,而非上一个提交,否则旧实现的bug也会混入基线。

❓

Q&A

LobeHub 为什么决定把 antd 换掉?

性能优化时发现 antd 组件本身开销大(如 Tooltip 单个 0.5ms),且 UI 难以高度定制,因此决定替换。

迁移过程中如何保证业务不停航?

采用新旧组件同包并存、按组件逐个迁移的方式,业务按组件切换,无需拉长期分支重写。

如何防止新代码继续使用旧的 antd 组件?

通过 ESLint 规则和 pre-commit hook 禁止旧导入,存量调用进入豁免名单,且豁免名单只减不增。

迁移后单个 Button 的 JS 体积减少了多少?

从约 130KB 降至约 32KB(gzip 后),主要去掉了 antd 和 rc-component 依赖。

样式方案从 emotion 切换到 StyleX 后,首屏性能有什么变化?

运行时开销降低(CSS 规则减少、阻塞时间少 9%),但 4G 下 FCP 从 440ms 涨到 594ms,因整份 style.css 阻塞渲染。

迁移后发现了哪些 antd 隐式提供的样式假设?

包括根节点字体字号、图标基础布局、全局 reset 等,删除 antd 后需在 theme.css 和 Icon 组件中补回。

如何验证迁移后样式没有退化?

通过逐元素比较 computed style,覆盖亮暗主题、断点、hover 等条件,并建立可信基线(自比稳定、能抓差异、承认局限)。

🏷️

标签

➡️

继续阅读