Markdown 引擎与序列化策略
1. 设计定位
Tiptap 的 Markdown 不是“单独编辑器模式”,而是通过扩展系统把 markdown 解析/序列化能力嵌入现有 Editor。
关键文件:
packages/markdown/src/Extension.tspackages/markdown/src/MarkdownManager.ts
2. 接入方式
Markdown 扩展会:
- 扩展
EditorOptions.contentType - 扩展
setContent/insertContent/insertContentAt的contentType - 在
onBeforeCreate注入editor.markdown与editor.getMarkdown()
当 contentType: 'markdown' 时,初始字符串会先 parse 成 JSON 再进入 core。
源码片段(packages/markdown/src/Extension.ts):
ts
if (assumedType === 'markdown') {
const json = this.editor.markdown.parse(this.editor.options.content as string)
this.editor.options.content = json
}3. 为什么选择“扩展注册式”而不是硬编码语法表
MarkdownManager 会扫描扩展能力:
markdownTokenNameparseMarkdownrenderMarkdownmarkdownTokenizermarkdownOptions
这意味着 Markdown 支持范围由“启用的扩展集合”决定,天然与编辑器 schema 对齐。
4. parse 流程(简化)
- 用
markedlexer 把 markdown 转 token。 parseTokens遍历 token,按扩展注册处理。- 无 handler 时走 fallback(paragraph/heading/text/html)。
- 产出 Tiptap JSON(
doc+ content)。
其中 list 解析有一段针对 task list 混排的拆分逻辑,避免 task/bullet 混合 token 造成结构失真。
5. serialize 流程(简化)
- 遍历 JSON nodes。
- 每个 node 交给对应扩展
renderMarkdown。 - 文本节点需处理 mark 边界的开闭顺序。
- 处理重叠 marks 时,必要时使用
htmlReopen(HTML 标签重开)避免 markdown 歧义。
这是 Markdown 序列化里最难的一段:保证“可读”与“可逆”同时成立。
6. HTML token 的特殊处理
parseHTMLToken 会区分环境:
- 浏览器环境:用
generateJSON(html, extensions)按扩展 parseHTML 规则解析。 - 服务端环境:降级为纯文本/段落,避免无 DOM 时报错。
这是对 SSR 兼容性的工程化处理。
7. 典型问题证据
- Issue: #7107
hard break(双空格换行)在contentType: markdown下解析异常,后续已修复。 - Changelog 多次记录 markdown parse/serialize 边界修复,说明该模块长期演进中。
8. 高频追问
问:为什么 Markdown 不直接存字符串?
答:编辑时仍以 ProseMirror 文档树为真相源,markdown 只是输入/输出格式之一;否则很多编辑行为(selection、node transform、协同 merge)会非常脆弱。
问:如何支持自定义 markdown 语法?
答:在扩展中实现 markdownTokenizer + parseMarkdown + renderMarkdown,并与 node/mark schema 保持一致即可。