协同编辑与一致性策略
1. 协同层定位
Tiptap 协同扩展(@tiptap/extension-collaboration)本质是把 Yjs / y-prosemirror 能力接入到 Tiptap 的扩展体系中,而不是重写一套 CRDT。
核心文件:packages/extension-collaboration/src/collaboration.ts
2. 插件组合
协同扩展主要注入三类插件:
ySyncPlugin:文档同步。yUndoPlugin:协同历史(区别于本地 history)。- 可选
filterInvalidContent插件:内容校验开关时拦截非法同步数据。
3. 为什么与 undoRedo 不兼容
扩展在 onCreate 明确警告:
- 协同场景已有 Yjs UndoManager。
- 再叠加
@tiptap/extension-undo-redo会出现双历史语义冲突。
这是典型“同一职责只能有一个真相源(source of truth)”。
源码片段(packages/extension-collaboration/src/collaboration.ts):
ts
if (this.editor.extensionManager.extensions.find(extension => extension.name === 'undoRedo')) {
console.warn(
'"@tiptap/extension-collaboration" comes with its own history support and is not compatible with "@tiptap/extension-undo-redo".',
)
}4. 命令层的协同适配
协同扩展覆写 undo/redo 命令时,会先:
tr.setMeta('preventDispatch', true)避免走默认 dispatch 路径。- 检查 UndoManager 的栈是否为空。
- 真正执行
undo(state)/redo(state)。
这保证了命令 API 与协同事务模型一致,不会重复 dispatch。
5. 事务位置映射的重写
在 onBeforeCreate 中,协同扩展会替换 editor 的位置映射工具:
editor.utils.getUpdatedPositioneditor.utils.createMappablePosition
目的是让位置映射考虑协同事务语义,减少远端插入后的光标/锚点错位。
6. 内容安全阀(内容检查开启时)
当 enableContentCheck 开启:
- 在 Yjs
beforeTransaction阶段把 fragment 转 JSON 并用 schemacheck()校验。 - 非法内容触发
contentError,并支持disableCollaboration。 - 可销毁 Yjs doc 防止继续同步污染。
这在“弱信任协同输入”场景很关键。
7. 真实问题与修复证据
- Issue: #1973(早期协同加载异常)
- Changelog 中多次出现 collaboration/caret 稳定性修复
- 代码内还保留对 y-prosemirror 既有问题的兼容注释(issues/102, 114)
可总结为:协同层不是一次性完成,而是持续对第三方库行为做适配收敛。
8. 如何讲清“协同一致性”
建议结构:
- 一致性模型由 Yjs 保证,Tiptap 负责接入层语义。
- 命令与事务元数据(
preventDispatch)避免本地链路冲突。 - 内容校验 + disable 开关防止无效状态扩散。
- 插件顺序、历史系统、位置映射是协同场景三大高风险点。