Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[Bug] Windows 打开 3.76 MB / 6.8 万行 Markdown 文档切换到预览后,界面帧率跌到 1–3 fps 并持续数分钟卡死

Aperta
#1,380 3 commenti 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@milestone-17 ci sta già lavorando.

Dal 20/10/2026.

Valutazione

Questa issue non è ancora stata valutata.

Descrizione

area:editor bug claimed issue-form:bug P1 platform:windows review: medium
Platform / 平台

Windows

Area / 模块

Editor / 编辑器

Lithe version / Lithe 版本

0.6.2

Priority / 优先级

P1

OS and architecture / 操作系统与架构

Windows

Environment / 环境信息
  • Lithe:Windows 0.6.2 正式发布版,从 GitHub Release 安装(temp 目录里留有 Lithe-0.6.2-installer.exe)
  • WebView2 Runtime:155.0.4283.62
  • GPU:NVIDIA GeForce RTX 5060 Laptop GPU,驱动 32.0.15.8142(另装有 GameViewer / ToDesk 虚拟显示器适配器)
  • 工作区:单项目,getAllProjectFiles 报告 files=7773,位于本地磁盘
  • 触发文档:工作区内一个大 Markdown 文档,68,660 行 / 3.76 MB,同目录下有一个 images/ 目录,正文引用了其中 4 张图片
  • 语言服务:该工作区未检测到 Java 工程(javaWorkspaceDetection 以 java-workspace-not-detected 取消),可排除 JDT LS 干扰
  • 主题:lithe-dark

触发文档的结构统计(不含正文内容):

指标 数值
行数 / 体积 68,660 行 / 3,941,335 字节(3.76 MB)
标题 2,973
代码围栏 1,080 个,共 4,988 行(powershell 220、text 162、json 81、bash 28、http 15、go 12、markdown 5、无语言 5)
表格 5,085 个表格行 / 576 个表头分隔行
列表项 9,031
链接 / 图片 692 个链接 / 4 个图片引用
最长单行 11,967 字符(超过 2000 字符的行共 5 行)
Steps to reproduce / 复现步骤
  1. 在 Windows 上打开 Lithe 0.6.2,载入包含该大 Markdown 文档的工作区
  2. 打开这个大 Markdown 文档(68,660 行 / 3.76 MB)
  3. 用编辑器工具栏的模式选择器切到“预览”,或执行 Markdown: Preview Markdown(markdownViewMode = "preview")
  4. 界面随即整体卡顿,不只是预览区,连顶部标签、活动栏、滚动都失去响应,只能关掉预览或等待自行恢复

发生频率:稳定复现,同一会话内反复出现(见日志 22:08–22:16 共 18 条掉帧告警,其中 3 次 Frame drop ongoing 表示持续未恢复)。

Expected behavior / 预期行为

切换到大文档预览后,界面应保持可交互(滚动、切换标签、点击)不出现长时间冻结。同类产品对大文件有明确降级:VS Code 内置 Markdown 预览会在超大文件时跳过高亮/图片,或直接提示不预览。至少应保证预览渲染不阻塞全局交互线程。

Actual behavior / 实际行为

切到预览后整个界面帧率跌到个位数,最严重一段连续 58.6 秒无法正常操作。

日志时间线(session 37299c9a,时间 +08:00):

  • 22:07:55.049 bench:syntax language_id=markdown(编辑器语法就绪打点)
  • 22:08:08.731 Frame drop recovered duration_ms=16864 fps_avg=3.7 fps_min=0.1
  • 22:08:14 / 22:08:36 / 22:08:53 fps_avg=14.4 → 29.3 → 8.2,第三次 duration_ms=13349
  • 22:09:12.683 / 22:09:13.682 Frame drop ongoing duration_ms=14465 → recovered duration_ms=15465 fps_avg=11.1
  • 22:09:50.868 Frame drop recovered duration_ms=20656 fps_avg=5.8 fps_min=0.9
  • 22:12:24.933 Frame drop ongoing duration_ms=18947 fps_avg=2.2 fps_min=0.1
  • 22:12:52.312 → 22:12:59.965 ongoing 累积 29795/41361/53459 ms,fps_avg 降到 1.0、fps_min=0.1
  • 22:13:26.829 Frame drop recovered duration_ms=58592 fps_avg=2.2 fps_min=0.1

