qdma-ui · 自研组件库
Vue3 + ESM 按需引入 + Design Token + Storybook + 私有 npm,用组件契约收敛重复 UI 与包体失控,附七阶段、12 个坑与防回退机制。
qdma-ui · 自研组件库
Vue3 + ESM 按需引入 + Design Token + Storybook + 私有 npm,用组件契约收敛重复 UI 与包体失控。
30 秒电梯版
门店助手 H5 早期为赶进度引了三套 UI 库,同一业务出现 3 种按钮样式、3 套表单校验、2 套弹窗实现;加上分包粗放,H5 包体涨到 8.17MB,门店 WiFi 下加载 3.2 秒。我判断继续「换一个库」解决不了问题——问题是缺一层自己的业务组件契约。所以从 0 搭了 qdma-ui:Vue3 + Rollup 多产物、ESM 按需引入、统一 design token、10+ 核心业务组件、Storybook + 单测、私有 npm。全量替换三方库后,包体 8.17MB → 5.29MB(↓35.3%),依赖 ↓45.1%,WiFi 加载 3.2s → 2.14s,Lighthouse 61 → 89。
为什么要自研:先回答「这不是造轮子吗」
先说什么时候不该自研:只需要通用基础组件、团队 <5 人、没有明确业务组件诉求。
再说为什么必须自研:
| 理由 | 表现 | 为什么现成库解决不了 |
|---|---|---|
| 三库并存契约不统一 | 3 种按钮、2 套弹窗、校验各写各的 | 换库只是换一家,多套在契约就永远不统一 |
| 体积失控 | 8.17MB、WiFi 3.2s,大量组件用不上却打不进 tree-shaking | 第三方库为通用性牺牲体积,只有自己的库能精确裁剪 |
| 业务定制被卡 | 地址级联、扫码上传、离线表单三方库改不动 | fork 三方库背上长期 rebase 成本 |
一句话:自研的不是「基础组件」,是「业务组件契约 + 体积控制权」。基础能力站在成熟库肩膀上,业务语义层必须自己握在手里。
七阶段
盘点 → 排优先级 → Rollup 基建 → Token 先行 → 组件开发 → 灰度替换 → 发布治理。
- 盘点:按使用次数排序,常发现「引了 200 个组件实际只用 30 个」。
- 优先级:P0 表单/表格/弹窗(高频+强业务);P1 上传/级联/步骤条(业务特有);P2 按钮/标签/空态(便宜但统一视觉)。
- Rollup 基建:多产物(ESM/CJS/UMD)+
.d.ts+ 独立 CSS。external: ['vue']是底线。 - Design Token 先行(顺序不能反,先写组件再抽 token 等于重写)。
- 组件开发:受控/非受控双通道、
$attrs透传、插槽优先、语义化变体。 - 灰度替换:一次替换一类组件(按钮→弹窗→表单/表格),新旧并存可回滚,逐项记录体积。
- 发布治理:私有 npm + semantic versioning + 自动 CHANGELOG + CI(lint→test→build→size-check→publish)。
Design Token 三层
原始值(palette)→ 语义层(semantic,业务只认这层)→ 组件级(component)
业务只用语义层,换主题 = 覆盖语义层变量;组件内不写死任何颜色值(stylelint 卡住硬编码)。
按需引入与 tree-shaking(最经典坑)
「写了 ESM」不等于「能 tree-shake」。体积反弹根因:export * 全量导出,构建工具不敢裁。解法:
"sideEffects": ["*.css"]- 多入口(
@qdma/ui/es/button按路径引入) babel-plugin-import兜底老构建链路external与peerDependencies成对(Vue 必须 external,否则双 Vue 实例响应式失效)- CI 体积对比门禁
坑清单(12 条核心)
| # | 坑 | 根因 |
|---|---|---|
| 1 | 打包进了 Vue,双实例 | external 没配全(带路径引用没匹配上) |
| 2 | 体积没降反升 | 全量导出,tree-shaking 失效 |
| 3 | 样式顺序错乱 | CSS 产物顺序不可控 |
| 4 | TS 没类型提示 | 未产出 .d.ts |
| 5 | 按需引入配置复杂 | 没用 ESM 多入口 |
| 6 | 组件 API 一改下游全红 | 破坏性变更未走 major |
| 7 | Storybook 和产物不一致 | 文档跑源码、业务用产物 |
| 8 | 主题切换不彻底 | 组件内硬编码色值 |
| 9 | 覆盖率高仍有 bug | 只测函数没测交互路径 |
| 10 | 构建产物残缺 | 组件间循环依赖 |
| 11 | 业务装不上 | 版本冲突/peerDependencies 没声明 |
| 12 | 体积反弹 | 没依赖准入与体积门禁 |
长效防回退三件套
- 体积门禁:PR 对比产物体积,增长 <3% 通过、3~10% 警告、>10% fail
- 依赖准入:新增运行时依赖必须评审(有无现成能力/体积多大/能否按需引入)
- 性能预算:Lighthouse/体积阈值写进 CI,性能回归一票否决
量化结果(逐项归因)
- 包体 8.17MB → 5.29MB(↓35.3%):三方库全量替换为按需 ESM + tree-shaking
- 依赖包体积 ↓45.1%:三套 UI 库收敛为一套
- WiFi 加载 3.2s → 2.14s(↓33.07%):包体下降 + 资源合并
- Lighthouse 61 → 89:包体 + 主线程优化(TBT ↓47.2%)
讲效益分三层:体验层(加载/Lighthouse)、工程层(包体/依赖)、组织层(10+ 组件的统一契约,新项目复用)。长期价值最大的是组织层——体积是一次性的,契约是长期的。