Skip to content
目录

扩展系统设计(Extension/Node/Mark)

1. 扩展模型总览

Tiptap 把扩展分成三类:

  • Extension:通用能力扩展(命令、插件、事件、规则等)。
  • Node:映射 ProseMirror NodeSpec + NodeView。
  • Mark:映射 ProseMirror MarkSpec + MarkView。

三者共享同一个基础类 Extendable,这就是 Tiptap 可扩展性的核心骨架。

2. Extendable 的几个关键点

位置:packages/core/src/Extendable.ts

2.1 配置继承链

  • extend():创建子扩展,并建立 parent/child 链。
  • configure():基于现有扩展合并 options,返回新实例。

2.2 parent 调用能力

配置中的 hook 能访问 parent,因此可做“在父行为上增强”而不是完全重写。
这让 StarterKit/二次封装扩展成为可维护模式。

2.3 全能力接口集中

一个扩展可声明:

  • addCommands
  • addKeyboardShortcuts
  • addInputRules / addPasteRules
  • addProseMirrorPlugins
  • transformPastedHTML
  • 生命周期事件(onCreate/onUpdate/...
  • Markdown 相关能力(parseMarkdown/renderMarkdown/markdownTokenizer/...
  • dispatchTransaction(v3.15 新增)

3. ExtensionManager:组合与执行顺序控制中心

位置:packages/core/src/ExtensionManager.ts

3.1 解析流程

  1. resolveExtensions:扁平化 + 排序 + 重名告警。
  2. 根据扩展构建 schema。
  3. 汇总 commands/plugins/nodeViews/markViews。
  4. 把扩展 hook 绑定到 editor 事件总线。

3.2 顺序策略(重点)

  • ProseMirror 插件数组“前面的先执行”。
  • 为了符合 Tiptap “后定义可覆盖前定义”的直觉,内部会 reverse + priority sort。
  • priority 越高越先执行(默认 100)。

这解释了为什么某些冲突行为要靠 priority 调整,而不是仅靠数组顺序。

源码片段(packages/core/src/ExtensionManager.ts):

ts
// ProseMirror 前面的插件先执行
// Tiptap 里为了更自然的“覆盖感”,先 reverse 再按 priority 排序
const extensions = sortExtensions([...this.extensions].reverse())

4. configure/extend 继承陷阱与修复

典型案例:

  • Issue/PR: #5147
  • 问题:某些情况下 configure() 触发的继承关系导致 hook 重复或行为叠加异常。
  • 设计目标:configure 应像“基于当前实例复制并覆盖 options”,而不是错误地制造重复父子语义。

这个案例是说明时解释“插件系统为何难”的很好素材:继承链设计不当会在插件生命周期层面放大副作用

5. StarterKit 体现的“组合优于继承”

位置:packages/starter-kit/src/starter-kit.ts

StarterKit 并非硬编码大对象,而是通过 addExtensions() 动态 push 子扩展,并支持每个子项 false 关闭或局部配置覆盖。

价值:

  • 避免“超级配置对象”膨胀。
  • 让业务可按需裁剪(比如协同场景禁用 undoRedo,改用协同历史)。

6. 回答模板

如果被问“为什么 Tiptap 扩展系统好用”:

  1. 能力统一入口(commands/plugin/rules/events/nodeview)。
  2. 顺序可控(priority + manager 组合逻辑)。
  3. 继承可组合(extend/configure + parent)。
  4. 框架无关(core 层不绑定 React/Vue)。
  5. 高阶能力也通过扩展落地(协同、Markdown、Suggestion),不是特例分支。

MIT License.