同机同安装的对照:下一个会话开启诊断日志后空载(不打开预览)帧率稳定满帧,四次 heartbeat 分别为 59.8 / 60 / 60 / 60 fps。

初步定位(本地已用真实 Chromium 复现,但尚未在 Lithe 内插桩确认,属待验证的推测):

markdown-preview.tsx 通过 useHighlightedMarkdown 同步调用 parser.ts 的 parseMarkdown,再用单次 dangerouslySetInnerHTML 把整篇文档一次性塞进 DOM:

const html = useHighlightedMarkdown(deferredSourceContent, { frontMatter: "render" });
<div className="markdown-content w-full max-w-3xl" dangerouslySetInnerHTML={{ __html: html }} />

用它处理这份真实文档(3.76 MB,转换后 HTML 约 3.35 MB)在真实 Chromium 中的实测:

场景 结果
parseMarkdown 单次 90–105 ms(3 次,中位 95.6 ms)
单次 innerHTML 构建 101 ms
强制布局 1,888 ms(内容高度 903,584 px)
产出 DOM 节点 31,684
只让这份 DOM 静态存在并滚动 58.5 fps(≈空载 60 fps,这一步不是瓶颈)
渲染一次后不再重渲染 60.4 fps,0 个 long task
每 250 ms 重新渲染一次 3.5 fps,6 个 long task

最后两项是关键:单独一份大 DOM 并不致命,反复整体重建 + 整体重排才会把主线程完全占满,而 3.5 fps 与日志中的 1–3 fps 高度吻合。日志中 markdown 相关打点确实在反复出现(22:07:55、22:09:35、22:09:48 两条、22:10:04),说明预览在持续重算,与上面的复现路径一致。

补充对照:把同一份内容改成按块 content-visibility: auto 分块渲染,静态内容高度从 903,584 px 降到 17,436 px,滚动保持 60.4 fps。

可能相关的放大因素(均未单独验证,供排查参考):

  • use-highlighted-markdown.ts 在 parseMarkdown 之后还会调用 highlightMarkdownCodeBlocks 再生成一份 HTML 并 setHtml,等于每轮渲染两次 DOM 重建;本文件含 1,080 个代码围栏
  • useDeferredValue 只在同一次渲染中降优先级,并不能阻止内容变化时整篇重新解析 + 重建
Logs or diagnostic output / 日志或诊断输出
来源:%LOCALAPPDATA%\app.lithe.windows\logs\lithe.2026-10-20T22-04-16.697+0800.*.001.log,session 37299c9a,时间 +08:00。日志中的 <redacted_path> 由 Lithe 自身输出,未做改动。


# ---- 首次掉帧:切到预览后 ----
2026-10-20T22:07:55.049+08:00 INFO  [session=37299c9a] [scope=bench:syntax] <redacted_path> language_id=markdown theme_id=lithe-dark token_types=<redacted>
2026-10-20T22:08:08.731+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop recovered duration_ms=16864 fps_avg=3.7 fps_min=0.1
2026-10-20T22:08:14.325+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop recovered duration_ms=4583 fps_avg=14.4 fps_min=0.8
2026-10-20T22:08:36.771+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop recovered duration_ms=4749 fps_avg=29.3 fps_min=12.5
2026-10-20T22:08:53.152+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop recovered duration_ms=13349 fps_avg=8.2 fps_min=0.5

# ---- 持续未恢复 ----
2026-10-20T22:09:12.683+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop ongoing duration_ms=14465 fps_avg=7.7 fps_min=0.5
2026-10-20T22:09:13.682+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop recovered duration_ms=15465 fps_avg=11.1 fps_min=0.5

