Tiptap 编辑器深度解析:Headless 富文本框架的扩展架构、核心 API 与本地实战
【免费下载链接】tiptapThe headless rich text editor framework for web artisans.项目地址: https://gitcode.com/GitHub_Trending/ti/tiptap
Tiptap 是一个无界面(headless)、框架无关的富文本编辑器框架,基于 ProseMirror 构建,通过扩展(Extension)机制实现从基础文本样式到复杂块级编辑能力的自由组合。本文以 Tiptap 仓库根目录 README.md 为主线,结合 packages/core 的源码实现与 demos 中的真实示例,完整梳理其设计哲学、包生态结构、EditorOptions配置体系与扩展开发机制,帮助你在本地搭建、运行并二次开发 Tiptap 编辑器。
一、Tiptap 是什么:Headless、框架无关、扩展驱动
根据仓库 README.md 的定义,Tiptap Editor 是一个 headless、framework-agnostic(框架无关)的富文本编辑器,核心特征有四点:
- Headless(无内置界面):Tiptap 不附带任何固定 UI,不需要类名覆盖或代码 hack 来改样式,设计自由度完全交给使用者;
- 框架无关:同一套核心可以在 Vue、React、Svelte 或纯 JavaScript 环境中集成,无兼容性问题;
- 基于扩展:从简单文本样式到拖拽块编辑,功能均由扩展提供。README 提到官方文档与社区提供了上百个扩展可供选择;
- UX 完全可控:编辑器允许开发者自定义扩展与节点(Node),把交互体验的定义权交给应用层。
在源码层面,这一理念对应 packages/core 包——它被描述为headless rich text editor(见 packages/core/package.json),当前版本为 3.30.3。Tiptap 的底层构建在 ProseMirror 之上:仓库通过 packages/pm 这一 workspace 包对 ProseMirror 各子模块(model、state、view、schema-list、tables 等)做统一再导出,所有@tiptap/*包都依赖@tiptap/pm而非直接引用上游包,从包管理角度屏蔽了 ProseMirror 的模块拆分细节。
此外,README 指出 Tiptap 与开源协作后端 Hocuspocus 共同构成 Tiptap Suite 的基础,后者围绕 Yjs 的 CRDT 能力构建。本仓库中对应的是 packages/extension-collaboration 等协作相关扩展包。
二、仓库结构:一个多包(monorepo)生态
从仓库目录结构看,Tiptap 是一个典型的 pnpm workspace monorepo,分为四大板块:
2.1packages/:核心与官方扩展
这是最重要的部分,包含:
- 核心:packages/core(编辑器引擎、扩展基类、命令系统)、packages/extensions(扩展集合)、packages/starter-kit(开箱即用的扩展套件);
- 框架适配层:packages/react、packages/vue-2、packages/vue-3;
- 序列化/静态渲染:packages/markdown、packages/html、packages/static-renderer、packages/ai-toolkit(含流式输出 reveal 等 AI 场景工具);
- 具体功能扩展:按
extension-*命名组织,如 packages/extension-bold、packages/extension-heading、packages/extension-table、packages/extension-link、packages/extension-list、packages/extension-collaboration、packages/extension-collaboration-caret、packages/extension-drag-handle、packages/extension-mention、packages/extension-emoji、packages/extension-youtube 等数十个。
2.2packages-deprecated/:废弃包的兼容层
packages-deprecated 下保留了extension-character-count、extension-history、extension-placeholder、extension-task-item等旧包。例如历史上独立的 ListItem / TableCell / TaskList 等,如今已合并进 packages/extension-list 与 packages/extension-table 这类聚合包中——废弃包的存在说明 Tiptap 在扩展粒度上做过重组,但仍维持向后兼容。
2.3demos/:可运行的官方示例应用
demos 是一个 Vite + 多框架的示例站点,目录按功能域划分:Examples/(默认、协作、无障碍等)、Extensions/(DragHandle、FindAndReplace、TableOfContents 等)、Marks/(Bold、Link、Underline…)、Nodes/(Heading、CodeBlock、Youtube…)、Tutorials/(从 textarea 到 Tiptap 再到 Yjs 协作的渐进式教程)、Experiments/等。每个示例通常包含 React(.jsx/.tsx)与 Vue(.vue)两套实现,这正是 README 所宣称的"框架无关"的直观体现。
2.4scripts/与工程配置
根目录 package.json 定义了整套工程脚本:pnpm dev(启动 demos)、pnpm build、pnpm test:unit(基于vp test)、pnpm test:e2e(Playwright)、pnpm lint/pnpm format(oxlint / oxfmt)等。环境要求为Node >= 24,包管理器固定为pnpm@11.2.2(packageManager字段声明)。
三、核心引擎:@tiptap/core的 Editor 与 EditorOptions
3.1 Editor 类
编辑器入口是 packages/core/src/Editor.ts 中的Editor类(自 L59 起)。关键实现事实:
Editor继承自EventEmitter(编辑器事件系统),内部持有CommandManager(命令系统)、ExtensionManager(扩展管理器)、ProseMirror 的Schema与EditorView、EditorState;- 默认选项在 L92-L120 定义,例如
content: ''、extensions: []、autofocus: false、editable: true、enableInputRules: true、enablePasteRules: true等; - 实例具备
instanceId(随机生成)、isInitialized标志与extensionStorage(各扩展的持久化存储); - 内置核心扩展在构造时自动注入,包括
ClipboardTextSerializer、Commands、Delete、Drop、Editable、FocusEvents、Keymap、Paste、Tabindex、TextDirection(见 Editor.ts L11-L22 的导入),它们分别对应键盘快捷键、焦点/失焦事件、粘贴处理等基础行为。
3.2 EditorOptions 完整配置项
完整的选项类型定义在 packages/core/src/types.ts 的EditorOptions接口中,逐项说明如下:
| 选项 | 类型 / 取值 | 说明 |
|---|---|---|
element | Element \| { mount: HTMLElement } \| ((editor) => void) \| null | 编辑器挂载目标:传Element则追加到该元素;传null则不自动挂载;传函数则由其自行放置编辑器 DOM |
content | Content | 初始内容,支持 HTML 字符串、JSON 对象或 JSON 数组 |
extensions | Extensions | 使用的扩展列表 |
injectCSS | boolean | 是否注入基础 CSS |
injectNonce | string \| undefined | 注入样式时使用的 CSP nonce |
autofocus | FocusPosition | 初始聚焦位置 |
editable | boolean | 是否可编辑(只读模式设为false) |
textDirection | 'ltr' \| 'rtl' \| 'auto' \| undefined | 全文本方向策略:auto会按内容检测设置dir属性 |
editorProps | EditorProps | 透传给 ProseMirrorEditorView的 props |
parseOptions | ParseOptions | 内容解析选项 |
coreExtensionOptions | 对象 | 核心扩展细粒度配置:clipboardTextSerializer.blockSeparator、tabindex.value、delete.async/delete.filterTransaction |
enableInputRules | EnableRules | 是否启用输入规则(如 Markdown 快捷输入) |
enablePasteRules | EnableRules | 是否启用粘贴规则 |
enableCoreExtensions | boolean \| Partial<Record<...>> | 布尔值全量开关;也可传对象按名称关闭单个核心扩展,如{ keymap: false } |
enableContentCheck | boolean,默认false | 初始化时检查内容合法性,非法时触发contentError事件 |
emitContentError | boolean,默认false | 不做阻断性检查,但保留内容并触发contentError警告 |
onBeforeCreate/onCreate/onMount/onUnmount | 回调 | 生命周期钩子 |
onUpdate/onSelectionUpdate/onTransaction | 回调 | 内容、选区、事务变化回调 |
onFocus/onBlur | 回调 | 焦点事件回调 |
onDestroy | 回调 | 销毁回调 |
onPaste/onDrop/onDelete | 回调 | 粘贴、拖入、删除的内容拦截回调 |
enableExtensionDispatchTransaction | boolean,默认true | 是否允许扩展自定义dispatchTransaction钩子 |
Editor.tsL119-L120 还展示了onContentError的默认行为是直接抛出 error(throw error),意味着开启enableContentCheck后若不加自定义处理,非法内容会直接中断初始化——这是使用时的一个重要注意点。
3.3 core 包的公开 API
从 packages/core/src/index.ts 可以看出@tiptap/core的完整导出面:Editor、Extension、Node、Mark、NodeView、MarkView、InputRule、PasteRule、CommandManager、Tracker,以及内置的commands命名空间、jsx-runtime(createElement/Fragment,用于在 JS 环境中以 JSX 描述文档节点)等。扩展作者几乎只需要依赖这些导出即可编写新扩展。
四、扩展机制:Extension 的 create / configure / extend 三件套
README 强调 Tiptap "通过扩展定制与扩展能力",其机制实现于 packages/core/src/Extension.ts(L16-L59):
Extension.create(config)(L27-L33):静态工厂方法,接受配置对象或返回配置对象的函数,返回Extension实例。这是所有自定义扩展的入口;configure(options)(L35-L37):用新选项创建同一扩展的配置化副本,常用于"给扩展传参"而不改变其行为;extend(extendedConfig)(L39-L58):基于现有扩展派生新扩展,可在派生时覆盖addCommands、addKeyboardShortcuts、addAttributes等任意配置段,同样支持函数式配置。
Extension继承自Extendable(packages/core/src/Extendable.ts),后者统一处理 name / option / storage 合并逻辑;Node与Mark类(packages/core/src/Node.ts、packages/core/src/Mark.ts)分别继承该体系,形成"扩展—节点—标记"三层结构。
以仓库内一个最小扩展为例,packages/extension-strike/src 展示了如何定义一个 Mark 类扩展并注册strike命令与 HTML 序列化规则;而 packages/extension-heading/src 则展示了 Node 扩展如何声明level属性。两者都可配合__tests__目录下的用例(如 packages/extension-strike/tests的测试文件)验证行为。
五、快速上手:StarterKit 与仓库内真实示例
5.1 StarterKit 包含什么
packages/starter-kit/package.json 的依赖列表就是它的完整内容:extension-document、extension-paragraph、extension-text(文档结构三件套)、extension-heading、extension-blockquote、extension-bullet-list/extension-ordered-list/extension-list/extension-list-item(标题、引用与列表)、extension-bold/extension-italic/extension-strike/extension-underline/extension-code(行内标记)、extension-code-block、extension-horizontal-rule、extension-hard-break、extension-link,以及extension-dropcursor/extension-gapcursor(拖放与空位光标)等。也就是说,一个"基础编辑器"≈ 一个StarterKit。
5.2 官方默认示例(React 版)
demos/src/Examples/Default/React/index.tsx 展示了标准用法:
import { EditorContent, useEditor } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit' import { TextStyleKit } from '@tiptap/extension-text-style' const extensions = [TextStyleKit, StarterKit] export default () => { const editor = useEditor({ extensions, content: `<h2>Hi there,</h2><p>this is a <em>basic</em> example…</p>`, }) return ( <> <MenuBar editor={editor} /> <EditorContent editor={editor} /> </> ) }要点:内容可以是 HTML 字符串;MenuBar是演示用的自绘工具栏(headless 的体现);同一示例在 demos/src/Examples/Default/Vue 中有 Vue 实现,在 demos/src/Examples/Default/Svelte 中有 Svelte 实现,可对比三种框架适配层的 API 差异(useEditor响应式 ref 等)。
纯 JS / 无框架场景下,直接使用@tiptap/core:new Editor({ element, extensions, content }),element参数语义见前文EditorOptions表。
六、在本地运行 Tiptap 仓库
以下命令均基于根目录 package.json 的scripts定义(要求 Node >= 24、pnpm 11.2.2):
pnpm install # 安装 workspace 依赖(prepare 阶段会自动运行 vp config) pnpm dev # 启动 demos 应用(vp run start) pnpm build # 递归构建所有 packages(vp run -r build) pnpm test:unit # 运行单元测试(vp test) pnpm test:e2e # Playwright 端到端测试(chromium) pnpm lint # oxlint 静态检查几个值得关注的工程化细节:
- 新增 demo 脚手架:scripts/make-demo.sh 通过
pnpm run make:demo运行,交互式询问 demo 名称与分类(Dev/Examples/Extensions/Experiments/Marks/Nodes),并从 demos/src/Examples/Default 模板复制目录(见 CONTRIBUTING.md "Create a new demo" 一节); - 发布流程:使用 Changesets 管理版本,
pnpm run publish会先vp run build再执行changeset publish;CONTRIBUTING.md 中说明新包需要手动首发并配置 NPM trusted publishing(provenance)后才能被 CI 自动发布; - 测试分层:
packages/*/__tests__下是大量按功能命名的单测(如 packages/core/tests/setContent.spec.ts、packages/core/tests/insertContent.spec.ts、packages/core/tests/isActive.spec.ts),配合demos下的 Playwright e2e(配置见 playwright.config.ts)构成双保险。
七、协作编辑与 Tiptap Suite
README 专设 "Make your editor collaborative" 一节:协作能力由开源后端 Hocuspocus 提供,其核心是 Yjs CRDT;编辑器与 Hocuspocus 共同构成 Tiptap Suite 的基础。
在仓库中对应的实现有:
- packages/extension-collaboration:把 Yjs 的
Y.Doc与 ProseMirror 状态桥接(含 5 个源文件与 2 个测试文件); - packages/extension-collaboration-caret:远程用户光标渲染;
- packages/extension-unique-id:协作场景下为节点补充唯一 ID(其
__tests__中含 YDoc 相关用例,对应 demos/src/Extensions/UniqueIDWithYdoc 示例)。
README 同时提到Pro Extensions(商用订阅扩展),覆盖协作编辑、评论、版本管理、文档转换与 AI 相关功能;仓库中 packages/ai-toolkit 则代表了开源侧的 AI 工具能力(如streaming-reveal流式输出渐显,见 packages/ai-toolkit/src/streaming-reveal.ts 及其测试 packages/ai-toolkit/tests/streaming-reveal.spec.ts)。
八、社区、贡献规范与许可
- 贡献指南:CONTRIBUTING.md 要求提交前运行测试与 linter、为包变更附带 Changeset、PR 关联已指派的 issue、AI 辅助生成内容须显式披露、30 天无响应会关闭 PR 等;
- 安全:漏洞报告遵循 SECURITY.md;
- 许可:项目采用 MIT 许可证,详见 LICENSE.md;
- 社区:README 引导使用者通过 GitHub Discussions 进行讨论(具体入口以仓库页面的 Discussions 区为准)。
九、小结
Tiptap 的技术定位可以概括为:"ProseMirror 之上的声明式扩展层"。packages/core 提供Editor+Extension双核心与完整的事件/命令体系;packages/pm 屏蔽 ProseMirror 模块细节;extension-*包把每个功能单元化;demos 则证明同一 API 可以在 React、Vue 2/3、Svelte 与纯 JS 中互换运行。理解EditorOptions(packages/core/src/types.ts)与Extension.create / configure / extend(packages/core/src/Extension.ts)这两块,就掌握了在 Tiptap 生态中搭建、定制乃至贡献新扩展所需的全部核心知识。
【免费下载链接】tiptapThe headless rich text editor framework for web artisans.项目地址: https://gitcode.com/GitHub_Trending/ti/tiptap
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考