Skip to content

Medal W-Key 前端基础设施现代化复盘

Medal W-Key
Technology and Product

来源:https://medal.tv/blog/posts/w-key-in-frontend-synergizing-technology-and-product 作者

Zhang(Medal Frontend),2026-06-23 分类:开源项目分析/阅读/

文章核心
→ Micro 现代化路径

Medal.tv(游戏录屏分享平台)Electron+Web 前端陷入恶性循环——老组件锁死依赖升级,新功能又让重构成本飙升。作者用”W-Key”(魔兽术语,只推一路不绕弯)比喻自己的策略:先重塑开发者体验,再让组件现代化水到渠成

痛点与反模式

  • 组件 prop 过多、行为不单一、默认值满天飞、新旧 API 混杂
  • 修一个组件改动像 Deogen(Phasmophobia 怪物)一样令人恐惧
  • 业务功能 1-2 工时估点,实际拖成数倍

宏(Macro)层 — 先动基础设施

步骤解决问题关键点
PNPM monorepo组件库版本混乱、合并冲突性能 + 工作区
Vite + HMRElectron 应用本地冷启动,改代码秒级反馈CEO Ken Colton 推动
规范化 import 路径AI 工具可靠识别死代码包名 + 清晰路径 + 文件扩展名,禁用 barrel 桶
Vite 统一生产构建摆脱 Rollup,Glob Imports 简化拆包处理 native module + externalize 依赖
Electron + Web 代码合并抽 client-agnostic utils/hooks/types 包进一步 DRY

微(Micro)层 — 组件现代化

  • Bundle 优化(总减重 41.3 MB,从 ~44 MB 到 2.7 MB,减 94%):
    • 删 component barrel 文件:-2.6 MB
    • 外置 SoundAlertsData .wav:-3.4 MB
    • Rollup 优先 ESM 改善 tree-shake:-4 MB
    • 动态 import i18n 翻译文件:-13 MB(单项最大)
    • 路由级 code split(顺手把 React Router v5 升到 v7):-5.4 MB
  • UI 库迁移
    + Styled Components → Tailwind + Shadcn (Radix UI)Base UI
  • 自定义 ESLint 规则 + 文档双重标记废弃组件,告诉团队”用什么替换什么”
  • 测试
    ;构建

关键数字

最终 renderer bundle:2.7 MB(从 ~44 MB 起算,减 94%)。整个过程”能站起来整个页面只用一天”。


讨论延伸
File 与 Tree-Shaking 实战

这部分不是文章原文,是阅读后与助手讨论沉淀的实践知识。

为什么 barrel file 默认禁用

components/index.ts 这种”桶文件”看起来很工程化(导入简洁、利于重构),但运行时 import 的是桶,不是真正的模块——export { Button } from './Button' 执行时会加载整个 Button.tsx 及其 import 链(hooks/utils/styles/副作用代码)。

打包器想 tree-shake 掉没用的导出,前提是能 100% 确定那个导出没有副作用。而业务组件天然有副作用:

  • 组件 mount 时发请求
  • i18n 库初始化读 localStorage
  • 主题切换改 document.documentElement
  • 状态管理建 store
  • analytics/错误监控 import 时启动

Medal 给出真实数字:删一个 components 桶 → -2.6 MB;外置一个被桶路径意外拖入的 .wav → -3.4 MB。如果桶真的”按需加载”,这 10MB 不可能只改几行 import 路径就省出来。

推荐的写法

// ❌ 桶导入(业务代码默认禁用)
import { Button, Modal } from '@/components';
// ✅ 显式深路径 + 扩展名
import { Button } from '@/components/Button/Button';
import { Modal } from '@/components/Modal/Modal';

为什么 lodash-es 能保持 tree-shake

业务项目做不到的事,lodash-es 全做到了——这是”库 vs 业务”的本质边界,不是工具差异。

条件lodash-es业务代码桶
sideEffects: false 担保✅ 主动承诺❌ 不敢写(有真副作用)
桶里 re-export 的粒度✅ 几百个纯函数叶子❌ Button→hooks→utils→styles,链长且脏
模块顶层无副作用✅ 纯函数❌ mount/init/subscribe
作者 tree-shake CI✅ 严格测试❌ 没有
纯 ESM 格式看项目