# ---- 预览仍在重算 ----
2026-10-20T22:09:35.369+08:00 INFO  [session=37299c9a] [scope=bench:syntax] <redacted_path> language_id=markdown theme_id=lithe-dark token_types=<redacted>
2026-10-20T22:09:48.516+08:00 INFO  [session=37299c9a] [scope=bench:syntax] <redacted_path> language_id=markdown theme_id=lithe-dark token_types=<redacted>
2026-10-20T22:09:48.517+08:00 INFO  [session=37299c9a] [scope=bench:syntax] <redacted_path> language_id=markdown theme_id=lithe-dark token_types=<redacted>
2026-10-20T22:09:50.868+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop recovered duration_ms=20656 fps_avg=5.8 fps_min=0.9
2026-10-20T22:10:04.327+08:00 INFO  [session=37299c9a] [scope=bench:syntax] <redacted_path> language_id=markdown theme_id=lithe-dark token_types=<redacted>

# ---- 最严重的一段:连续近一分钟 ----
2026-10-20T22:12:24.933+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop ongoing duration_ms=18947 fps_avg=2.2 fps_min=0.1
2026-10-20T22:12:52.312+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop ongoing duration_ms=29795 fps_avg=1.4 fps_min=0.1
2026-10-20T22:12:52.350+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop ongoing duration_ms=41361 fps_avg=1.2 fps_min=0.1
2026-10-20T22:12:59.965+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop ongoing duration_ms=53459 fps_avg=1 fps_min=0.1
2026-10-20T22:13:26.829+08:00 WARN  [session=37299c9a] [scope=perf.fps] Frame drop recovered duration_ms=58592 fps_avg=2.2 fps_min=0.1

# ---- 对照:同机同安装,空载满帧(下一会话,已开启诊断日志) ----
2026-10-20T22:31:10.215+08:00 DEBUG [session=96fedddb] [scope=perf.heartbeat] FPS diagnostic heartbeat duration_ms=10115 fps_avg=59.8 fps_min=58
2026-10-20T22:31:20.366+08:00 DEBUG [session=96fedddb] [scope=perf.heartbeat] FPS diagnostic heartbeat duration_ms=10148 fps_avg=60 fps_min=60
2026-10-20T22:31:30.463+08:00 DEBUG [session=96fedddb] [scope=perf.heartbeat] FPS diagnostic heartbeat duration_ms=10099 fps_avg=60 fps_min=60


本地复现输出(真实 Chromium,rAF 计数口径与 fps-monitor.ts 一致):


# 真实文档结构统计
{"bytes":3941335,"bytes_mb":3.76,"lines":68660,"headings":2973,"code_fences":1080,
 "code_fence_lines":4988,"table_rows":5085,"list_items":9031,"links":692,
 "max_line_length":11967,"lines_over_2000_chars":5}

# 解析
{"parse_ms_runs":[105.4,95.6,90.2],"parse_ms_median":95.6,"html_bytes":3348122}

# 单次渲染
{"chunks":3431,"build_ms":101.3,"layout_ms":1888.5,"content_height_px":903584,
 "dom_nodes":31684,"current_fps":58.3,"chunked_fps":60.4,"chunked_height_px":17436,"baseline_fps":60}

# 关键隔离实验:静态 DOM vs 反复重渲染
static_big_dom       {"fps":58.5}
pipeline_no_rerender {"fps":60.4,"rerenders":0,"long_tasks":0}
pipeline_rerender4x  {"fps":3.5,"rerenders":4,"long_tasks":6}
Screenshots or recordings / 截图或录屏

未提供(本次为从日志复现 + 本地实测,未录制界面录屏)。需要的话可以补充一份显示掉帧过程的 GIF。

Checklist / 提交前检查
  • I searched existing issues for duplicates. / 我已搜索现有 Issue,确认没有重复报告。
  • I removed secrets and personal data from the report. / 我已从报告中移除敏感信息和个人数据。
Lingua principale
TypeScript
Stelle
1.8k
Fork
151
Merge medio
12h 26m
PR unite (30g)
339

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di 1lck/Lithe-IDEA

Tutte le issue di 1lck/Lithe-IDEA

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.