Plate 编辑器性能基准的 Memory 与 DOM Tagging 规则:时序之外的堆、节点与监听器压力门禁
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
导读
本文讲解 Plate 仓库性能规则体系中的Memory And DOM Tagging(内存与 DOM 标签)规则。它解决的是富文本编辑器性能基准中最容易被忽视的问题:一个交互基准如果只报"多快"而不报"多耗",就可能被"悄悄爆炸内存换时序"的假优化蒙混过关。读完本文,你将掌握这套规则要求的 10 类内存/DOM 压力标签的含义、如何在 Plate 的性能基准体系(performance-benchmark-spec.md)中落地这些标签,以及它与 degradation-contract.md、interaction-inp-matrix.md 等相邻规则如何共同构成编辑器性能验收门禁。
规则原文位于 .agents/rules/performance/rules/memory-dom-tagging.md,全文虽然精炼,却是编辑性能工作流(尤其是大规模文档、虚拟化、staged mounting 等方向)的强制验收条款。
规则定位:什么时候必须启用
原规则的第一句给出了清晰的适用条件:
Use this when a lane may explode heap, DOM nodes, component instances, listeners, or cached indexes.
即:当一个基准 lane(benchmark lane)可能撑爆堆内存(JS heap)、DOM 节点、React 组件实例、事件监听器或缓存索引时,就必须启用这套标签体系。
这正好对应 Plate 性能基准中的两类典型风险场景:
- 压力类 / 大规模文档类 lane:如
04_mount-50k、08_remove-single-block、55_typing-churn-memory、56_paste-clear-memory、57_history-churn-memory、58_table-selection-memory(lane 定义见 performance-benchmark-spec.md)。文档越大,DOM 节点数、组件实例数与缓存索引就越容易失控。 - 引入了隐藏/虚拟化渲染机制的 lane:Plate 的 slate-v2 路线中存在
hidden subtree、staged DomPresent、virtualized等渲染模式。这类模式天然以"减少 DOM 节点"为目标,正是最需要 memory/DOM 标签来证明"省了 DOM 但没爆堆"的场景。
核心规则:没有标签的时序是不完整的
Timing without memory and DOM tags is incomplete for repeated editor surfaces.
这针对的是"重复编辑器表面"(repeated editor surfaces)——即在同一个编辑器上反复执行的输入、选择、粘贴、撤销/重做等操作。原因很直接:
- 编辑器是长生命周期应用,DOM 节点、监听器、缓存一旦累积,不会随单次操作结束而自动回收;
- 只测单次时序,无法区分"这次快是因为算法好"还是"这次快是因为把工作推迟/卸载到了别处";
- 对于反复执行的表面(例如在 5000 block 文档上反复输入),内存与 DOM 压力会逐次叠加,最终表现为卡顿、掉帧甚至崩溃。
因此规则要求:凡是涉及重复编辑表面的基准,时序指标(latency)必须与内存/DOM 压力指标(memory/DOM pressure)成对记录。
10 类标签逐项解读
原规则给出了完整的标签清单,下面逐项说明其含义与在编辑器基准中的观测点:
| 标签 | 观测对象 | 典型来源 |
|---|---|---|
| JS heap | 浏览器 JS 堆内存占用 | 编辑器模型、操作栈、缓存对象、快照 |
| DOM node count | 当前挂载的 DOM 节点总数 | 块节点、叶子节点、装饰(decoration)渲染产物 |
| React component count proxy | 以 React 组件实例数作为 DOM 压力的代理指标 | 每个 Element/Leaf/装饰组件实例 |
| mounted group/island count | 已挂载的组/岛(group/island)数量 | 根组、shell 岛、可编辑区域(editable island) |
| event listener count | 事件监听器数量 | 每个根节点、菜单、浮动工具栏注册的监听器 |
| cached range/index sizes | 缓存的 Range / 索引大小 | 选区缓存、路径索引、脏块索引 |
| dirty id set sizes | 脏 id 集合大小 | 待重渲染/待归一化的节点 id 集合 |
| hidden boundary count | 隐藏边界数量 | 隐藏子树、staged 未挂载区域的边界 |
| decoration/comment/annotation count | 装饰/评论/注解数量 | 搜索高亮、拼写标记、评论锚点等 |
| custom renderer flags | 自定义渲染器标志 | 是否走自定义 renderer 分支的标志位 |
这 10 个标签共同覆盖了富文本编辑器内存压力的三个层面:
- 内容层:DOM 节点数、React 组件数、装饰/注解数——由文档内容与渲染策略决定;
- 状态层:缓存的 Range/索引、脏 id 集合、隐藏边界数——由编辑器的增量更新与归一化机制决定;
- 基础设施层:JS heap、事件监听器数、挂载的组/岛数——由应用骨架与插件生态决定。
标签与基准 lane 的绑定:Memory Suite 的落地方式
标签不是孤立记录的数字,必须绑定到具体的基准 lane 上。Plate 的 performance-benchmark-spec.md 用专门的Memory Suite定义了 8 条带稳定 id 的内存 lane:
51_ready-memory:路由加载后、文档挂载前的内存基线52_mount-1k-memory/53_mount-10k-memory/54_mount-50k-memory:不同文档规模挂载后的内存55_typing-churn-memory:已挂载文档上的反复输入循环56_paste-clear-memory:反复大段粘贴 + 清空循环57_history-churn-memory:反复 编辑/撤销/重做 循环58_table-selection-memory:表格选区操作
这些 lane 覆盖了原规则强调的"repeated editor surfaces":55/56/57三条都是反复执行同一类操作,正是观测内存/DOM 压力逐次累积的窗口。
每条内存 lane 的结果必须包含三个指标:
- used heap:当前已用堆内存;
- heap delta:操作前后的堆增量;
- retained delta after idle:空闲等待后的保留增量——这是判断"是否泄漏"的关键,因为临时垃圾会在 idle 后被回收,而真正泄漏的对象会持续保留。
从仓库的基准健康检查产物 benchmark-health-latest.json 可以看到该体系的运行状态:它维护了一个 artifact 注册表(registry),其中有 21 个必选 artifact 与 2 个可选 artifact,其中就包括可选 lanehistory-retained-memory(对应命令bun run bench:core:history-retained-memory:local,产物slate-history-retained-memory-benchmark.json)。这个 lane 名字里的 "retained" 直接对应 spec 中的retained delta after idle,是"历史栈保留内存"这一具体泄漏风险的落地观测。
Gate:时序与压力必须同时过关
原规则的 Gate 是全篇的验收条款:
For every large/stress benchmark, record both latency and memory/DOM pressure. Do not accept a mode that improves timings by quietly exploding memory.
翻译成可执行的验收标准:
- 每个大规模/压力基准都必须成对记录:latency 与 memory/DOM pressure 缺一不可;
- 禁止"隐性交换":任何渲染模式(staged mounting、虚拟化、shell 模式等)如果只是把时序做漂亮而内存/DOM 压力暴涨,一律不接受;
- 验收以对账为准:提交一个模式变更时,必须能拿出"同一 lane 的时序 + 同一 lane 的内存/DOM 标签"两组数据,证明改进不是靠卸载或延迟分配换来的。
这与 degradation-contract.md 的立场完全一致:降级(degradation)必须显式声明改变了哪些原生行为,且不能把降级模式包装成"同一个编辑器,只是更快"。Memory/DOM 标签正是戳穿这种包装的证据工具。
与相邻性能规则的协作
memory-dom-tagging 不是孤立规则,它与.agents/rules/performance/rules/目录下的其他规则共同构成编辑性能验收体系:
- interaction-inp-matrix.md:负责交互级 p50/p75/p95/p99 延迟矩阵,按队列和模式分组——它回答"响应多快",memory-dom-tagging 回答"代价多大";
- degradation-contract.md:要求每个降级模式显式记录队列阈值、浏览器查找、屏幕阅读器、选区、剪贴板、IME、移动端、撤销历史、协作等行为的改变——它回答"牺牲了什么",memory-dom-tagging 回答"牺牲了多少资源";
- repeated-unit-budget.md(同目录):为重复单元(如每个根组、每个可编辑后代)设定预算,防止虚拟化在预算耗尽之前就被当作默认方案。
在 Plate 的 slate-v2 大规模文档计划中,这些规则是成套引用的。例如 2026-05-03-slate-v2-dom-present-large-doc-phase-6-plan.md 的 Performance Pass 一节明确列出的指标集包括:
metrics: startup, typing, select+type, full replace visible commit,
interactiveReady,nativeSurfaceComplete,DOM nodes, editable descendants, root groups, shell count, heap where available
其中DOM nodes、editable descendants、root groups、shell count、heap正是 memory-dom-tagging 标签清单在真实计划中的实例化——它同时覆盖了 DOM 节点数、挂载组/岛数、隐藏边界(shell count)与 JS heap 四类标签。该计划同样注明 "extra rules used: cohort segmentation, repeated-unit budget, interaction-INP matrix,memory/DOM tagging, degradation contract, staged-readiness",证明该规则是 slate-v2 DOM-present 大规模文档方向的强制验收项。
落地检查清单
在实际执行基准与性能评审时,可以按以下清单核对本规则是否被满足:
- 场景判定:当前 lane 是否属于"大文档 / 压力 / 重复表面"?若是,必须启用标签;
- 标签完整性:是否覆盖了内容层(DOM 节点、组件数、装饰数)、状态层(缓存 Range/索引、脏 id 集、隐藏边界)与基础设施层(heap、监听器、挂载组/岛)三类中的相关项?
- 成对记录:是否同时记录了 latency 与 memory/DOM pressure,且二者绑定到同一 lane id(如 Memory Suite 的
51–58)? - 保留量观测:是否记录了
used heap、heap delta与retained delta after idle三个指标,以区分临时垃圾与真实泄漏? - Gate 对账:如果一个渲染模式声称时序提升,是否有证据证明内存/DOM 压力没有同步恶化?是否有违反 degradation-contract.md 的隐性交换?
- 产物可复现:内存/DOM 标签是否进入基准 artifact(如
*retained-memory-benchmark.json)并注册到 benchmark-health-latest.json 这样的健康检查 registry,而不是只出现在临时本地运行中?
小结
Memory And DOM Tagging 规则为 Plate 的性能基准补齐了"正确性的另一半":时序数字只能说明编辑器跑得多快,而 JS heap、DOM 节点数、事件监听器、缓存索引等 10 类标签才能说明编辑器在跑的过程中累积了多少负担。对于富文本编辑器这种长生命周期、高重复交互、大文档压力的应用,只有 latency 与 memory/DOM pressure 成对通过,一个"更快"的渲染模式才有资格进入验收——否则再漂亮的时序也可能只是内存爆炸前的短暂闪光。
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考