ADR 0001:自研薄内核,不采用微前端框架
- 日期:2026-08-20
- 状态:已采纳
- 相关:
AIO-ARCHITECTURE.md、VIEWER-REFACTOR.md
问题
"从零搓一套" 还是 "基于现有体系写插件"?
这个问题在两层上各问一次,答案方向相反,所以要分开答。
判断原则
自研只在"没有现成东西替你解决"的地方;其余一律用现成的。 而衡量哪层值得自研,看的是"这层选错了,改起来贵不贵"。
决定一:编排层自研(薄内核)
不采用 qiankun / single-spa / Module Federation。
理由
它们解决的是另一个问题。 这类框架做的是"路由级子应用组合"—— URL → 整个子应用。那正是本项目第一版被推翻的缝合怪模型。 我们要的是能力级派发:一个剧情阅读器在自己的页面里就地唤起 ADV, 不换路由、不换应用。
我们真正需要的东西,它们一样都不提供。 意图总线、能力查询 (
can()决定按钮画不画)、资源层、实体交叉表、WebGL 上下文治理—— 这些全都要自己写。区别只是"写在一个 2 万行依赖之上"还是"直接写"。它们的沙箱比 iframe 弱,还更重。 Proxy 快照式的 window 沙箱挡不住 WebGL 上下文配额,也不能真正隔离
window.cc与window.Live2DCubismCore这类在原型链上做手脚的老库。iframe 是平台自带的、真正的 realm 隔离。Module Federation 与"版本 pin"冲突。 它靠共享依赖对齐版本获得收益, 而 Exedra ADV 刚刚花了一整轮把 Pixi 版本 pin 死。让插件各自带自己的依赖, 正是我们要的。
但"自研"有个陷阱:不要发明运行时
自研内核 ≈ 2,700 行,而且每个非平凡机制都是平台原语:
| 机制 | 用的是 |
|---|---|
| 模块隔离 | ES modules / <iframe> |
| 跨 realm 通信 | postMessage / MessagePort |
| 生命周期 | Promise + 显式 suspend/resume/dispose |
| 事件 | 40 行的 EventBus |
| 取消 | AbortController |
没有自己的模块加载器、没有自己的沙箱、没有自己的响应式系统。 凡是平台或标准库有的,一律不重造。这才是"从零搓"的正确形态—— 薄胶水,不是新运行时。
反向检查:什么情况下这个决定是错的
如果将来插件要由互不信任的第三方独立发布、独立版本、跨团队部署, 那 Module Federation 那套的价值会真实起来。目前所有插件都出自同一批仓库、 同一批维护者,这个前提不成立。前提变了要重新评估这条 ADR。
决定二:查看器不重写,基于现有代码改造
⚠️ 本节已被 ADR 0002 修订。 下面的理由仍然成立,但它回答的是「现在要不要重写」,不是「终局是什么」。 终局是一套契约化的系统,适配器是迁移桥而不是终点。先读 0002。
方向与决定一相反:这一层尽量不从零。
理由
它们的价值不在代码,在沉淀的结论。 那个 3D 查看器的 shader 是 45 个 CI 提交逆向出来的(RDToon、SoftMetallic MatCap、ReDrive GLSL 公式); Exedra 有 91 个测试把行为钉死在真机 AArch64 原生实现上。 重写等于把这些全丢掉,然后在真机上重新踩一遍。
实测下来根本不需要重写。 见
VIEWER-REFACTOR.md:四个查看器分别 只需要"换一个参数 / 加一个入口 / 拦一层请求",没有一个是重写。 那个查看器的构造函数本来就是注入式的,文档里还专门举了传任意 URL 的例子。不需要它干净才能变成插件。 只需要两条缝——资源从哪来、怎么告诉它显示 什么。适配层吸收剩下的脏,之后再增量重构,那时适配层的测试就是安全网。
成本对比(这才是关键)
| 工作量 | |
|---|---|
| 内核 | ~2,700 行,已完成 |
| 四个查看器适配 + 交叉表补全 | 30–50 人日 |
内核是便宜的那部分。 在编排层上纠结选型是在优化错的东西; 真正该优化的是"怎么让查看器改动最小"。而答案恰恰是薄内核—— 它允许每个查看器保留自己的构建、自己的依赖、自己的版本 pin。
具体到每一层用什么
| 层 | 决定 |
|---|---|
| 构建 | 用 Vite,不自研 |
| 模块隔离 | iframe + MessagePort(平台原语),不用 Proxy 沙箱 |
| 微前端框架 | 不用 |
| 事件/状态 | 自研 40 行 EventBus,不上 Redux / RxJS |
| 资源层 | 自研——这个需求形状没有现成品 |
| 实体交叉表 | 自研——纯数据,且必须人工核对 |
| UI 组件 | 用现成的,但必须能被四种技术栈共用 → CSS 变量 + Web Components,不能是 React 组件库 |
| 测试 | vitest,不自研 |
最后一行值得单说:宿主里同时活着 three.js、Pixi、cocos2d、Cubism 四条运行时, 任何绑定单一框架的组件库都会把其中三个排除在外。共享 UI 只能落在 CSS 自定义属性与 Web Components 这一层。