# 大文档异步载入(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+ 文档时的进度徽标与切换手感。