@tiptap/extensions 深度解读:Tiptap 统一工具扩展包的组成、迁移方式与演进脉络

发布时间:2026/9/10 21:20:39
@tiptap/extensions 深度解读:Tiptap 统一工具扩展包的组成、迁移方式与演进脉络 tiptap/extensions 深度解读Tiptap 统一工具扩展包的组成、迁移方式与演进脉络【免费下载链接】tiptapThe headless rich text editor framework for web artisans.项目地址: https://gitcode.com/GitHub_Trending/ti/tiptap导读tiptap/extensions是 Tiptap 仓库在 v3 时代推出的“聚合型”扩展包它把History撤销/重做、Placeholder、CharacterCount、DropCursor、GapCursor、TrailingNode、Focus、Selection等八个常用工具扩展收敛到单一包名下帮助开发者用一次安装、统一版本号完成以往需要依赖多个独立包才能实现的能力。本文以该包的 CHANGELOG 为骨架结合 包内源码 与 旧版独立包目录梳理包的组成、逐项迁移路径、每类扩展的可用选项以及版本演进中值得关注的 bug 修复与性能改造读者读完后能直接完成从旧包到新聚合包的迁移并理解各选项在底层 ProseMirror 插件中的真实作用。一、包定位为什么需要tiptap/extensions从 CHANGELOG 的 3.0.1 版本major change提交bfec9b2可以看到这个包的来源说明Adds the newtiptap/extensionspackage which packages multiple utility extensions likeHistory,Placeholder,CharacterCount,DropCursor,GapCursor,TrailingNode,Focus, andSelection.也就是说它是一个聚合包aggregate package既不重复实现 Node/Mark 扩展也不引入新的 UI 依赖只是把分散在tiptap/extension-character-count、tiptap/extension-dropcursor、tiptap/extension-gapcursor、tiptap/extension-history、tiptap/extension-placeholder、tiptap/extension-focus等旧独立包中的能力统一收纳。这一点在源码层面可以直接验证package.json 中包名为tiptap/extensions仓库内当前版本为3.30.3与tiptap/core、tiptap/pm的版本严格对齐声明了对tiptap/core、tiptap/pm的 peerDependencies工作区版本同时提供子路径导出subpath exports允许按需引入tiptap/extensions/character-count、/drop-cursor、/focus、/gap-cursor、/undo-redo、/placeholder、/selection、/trailing-node便于 tree-shaking 与按需打包。入口文件 的导出完全与包名一一对应export * from ./character-count/index.js export * from ./drop-cursor/index.js export * from ./focus/index.js export * from ./gap-cursor/index.js export * from ./placeholder/index.js export * from ./selection/index.js export * from ./trailing-node/index.js export * from ./undo-redo/index.js说明CHANGELOG 的早期 release3.0.0-next.6将该包描述为 which holds utility extensions随后的稳定版本统一为上述八类扩展。此外同一版本还提到pnpm package aliases用于 monorepo 版本锁定、以及强制 type imports便于 bundler 在生成 dist/index.js 时剔除纯类型导入等工程化改动这些不会影响业务侧 API。从仓库结构看被替代的旧独立包被移入 packages-deprecated 目录extension-character-count、extension-dropcursor、extension-focus、extension-gapcursor、extension-history、extension-placeholder等可以从实现层面印证“迁移到tiptap/extensions”确实是仓库推荐的演进方向。二、八个聚合扩展逐个拆解2.1 CharacterCount字数/词数统计与硬性限制Changelog 中 CharacterCount 的官方描述为“添加一个光标用于指示拖放时新节点将插入的位置”但结合 源码 可以看出该描述明显是从 DropCursor 条目复制而来changelog 中的笔误其真实职责是统计文档字符数/词数并在超出限制时拦截或裁剪输入同时把计数能力暴露在storage.characterCount中供 UI 读取。迁移方式CHANGELOG 原始 diff- import CharacterCount from tiptap/extension-character-count import { CharacterCount } from tiptap/extensions类型导入import { CharacterCount, CharacterCountOptions } from tiptap/extensions;可选配置项来自 character-count.ts 的类型定义与addOptions默认值选项类型默认值作用limitnumber \| nullnull允许的最大字符数0/null/undefined均表示不限制modetextSize \| nodeSizetextSizetextSize使用node.textBetween(...)的文本内容计算nodeSize使用 ProseMirror 节点大小计算autoTrimbooleantrue当程序化设置的内容超过限制时是否自动裁剪3.23.4 新增详见下文textCounter(text: string) number(text) text.length自定义字符计数函数如基于Intl.Segmenter统计wordCounter(text: string) number按空格分词计数自定义词数统计函数它的核心机制并非简单“算个数”而是通过一个 ProseMirror 插件同时控制两类行为见 filterTransaction 与 appendTransactionfilterTransaction在每次输入事务到达前校验新旧字符数。超过上限的非粘贴事务会被直接拦截返回false对粘贴内容则尝试删除超出部分transaction.deleteRange若裁剪后仍超限则整体拒绝appendTransaction在编辑器创建初期检测初始内容是否已经超限例如setContent注入超长内容超限时发出console.warn并从文档开头删除多出的字符。开发者可通过storage.characterCount.characters()/.words()在 UI 上实时展示计数也可传入自定义 node 与 mode 按指定范围计算。2.2 DropCursor拖放时的插入位置指示线官方能力为拖放操作添加一条可视光标指示新节点将被插入的位置CHANGELOG 原文adds a cursor that indicates where a new node will be inserted when dragging and dropping。该能力底层直接来源于 ProseMirror 的 dropcursor 插件在本聚合包内被包装成 Tiptap 扩展。迁移原始 diff- import DropCursor from tiptap/extension-dropcursor import { DropCursor } from tiptap/extensionsimport { DropCursor, DropCursorOptions } from tiptap/extensions;2.3 GapCursor空白区域的占位光标官方能力当点击文档中没有内容的区域例如两个块级节点之间时出现一个光标让用户可以在“缝隙”中定位并开始输入CHANGELOG 原文a cursor that appears when you click on a place where no content is present, for example in-between nodes。迁移原始 diff- import GapCursor from tiptap/extension-gapcursor import { GapCursor } from tiptap/extensionsimport { GapCursor } from tiptap/extensions;GapCursor 是诸多块级交互点击表格/图片之间的空隙后回车换行的基础设施通常建议与 DropCursor 一起加入编辑器的extensions数组。2.4 History / UndoRedo撤销与重做CHANGELOG 将该条目命名为Historyadds undo and redo functionality to the editor但迁移后的新包入口实际暴露的扩展名是UndoRedo见 undo-redo/index.ts 与 undo-redo.ts其内部包装的正是 ProseMirror 官方的history插件。在 3.0.0-next.6 的早期描述中还出现过HistoryOptions命名最终稳定版统一为UndoRedoOptions引用源码时建议以UndoRedo为准。迁移原始 diff- import History from tiptap/extension-history import { History } from tiptap/extensionsimport { UndoRedo, UndoRedoOptions } from tiptap/extensions;配置项来自 undo-redo.ts选项类型默认值作用depthnumber100保留的历史事件数量超出后丢弃最旧记录newGroupDelaynumber500两次变更之间间隔超过该毫秒数后开启新的撤销分组UndoRedo还自动注册了undo/redo命令与快捷键Mod-z撤销、Shift-Mod-z与Mod-y重做并且额外处理了Mod-я等俄语键盘布局见 addKeyboardShortcuts。需要特别留意的是源码中的兼容性警告undo-redo.ts 注释如果项目同时使用tiptap/extension-collaboration必须移除undo-redo因为协作扩展自带独立的 Yjs 历史实现两者叠加会互相干扰。2.5 Placeholder空状态占位提示官方能力当编辑器或某个节点为空时展示一段占位文本CHANGELOG 原文adds a placeholder text to the editor, which is displayed when the editor is empty。迁移原始 diff- import Placeholder from tiptap/extension-placeholder import { Placeholder } from tiptap/extensionsimport { Placeholder, PlaceholderOptions } from tiptap/extensions;默认选项见 placeholder.ts选项类型默认值作用emptyEditorClassstringis-editor-empty整个编辑器为空时附加到根节点的 classemptyNodeClassstringis-empty单个空节点上附加的 class3.25.0 起可传函数按节点返回动态 classdataAttributestring见DEFAULT_DATA_ATTRIBUTE用于承载占位文案的 data 属性名3.18.0 新增placeholderstring \| ((node) string)Write something …占位文本或按节点动态生成的函数showOnlyWhenEditablebooleantrue编辑器处于只读/不可编辑时不展示占位符showOnlyCurrentbooleantrue仅当光标位于该空节点时才展示includeChildrenbooleanfalse是否覆盖嵌套的空子节点与早期独立版本相比稳定版的 placeholder 是 CHANGELOG 中修复最密集的模块其演进详见第 4 节建议基于视口增量扫描而非全文档遍历的机制在性能上对超大文档尤其关键。2.6 TrailingNode文档末尾的兜底节点官方能力始终在文档结尾保留一个节点如段落保证用户把内容删光后依然有可输入的位置CHANGELOG 原文adds a node at the end of the editor, which can be used to add a trailing node like a paragraph。import { TrailingNode, TrailingNodeOptions } from tiptap/extensions;配置项见 trailing-node.ts选项类型默认值作用nodestringundefined期望插入的节点类型名省略时从 schema 顶部节点类型的defaultType推导最后兜底paragraph3.7.0 起可选notAfterstring \| string[][paragraph]这些节点之后不再强制插入尾节点防止无限循环实现细节值得注意appendTransaction 与 state.apply通过appendTransaction在每次事务后检查doc.lastChild类型不符合预期时在doc.content.size位置插入新节点插件 state 会忽略带__uniqueIDTransactionmeta 的事务避免与 UniqueID 扩展互相触发形成死循环对应 3.10.5 的修复支持通过事务 metaskipTrailingNode跳过本次插入3.20.5 新增见 skipTrailingNodeMeta 导出例如编程式内容替换时不想自动补尾节点。2.7 Focus光标所在节点的聚焦高亮官方能力当编辑器获得焦点时为光标所在的节点附加 class便于围绕“当前编辑位置”做视觉高亮如大纲编辑器中的当前段落描边。import { Focus, FocusOptions } from tiptap/extensions;配置项见 focus.ts选项类型默认值作用classNamestringhas-focus附加到聚焦节点的 classmodeall \| deepest \| shallowestallall高亮光标路径上的所有节点deepest只高亮最内层节点shallowest只高亮最外层包含节点实现上它利用props.decorations当!isEditable || !isFocused时返回空DecorationSet因此只读状态与失焦状态下不会产生任何高亮装饰见 focus.ts。2.8 Selection失焦后的自定义选区样式官方能力当编辑器失去焦点时用可自定义 class 的 decoration 代替浏览器原生选区让“内容被选中”的状态在失焦后依然可见CHANGELOG 原文adds a selection state to the editor, which can be used to style the editor when theres a selection。import { Selection, SelectionOptions } from tiptap/extensions;配置项见 selection.tsclassName默认selection作用于选区内联文本的 decoration class。该扩展的行为约束在源码中有非常清晰的定义selection.ts仅在非空文本选区、非 node selection、且编辑器可编辑时同步原生选区shouldSyncDomSelection仅当编辑器失焦且当前没有拖拽时才渲染.selectiondecorationshouldPreserveSelection避免原生::selection样式与 decoration 重叠失焦时调用window.getSelection()?.removeAllRanges()清除原生高亮重新聚焦后通过view.focus()恢复——这正是 3.26.0 与 3.27.4 两次针对“多行选区高亮溢出、失焦后原生选区与 decoration 重叠”问题的修复落地方式处于拖拽editor.view.dragging时不保留装饰对应 CHANGELOG 中ce47182Remove selection decoration when editor is on dragging mode与 minor change52b6644skip decorations for node selection and non editable editor。三、迁移实操从旧独立包切到聚合包3.1 替换 import 语句官方 diff 汇总以下 diff 均完整摘自 CHANGELOG 3.0.1 的发布说明可直接套用# CharacterCount - import CharacterCount from tiptap/extension-character-count import { CharacterCount } from tiptap/extensions # DropCursor - import DropCursor from tiptap/extension-dropcursor import { DropCursor } from tiptap/extensions # GapCursor - import GapCursor from tiptap/extension-gapcursor import { GapCursor } from tiptap/extensions # History - import History from tiptap/extension-history import { History } from tiptap/extensions # Placeholder - import Placeholder from tiptap/extension-placeholder import { Placeholder } from tiptap/extensions # Focus - import Focus from tiptap/extension-focus import { Focus } from tiptap/extensionsTrailingNode与Selection在旧独立生态中并不存在对应的稳定独立包属于随聚合包一起提供的扩展直接引入即可。3.2 安装与整体示例假设项目使用 pnpm 与 Tiptap v3安装后即可在Editor中组合使用import { Editor } from tiptap/core import StarterKit from tiptap/starter-kit import { CharacterCount, DropCursor, Focus, GapCursor, Placeholder, Selection, TrailingNode, UndoRedo, } from tiptap/extensions const editor new Editor({ element: document.querySelector(#editor)!, extensions: [ StarterKit, // 依赖 tiptap/pm 的 ProseMirror 官方实现 UndoRedo.configure({ depth: 100, newGroupDelay: 500 }), Placeholder.configure({ placeholder: Write something … }), CharacterCount.configure({ limit: 280 }), TrailingNode, Focus.configure({ className: has-focus, mode: all }), Selection.configure({ className: selection }), DropCursor, GapCursor, ], content: pHello Tiptap/p, }) // 读取实时计数 const count editor.storage.characterCount.characters()对应示例可在 demos/src/Examples/Extensions 目录下找到独立可运行的 demo如UndoRedo、Placeholder、CharacterCount、Focus、Selection、TrailingNode等子目录每个 demo 目录内包含对应框架的组件源码与样式。旧包源码仍保留在 packages-deprecated便于需要对照实现的场景查阅。四、CHANGELOG 中的关键演进从“能用”到“稳定”CHANGELOG 的价值不只在于版本号下面这些带提交号的条目直接反映了聚合包在真实业务场景下踩过的坑与解法可帮助开发者理解何时需要升级、升级后行为有何变化。4.1 CharacterCount 的能力补充3.23.457e53c1新增autoTrim选项。默认true会在程序化设置超限内容时自动裁剪设为false后允许内容临时超过limit由开发者调用裁剪或仅做校验告警给“字数仅提示不拦截”的场景留出余地。4.2 Placeholder 的持续性能与稳定性改造这是 CHANGELOG 中投入最集中的区域按时间线看是一条清晰的“优化-修复-重构”链路版本内容解决的核心问题3.23.6937ff2e将全文档doc.descendants()遍历替换为“光标快速路径默认配置 视口受限扫描非默认配置”大文档 decoration 计算开销过高3.24.02d05614把视口重算推迟到 rAF、加入 overscan 边距、节流滚动更新协作编辑下的占位符闪烁与滚动时 CPU 占用3.27.12be3fb9当编辑器被遮挡如模态框打开无法可靠测量时“冻结”视口窗口被遮挡时反复切换data-placeholder导致的闪烁3.27.376a76da用增量StateFieldDecorationSet替换基于视口的 decoration 扫描只对每个事务涉及的顶层节点重算彻底移除对 DOM 测量posAtCoords、rAF 调度和滚动监听器的依赖协作/遮挡/高频编辑多重场景下的闪烁与卡顿此外两项配置级增强3.18.0a65e55d新增dataAttribute可自定义承载占位文案的 data 属性名默认值定义于 constants.ts3.25.0MinoremptyNodeClass允许传入函数按节点类型/状态返回不同的动态 class。4.3 TrailingNode 的兼容性修复3.7.07cf3ab6node选项变为可选解析优先级调整为“schema 顶层节点默认类型 配置的nodeparagraph兜底”修复了自定义顶层默认节点项目的尾节点类型错误3.11.1eea7190修复 TrailingNode 未按node选项推断默认节点类型的问题3.10.59d5aeb1修复 UniqueID 与 TrailingNode 同时启用时产生的无限事务循环导致浏览器标签页冻结的问题源码中对应忽略__uniqueIDTransactionmeta 事务的判断3.20.50c2bbfe支持设置skipTrailingNode事务 meta 跳过本次尾部节点插入。4.4 Selection 的视觉一致性打磨3.6.37024d69改用正确的SelectionOptions类型使配置项获得准确类型提示3.26.075e8404/3.27.4d2983cd失焦时隐藏原生浏览器选区、聚焦时恢复让.selectiondecoration 与原生::selection不再重叠多行选区的显示更干净。4.5 工程化与版本同步3.30.x~3.0.x 之间的大量条目为“Updated dependencies [hash] - tiptap/core / tiptap/pm”说明聚合包严格跟随 core/pm 发布节奏跨扩展之间没有隐性版本漂移这正是聚合包相对“各装各的旧包”的最大工程收益3.0.1 同时收录了 pnpm 版本别名锁定、强制 type-only import、beta 与稳定版特性同步等仓库级工程改动。五、总结与升级建议若你正在维护一个同时安装了tiptap/extension-placeholder、tiptap/extension-character-count等独立扩展的 Tiptap v3 项目可以直接把 import 替换为tiptap/extensions聚合导出diff 见第 3.1 节并删除多余依赖收获统一版本号与子路径按需引入的便利如果你曾在大文档或协作场景下遇到 placeholder 闪烁建议至少升级到3.27.3增量 StateField 重构需要“字数提示但不强制”的需求请在3.23.4使用autoTrim: false若同时使用 TrailingNode 与 UniqueID务必使用包含 3.10.5 修复的版本避免无限事务循环使用协作功能时请按源码注释移除undo-redo。以上全部结论均可在此仓库内直接核对聚合包入口、各扩展源码、完整变更记录 以及 被替代的独立包源码。【免费下载链接】tiptapThe headless rich text editor framework for web artisans.项目地址: https://gitcode.com/GitHub_Trending/ti/tiptap创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考