ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Slate v2 History 性能基线批次:`withHistory(createEditor())` 对比通道的落地与测量

Slate v2 History 性能基线批次:`withHistory(createEditor())` 对比通道的落地与测量 Slate v2 History 性能基线批次withHistory(createEditor())对比通道的落地与测量【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本文档基于仓库中的 2026-04-11-slate-v2-history-perf-batch.md 展开围绕 Slate v2 重写过程中历史记录undo/redo性能基线的建立与验收决策展开。文章以该计划文档为骨架结合当前仓库中的源码实现with-history.ts、history.ts、测试用例with-history.spec.tsx与基准目标配置slate-v2.json、slate-v2-latest.json进行深度扩充帮助读者理解Slate v2 的历史记录实现与旧版 Slate 在架构与合并启发式上的差异为什么“typing 合并启发式”是历史性能的生死线如何通过bench:history:compare:local对比通道量化 undo/redo 成本在“比旧版慢但不阻塞发布”的灰色地带如何用数据而非感觉做出验收决策。阅读本文前建议先了解 Slate v2 项目背景见 docs/slate-v2/master-roadmap.md本文聚焦于历史记录性能这一条具体验收通道。一、背景为什么需要一条withHistory(createEditor())的性能对比通道1.1 Slate v2 重写带来的历史记录不确定性Slate v2 是对 Slate 核心的重新实现其中 with-history.ts 对应的历史记录插件负责把每次操作写入 undo/redo 栈并支持将同类型、同路径的连续操作合并成单个批batch从而让“连续输入一整段文字”能够一次撤销。旧版 Slate 的slate-history经过多年打磨积累了成熟的合并启发式而 v2 重写后的历史实现属于新代码其行为与成本都需要重新验证。1.2 该批次的目标本计划文档2026-04-11-slate-v2-history-perf-batch.md明确列出的目标是Close the missingwithHistory(createEditor())perf lane for strict RC acceptance and stop hand-waving about history cost.即在严格 RCRelease Candidate验收前补上withHistory(createEditor())这条缺失的性能通道并停止对“历史记录到底多贵”的含糊其辞。该目标隐含两个验收维度行为正确undo/redo 必须能正确还原文档与选区成本可量化在大型文档上的 undo/redo 延迟必须能被测量、被记录、被比较而不是靠“应该还行吧”来敷衍。二、当前仓库中历史记录的实现骨架2.1withHistory的核心职责with-history.ts 中的withHistory装饰器做了三件事在编辑器上初始化history状态e.history { redos: [], undos: [] }拦截apply决定每个操作是否需要保存shouldSave、是否需要与上一个操作合并shouldMerge、以及是否强制开启新批setSplittingOnce提供undo/redo方法通过OperationApi.inverse反转操作实现撤销并在withoutSaving/withoutNormalizing的包裹下执行避免撤销本身再次写入历史或触发规范化。2.2 合并启发式性能与体验的生死线shouldMerge是历史记录性能的核心逻辑with-history.tsconst shouldMerge (op: Operation, prev: Operation | undefined): boolean { if ( prev op.type insert_text prev.type insert_text op.offset prev.offset prev.text.length PathApi.equals(op.path, prev.path) ) { return true; } if ( prev op.type remove_text prev.type remove_text op.offset op.text.length prev.offset PathApi.equals(op.path, prev.path) ) { return true; } return false; };其逻辑要点连续插入insert_text与上一个insert_text在同一路径、且新操作偏移量恰好等于上一个操作偏移量加文本长度时合并——即“在光标处连续打字”会合成一个批连续删除remove_text与上一个remove_text在同一路径、且新操作的结束位置恰好衔接上一个操作的开始位置时合并——即“连续按退格键”会合成一个批其余情况如切换路径、混合插入删除、结构变化一律不合并各自成批。2.3 保存策略与批管理shouldSavewith-history.ts只排除set_selection操作其余操作全部计入历史。apply内对批的处理逻辑为若当前处于withoutSaving如 undo/redo 执行期不写历史若处于withoutMerging强制不合并若处于withNewBatch/setSplittingOnce强制当前操作开启新批否则按shouldMerge判断并入上一个批每次写入后若undos超过 100 个批则从队首丢弃并清空redos——这是历史上限策略防止无限增长。HistoryApihistory.ts则提供了withMerging、withoutMerging、withNewBatch、withoutSaving等弱映射标记工具供上层插件按需控制历史行为。三、本次批次的具体工作3.1 新增对比通道history.mjs计划文档记录的“Kept Work”第一项是新增对比脚本history.mjs注意该路径是作者本地机器上的绝对路径在当前仓库中并不存在本仓库未包含该脚本文件。我们可以从仓库内的基准目标配置推断其职责——slate-v2.json 中登记了名为history-compare的基准目标{ id: history-compare, question: Does Slate v2 match legacy Slate for undo/redo typing and fragment history?, owner: slate-v2, family: history, kind: compare, cwd: .tmp/slate-v2, command: HISTORY_BENCH_LEGACY_REPO../../../slate bun run bench:history:compare:local, metrics: { primary: history_compare_worst_p95_ratio, direction: lower, unit: ratio, printsMetric: true }, correctness: { command: bun check, policy: Use the target-specific correctness command when one exists; bun check is the fallback before promotion. }, artifacts: [ { path: .tmp/slate-v2/tmp/slate-history-compare-benchmark.json, required: true } ], thresholds: { promotion: history_compare_worst_p95_ratio at or below 2.0 with bun check green, plateau: stop after 2 correctness-green packets with less than 5% gain } }从该配置可以推断history.mjs的职责是在同一进程内分别构建 Slate v2 的withHistory(createEditor())编辑器与旧版 Slate 的历史编辑器在相同的操作序列打字、粘贴 fragment上分别执行 undo/redo输出双方耗时计算最差 p95 比率history_compare_worst_p95_ratio并写入.tmp/slate-v2/tmp/slate-history-compare-benchmark.json作为验收产物。3.2 接通命令bun run bench:history:compare:local命令在基准目标配置中体现为HISTORY_BENCH_LEGACY_REPO../../../slate bun run bench:history:compare:local即通过环境变量HISTORY_BENCH_LEGACY_REPO指向旧版 Slate 仓库再运行bench:history:compare:local脚本。历史对比通道还出现在仓库的基准注册表benchmark-registry.json以及历史结果归档slate-v2-latest.json中说明该通道已进入正式基准管理流程。3.3 恢复旧版风格的 typing 合并启发式计划文档记录的第三项工作是restored legacy-style typing merge heuristics inwith-history.ts“恢复旧版风格的 typing 合并启发式”是本批次的核心修复。结合上文shouldMerge的实现恢复的正是“同路径连续 insert_text 合并 / 连续 remove_text 合并”这两条规则。在此之前v2 重写的历史实现可能缺少或改动了这些规则导致每一次按键都各自成批。四、测量结果现状与基线4.1 基准场景计划文档给出的测量场景为5000 blocks文档包含 5000 个块20 typed characters模拟连续输入 20 个字符200 inserted fragment blocks模拟一次插入包含 200 个块的 fragment。共四条测量通道typing undo、typing redo、fragment undo、fragment redo。4.2 当前版本Slate v2数据计划文档记录的最新一轮读数通道耗时typing undo29.71mstyping redo20.53msfragment undo27.21msfragment redo42.95ms4.3 旧版Legacy Slate数据通道耗时typing undo0.36mstyping redo0.49msfragment undo1.92msfragment redo11.18ms4.4 关键结论一v2 仍比旧版慢计划文档明确承认So yes, currentslate-historyis still slower than legacy.按上表计算typing undo 相差约 82 倍29.71ms vs 0.36msfragment redo 相差约 3.8 倍42.95ms vs 11.18ms。需要注意这些数字来自计划文档作者在 2026-04-11 当日的测量属于当时版本的快照并非当前仓库代码的实时结果当前仓库并未包含history.mjs脚本与其产物读者如需复现需自行搭建对比环境。该差距指向 v2 历史实现仍有优化空间例如批的selectionBefore/selectionAfter记录、逆操作计算、以及批结构本身的分配成本。4.5 关键结论二先修掉的才是真正的灾难计划文档记录的修复前数据是before restoring merge heuristics, typing-burst undo/redo was roughly~490ms在恢复合并启发式之前打字爆发场景typing-burst的 undo/redo 大约在490ms量级——这已经接近“用户可感知卡顿”的边界属于真正的 RC blocker。修复后同一通道回到20-40ms低延迟区间after the fix, the same lane is back in the low20-40msband这正是合并启发式价值的直接证明如果连续 20 次按键被拆成 20 个独立批undo 就要反转 20 组操作合并成一个批后undo 只反转一组操作。批数量从线性增长降为常数级undo/redo 的成本随之骤降。五、验收决策保留通道但不重新打开 RC5.1 结论计划文档的最终结论Verdict是Keep the lane.—— 保留该对比通道作为历史性能的长期监测Do not reopen RC on this alone.—— 不因“v2 比旧版慢”这一点单独重新打开 RC 流程。5.2 理由拆解计划文档给出的三条理由这是 headless 对比通道不是用户可见的浏览器回归本通道只测量withHistory(createEditor())在 headless 环境下的操作成本不涉及渲染、布局、输入法、滚动等用户可感知因素因此不属于浏览器端回归剩余成本虽真实存在但在 5000 块文档上仍处于几十毫秒的低延迟区间29.71ms、20.53ms、27.21ms、42.95ms 这些数字对用户操作来说仍然舒适不构成体验灾难“慢于旧版但不阻塞”的策略早有先例项目既有的 deep-interview blocker 政策已经写明——只要实际体验仍然舒适慢于旧版可以不作为阻塞项。5.3 与项目验收体系的衔接在仓库的基准目标配置中history-compare的 promotion 阈值是history_compare_worst_p95_ratio at or below 2.0 with bun check green即“最差 p95 比率 ≤ 2.0 且bun check通过”才允许晋升。而计划文档中的 4.4 节数据显示最差比率约为 82 倍远高于 2.0 阈值——这说明该目标在 2026-04-11 时点处于“未晋升但已登记、已测量、已监测”的状态符合“保留通道但不重新打开 RC”的决策逻辑。后续版本是否达到 promotion 阈值需要以实际基准产物.tmp/slate-v2/tmp/slate-history-compare-benchmark.json为准。六、从代码与测试验证历史行为6.1 单元测试覆盖的合并语义with-history.spec.tsx 用测试锁定了合并语义选区操作不入历史editor.select(...)后history.undos与history.redos均为空对应shouldSave排除set_selection连续 insertText 合并成一个批连续insertText(t)、insertText(w)、insertText(o)后history.undos长度为 1批内operations长度为 3一次undo()即可全部还原连续 remove_text 合并成一个批连续两次delete({ reverse: true })后history.undos长度为 1批内operations长度为 2一次undo()还原整个单词。这些测试正是“恢复 typing 合并启发式”的行为证据测试要求连续打字/删除必须合成单批否则undo步数会与按键次数一致行为与性能双双退化。6.2 撤销与重做的实现细节undo的实现with-history.ts值得注意对批内操作逐一OperationApi.inverse后reverse()得到逆操作序列再逐个apply整个过程包裹在withoutSaving与withoutNormalizing中避免撤销动作本身污染历史或触发规范化级联撤销后把该批连同撤销前的选区selectionAfter写回redos栈供redo使用。redo则是把redos栈顶批的操作原样重放并恢复selectionAfter ?? selectionBefore选区。七、如何在当前仓库中查看与运行该通道由于本仓库为只读状态且对比脚本scripts/benchmarks/core/compare/history.mjs并未包含在本仓库中读者可以阅读基准目标配置 slate-v2.json 了解history-compare目标的问题定义、命令、指标与阈值查看历史结果归档 slate-v2-latest.json 了解该目标的登记状态阅读 history.ts 与 with-history.ts 源码理解历史记录实现阅读 with-history.spec.tsx 与 history.spec.tsx 掌握合并语义的行为契约如需在自己搭建的对比环境中复现命令形态为HISTORY_BENCH_LEGACY_REPOlegacy-slate-path bun run bench:history:compare:local产物路径为.tmp/slate-v2/tmp/slate-history-compare-benchmark.json。八、总结本批次2026-04-11完成的核心工作是新增history-compare对比通道把withHistory(createEditor())的 undo/redo 成本从“口头估计”变成可重复测量的基准目标接通bench:history:compare:local命令进入正式基准管理流程并登记在 slate-v2.json 与 slate-v2-latest.json 中恢复旧版风格的 typing 合并启发式把 typing-burst undo/redo 从~490ms拉回20-40ms区间——这是把“RC blocker”降级为“严格验收证明行”的关键修复得出验收决策v2 历史实现仍慢于旧版但在 5000 块文档上处于几十毫秒舒适区间且属 headless 对比通道而非用户可见回归因此保留通道、不单独重新打开 RC。这条通道的价值在于它让“历史记录贵不贵”这个问题从此有了数字答案也让未来的优化如批结构、逆操作计算、选区快照有了可对比的基线。正如计划文档所说——这是“RC blocker”与“比旧版慢但依然舒适地快”之间的分界线。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表