lodash-es 能 tree-shake 不是因为它有桶,是因为它有桶 + sideEffects: false + 叶子化导出 + 纯函数 + ESM + 作者严格维护。业务项目做不到这套组合拳,所以业务项目里桶就真的是炸弹。

库 vs 业务的分离策略

核心原则:库用桶,业务用显式路径

场景桶策略
公开 npm 包(antd、shadcn、radix-ui)✅ 必用桶(用户体验) + sideEffects: false
公司内部跨业务组件库(字节 semi)用桶 + 严格 sideEffects 治理
单业务用的”通用组件”禁用桶,显式深路径
纯类型导出✅ 安全(type-only import)
纯叶子工具函数✅ 影响小,可用
体积敏感的入口(路由、Electron renderer)❌ 必禁

单业务”通用组件库”的第三条路
模式

如果”通用组件库”只在公司内部一个项目复用,Shadcn 模式比 npm 包更省心——既保留”通用”,又规避”库 vs 业务”的桶陷阱。

  • 不发布 npm 包,不维护版本
  • 组件源码直接复制到业务项目里(npx shadcn@latest add button)
  • 改组件不用发版本、不用等业务升级,直接改
  • tree-shake 天然干净(路径都是显式的)
  • Medal 用的就是 Tailwind + Shadcn(Radix UI)——他们选择 Shadcn 而不是自造 npm 组件库,本质就是规避了”库 vs 业务”的桶陷阱

自建 npm 组件库的硬性配置清单

如果坚持做对外/对内 npm 组件库,以下配置必须做到位:

package.json
{
"sideEffects": ["**/*.css", "**/*.scss"],
"type": "module",
"exports": {
".": {
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
}
}
  • sideEffects 白名单样式文件(CSS import 是真副作用,不能 tree-shake)
  • 每个组件必须是叶子,内部不要再 re-export 其他组件
  • 加 tree-shake 测试到 CI(用 rollup-plugin-visualizer 或 webpack-bundle-analyzer 断言”引入 Button 不应包含 Modal 代码”)
  • 类型导出用 export type 显式标注,避免打包器误判
  • monorepo 跨包引用时,调试必须跑生产构建验证——开发态通过不代表生产态通过(webpack 某些配置会把整个软链接包当 chunk 全部引入)

反直觉观点

  • “删代码”比”写新代码”更难,因为没人鼓掌——需要先投资 DX(快反馈、可识别),再谈清理
  • Macro 先于 Micro:先动 monorepo/包管理/构建系统这种”看不见的基础设施”,让组件级重构变成顺水推舟
  • Bundle 优化最大单一来源不是 minify/gzip,而是动态 import 大文件(i18n 占 -13MB,占全部减量近三分之一)

一句话总结

Medal 的实践揭示了一个常被忽略的工程真理:前端现代化的最大杠杆点在基础设施层(Macro),不在组件层(Micro)。而实现 Macro 现代化的关键,是敢于在业务代码里禁用 barrel file、把 import 路径显式化——这是后续所有 tree-shake / 死代码删除 / AI 重构能成立的前提。

桶不是不能用,而是要分清”库 vs 业务”:库用桶 + sideEffects: false + 叶子化 + 纯函数 + 严格维护;业务用显式路径,或者直接用 Shadcn 模式从源头消除”库 vs 业务”的边界。

参考

  • [[开源项目分析/阅读/Harness工程-Multi-Agent架构实践|Harness Engineering]] — 阿里数据研发 Multi-Agent 工程实践,基础设施先行 + Harness 模式与 Medal 的 Macro→Micro 同源
  • [[前端/Typescript/Typescript|Typescript]] — export typeverbatimModuleSyntax 等语法层面的桶治理工具
  • [[前端/Typescript/tsconfig|tsconfig]] — isolatedModules + noUncheckedSideEffectImports 等可在编译期辅助发现副作用的配置
  • [[前端/Javascript/解决ESM Import 过多|解决ESM Import 过多]] — 与本文”显式深路径”思路同源,讨论 import 路径的工程化治理
  • [[Architecture/Monorepo/Lerna|Lerna]] — Medal 用的 PNPM monorepo 是同思路的现代实现,可对照参考