Files
new-markdown/docs/20-async-loading.md
Muse 2d0843dd97 feat(editor): 大文档异步分片载入(docs/20)
>6 万字符文档按顶层结构安全切分:第一片立即建编辑器,其余片逐片解析
追加、片间让出主线程,窗格左下角显示载入进度;载入期间可切标签、编辑
其他文档。追加期静音回写、状态保持磁盘完整原文,载完一次性对齐。
分片与整篇解析的序列化等价有测试锁定(tests/chunk-load.test.ts)。
此前试做的已关闭重开缓存已按用户要求整笔撤回。
2026-11-14 15:16:23 +08:00

49 lines
3.3 KiB
Markdown
Raw Permalink 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.
# 大文档异步载入(2026-11-14,取代已撤回的「重开缓存」方案)
## 需求(用户口径)
大文件打开慢可以接受,但不能阻塞其他操作:打开要在后台进行,给出加载中
状态,用户能切标签、编辑别的文档、点菜单。
## 为什么不是真 Web Worker 线程
实测探针:Milkdown 的解析管线离不开 DOM——headless Editor 在 Node 里
create() 直接抛 `document is not defined`(core 的 editorView 插件在装配期
就引用 document.body,其后 ProseMirror 视图构建也需要真实 DOM)。
把整套 DOM 模拟进 Worker 不可靠,故采用主线程分片让行达到同样的
「不阻塞」效果:每片工作控制在几十毫秒量级,片间让出主线程。
## 方案(src/editor/chunkLoader.ts + EditorPane)
- 阈值:内容 > 60,000 字符且可切分时走分片;否则维持同步打开。
- 切分(splitMarkdownChunks):围栏感知扫描,只在「空行 + ATX 标题 /
围栏开始 / 分隔线」处切——这些位置之前的块必然已闭合,片段独立解析
与整篇解析等价(等价性有自动化测试锁定);按约 24,000 字符攒片。
文档含链接引用定义或脚注定义时不切(定义可位于文末被前文引用),
整篇一片同步解析(与此前行为一致)。
- 载入:第一片直接建编辑器(马上能看、能滚);其余片借一台隐藏的同厂
编辑器取 parserCtx 逐片解析,经 JSON 转交给目标编辑器(两台编辑器的
schema 是不同实例,直接插节点会被 ProseMirror 静默丢弃——实测踩过,
已加「追加未生效即抛错」的断言),追加到文末(不进撤销历史),每片后
setTimeout(0) 让行并更新进度。
- 进度:窗格左下角徽标「正在载入文档… N%(可切换到其他文档继续工作)」,
不遮挡已载入内容;切到别的标签时它在隐藏窗格里继续载完。
- 状态一致性:追加期间静音序列化回写,标签状态里始终是磁盘上的完整
原文(自动保存/恢复/外部变更检测都不受影响);载完一次性以最终序列化
对齐(不标脏)。载入中编辑已载入部分是允许的,最终对齐会把编辑一并
收进去。若某片解析/追加失败:解除静音、把已载入部分如实回写为
未保存状态,绝不冒充完整文档。
## 验证
- tests/chunk-load.test.ts 5 例:切分拼接逐字还原、围栏内假标题不切、
引用定义整篇一片、小文档不分片;66K 字符富结构文档(标题/宽松列表/
任务/表格/围栏/Mermaid/公式/引用/分隔线)分片载入的序列化与整篇解析
**逐字相等**。全量 vitest 94 通过 + 2 跳过;tsc 0;构建通过。
- 无头浏览器实测(596K 字符文档):徽标从 13% 起显示;载入中真实鼠标
点击切到其他标签立即生效(徽标在其隐藏窗格里继续涨到 22%…),在其
编辑器里打字实时落字;全程无页面错误;载完 180 个二级标题齐全。
总耗时约 10.6 秒为本机无头慢速口径,WebView2 真机应明显更快;
关键不在总时长,而在全程界面可操作。
- 待真机验:桌面模式打开 100K+ 文档时的进度徽标与切换手感。