Files
mnote/design/03-rust-web/process/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md
T

204 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 3-13 [process] Rust Web 本地 Markdown GFM AST 解析器迁移方案 v1
> 更新时间:2026-05-08
>
> 关联:
> - `/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/local_folder_source.rs`
> - `/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/web_shell.rs`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-21-local-folder-convex-unified-tree-source-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-22-local-folder-convex-unified-tree-execution-checklist-v1.md`
## 1. 结论
当前本地 Markdown 渲染链路是 `mnote-web` 自己写的轻量 parser,不是 VSCode 解析器,也不是成熟 GFM AST pipeline。它已经被补到能覆盖一部分常见语法,但本质上仍是“自己造轮子”,后续维护成本会持续上升。
本方案建议把本地 `.md` 读取侧迁移到 Rust 侧成熟 GFM AST 解析器,再把 AST 映射到现有 `PageAggregate` / Tiptap bridge。当前手写 parser 只保留为过渡层,不再作为长期语义来源。
## 2. 当前问题
本地链路现在分成两段:
1. `local_folder_source.rs` 负责读取 `.md`、拆 frontmatter、把正文转成 block。
2. `web_shell.rs` 负责把 legacy block 转成 Tiptap,再把 Tiptap 保存回 legacy block。
问题不在 Tiptap 本身,而在前面的 Markdown 语义输入过窄:
- task list、inline code、bold、italic、strike、link 之前会被吞成纯文本。
- table 只做了最低限度兼容。
- 解析逻辑是手写规则,扩展语法时会不断叠条件分支。
- 读写两端各自维护一份近似规则,容易出现回写不一致。
## 3. 目标
- 用 Rust 侧成熟 GFM AST 解析器替换手写 Markdown parser。
- 保留当前已验证的本地文件夹读写能力。
- 让 Markdown 语法来源尽量统一,减少“读得懂、写不回”的差异。
- 继续支持当前已暴露的语法面:paragraph、heading、task list、list、quote、fenced code、divider、pipe table、attachment link、inline code、bold、italic、strike、link。
- 不改变 `PageAggregate`、树 projection、权限、路由边界。
## 4. 非目标
- 不在这一步追求“所有 Markdown 语法无损互转”。
- 不把 VSCode 源码直接搬进来。
- 不把 Next/Wolai 导入链路里的 `@blocknote/core markdownToBlocks` 作为本地 source 真相。
- 不把 tree/domain、Convex 或 editor island 的所有语义一起重写。
## 5. 方案比较
### 方案 A:继续扩写手写 parser
优点:
- 改动小,能快速补洞。
缺点:
- 语法越来越多时会失控。
- 读写规则难以保证一致。
- 这条路本质上还是重复造轮子。
### 方案 BRust GFM AST + 语义映射层
优点:
- 语法基础来自成熟实现,降低长期维护成本。
- AST 天然适合做 block / inline 双向映射。
- 更容易补齐 task list、table、inline mark 等常见 GFM 特性。
缺点:
- 需要补一层 AST 到 `PageAggregate` 的适配。
- 初期需要做迁移和回归测试。
### 方案 C:复用前端/导入侧解析器
优点:
- 可以少写部分逻辑。
缺点:
- 本地文件夹主链是 Rust 服务,不适合把语义真相放回 JS 侧。
- 导入链路和本地打开链路不是同一职责。
### 推荐
选方案 B。它最符合当前架构:语义在 Rust 侧收口,前端只消费稳定 projection。
## 6. 架构设计
### 6.1 解析层
新增一个本地 Markdown 解析模块,职责只做三件事:
- 读取 Markdown 文本并构建 GFM AST。
- 归一化 frontmatter、正文、块级节点、行内节点。
- 输出可供 `PageAggregate` 使用的中间表示。
建议把手写字符串规则替换成“AST -> 中间 IR -> block”的两段式转换,而不是直接从 AST 拼最终 JSON。
### 6.2 中间 IR
建议引入一个轻量中间层,表达 block / inline 的稳定语义:
- blockparagraph、heading、task_item、list_item、quote、code_block、divider、table、media。
- inlinetext + markscode、bold、italic、strike、underline、link)。
这样做的目的不是再造一套编辑器模型,而是把 Markdown 语义和 Tiptap/legacy block 解耦。
### 6.3 输出层
输出层仍然保留两条现有通路:
- 读入时:Markdown AST -> block document -> `PageAggregate.body.content`
- 保存时:editor document -> block document -> Markdown
`web_shell.rs` 里的 legacy / Tiptap 转换仍然存在,但它应该只负责编辑器适配,不再承担 Markdown 语法识别。
### 6.4 语法优先级
第一批必须稳定支持:
- task list
- inline code
- bold / italic / strike
- link
- pipe table
- fenced code
第二批再扩:
- nested list
- setext heading
- autolink
- footnote
- raw HTML
- block quote 多段落
## 7. 迁移步骤
### Step 1:引入 AST 解析器
先把当前自写 parser 包进一个兼容接口,替换为 Rust GFM AST 实现。
要求:
- 读入结果和现在的 `PageAggregate` 输出保持兼容。
- 已有 smoke 和 Rust 单测先不改大结构。
### Step 2:统一 block / inline 映射
把 AST 到 block document 的映射集中到一个明确模块,不再散在 `local_folder_source.rs` 大文件里。
要求:
- task list 变成真正的 todo / task item 语义。
- inline mark 进入统一的 marks 表达。
- table cell 里的 mark 也能保留。
### Step 3:统一回写
把 block document -> Markdown 的输出改成与 AST 语义对齐的反向映射。
要求:
- task list 回写为 `- [x]` / `- [ ]`
- 行内 mark 回写为标准 Markdown。
- 不支持的结构明确降级,不静默丢内容。
### Step 4:清理临时补丁
当 AST 迁移完成后,删除当前手写 parser 的临时补丁代码,保留测试和兼容层。
## 8. 测试计划
### Rust 单测
至少覆盖:
- task list 读写往返。
- inline code / bold / italic / strike / link 往返。
- table 单元格内 inline mark。
- frontmatter title 保留。
- 未支持语法的降级策略。
### 浏览器 smoke
至少覆盖:
- 本地 `.md` 打开后 checkbox 真正出现在 ProseMirror DOM。
- `code``strong``em``s``a` 在正文和表格单元格中真实渲染。
- 保存后刷新不丢语义。
### 回归边界
- 不破坏 `file_tree` / `page_tree`
- 不改变 local workspace 的只读边界。
- 不影响 Convex workspace 链路。
## 9. 风险
- GFM AST 到 block 的映射会比手写 parser 更清晰,但初期要补一轮语义归一化。
- 复杂 Markdown 结构不可能天然无损,必须明确哪些语法是支持、哪些是降级。
- 如果 AST 解析器选型过于底层,后续表格、任务列表和 inline mark 的兼容代码会变多,所以要优先选能直接拿到 AST 的成熟实现。
## 10. 完成判定
这个方案进入实现阶段的条件:
- 解析器选型明确。
- AST -> block / inline 映射接口冻结。
- 当前手写 parser 的临时补丁有对应回归测试。
- 浏览器 smoke 能稳定证明 task list 和 inline mark 真正渲染出来。