Skip to content
目录

Markdown 引擎与序列化策略

1. 设计定位

Tiptap 的 Markdown 不是“单独编辑器模式”,而是通过扩展系统把 markdown 解析/序列化能力嵌入现有 Editor。

关键文件:

  • packages/markdown/src/Extension.ts
  • packages/markdown/src/MarkdownManager.ts

2. 接入方式

Markdown 扩展会:

  • 扩展 EditorOptions.contentType
  • 扩展 setContent/insertContent/insertContentAtcontentType
  • onBeforeCreate 注入 editor.markdowneditor.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 会扫描扩展能力:

  • markdownTokenName
  • parseMarkdown
  • renderMarkdown
  • markdownTokenizer
  • markdownOptions

这意味着 Markdown 支持范围由“启用的扩展集合”决定,天然与编辑器 schema 对齐。

4. parse 流程(简化)

  1. marked lexer 把 markdown 转 token。
  2. parseTokens 遍历 token,按扩展注册处理。
  3. 无 handler 时走 fallback(paragraph/heading/text/html)。
  4. 产出 Tiptap JSON(doc + content)。

其中 list 解析有一段针对 task list 混排的拆分逻辑,避免 task/bullet 混合 token 造成结构失真。

5. serialize 流程(简化)

  1. 遍历 JSON nodes。
  2. 每个 node 交给对应扩展 renderMarkdown
  3. 文本节点需处理 mark 边界的开闭顺序。
  4. 处理重叠 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 保持一致即可。

MIT License.