首页
/ MarkText muya 编辑器核心版本演进:从 @muyajs/core 0.0.x 到 0.2.0 的变更全解与源码实证

MarkText muya 编辑器核心版本演进:从 @muyajs/core 0.0.x 到 0.2.0 的变更全解与源码实证

2026-09-04 11:33:17作者:滕妙奇

本文以 packages/muya/CHANGELOG.md 为主线,系统解读 MarkText 拆分出的 Markdown 编辑器核心库 @muyajs/core 的完整版本演进:0.2.0「marktext 上游回移批次」的 22 个 PR 到底改了什么、0.1.0 修复了哪些构建与渲染问题、0.0.x 系列每次小版本迭代做了什么,并结合仓库中的规范测试运行器、回归锁定清单和核心源码实现,说明这些变更记录背后可验证的技术细节,帮助你在集成或排查 muya 时精确理解每个版本的能力边界。

版本全景:CHANGELOG 记录了什么

packages/muya/CHANGELOG.md 完整覆盖了 @muyajs/core(包名见 packages/muya/package.json,当前版本为 0.2.0)的三次发布:

  • 0.2.0(2026-05-20/21):marktext 上游回移批次。CHANGELOG 明确指出这是「22 个 PR(#208–#230)将上游 marktext 的 muya 树端到端带入 @muyajs/core」的大版本,每一项变更都附带完整测试覆盖;
  • 0.1.0(2026-05-20):两个 Bug 修复版本,涉及 marked 实例状态泄漏与构建产物类型声明;
  • 0.0.33 ~ 0.0.39:早期快速迭代期,以样式、构建与零散 Bug 修复为主。

值得一提的是,0.2.0 在 CHANGELOG 中出现了两次记录:一次是 conventional-changelog 风格的自动记录(带 commit hash 与关闭的 issue 编号),另一次是人工撰写的摘要版(带 PR 链接与「Internal」内部改进小节)。两者互为索引,前者适合按 commit 追溯,后者适合按 PR 快速理解变更动机。

0.2.0:marktext 上游回移批次详解

功能特性(Features)

CHANGELOG 列出的 9 项新功能,每一项都能在当前仓库源码中找到对应实现:

1. 脚注完整实现(PR-8 / #221) 「block class + UI tool + click wiring + HTML backref」意味着脚注从块级解析、到浮动工具 UI、到点击跳转、再到导出 HTML 时生成回注锚点,形成了闭环。对应 UI 实现位于 packages/muya/src/ui/footnoteTool/

2. 引用链接与引用图片(PR-16 / #229) 支持 reference link/image 的 markdown 加载与往返(roundtrip),并补齐图片 domsrc 处理。CHANGELOG 摘要版对此的描述是「markdown loading + round-trip + image domsrc」。

3. LinkTools 分发(PR-11b / #226) LinkTools 现在可以对三种链接形态统一分发操作:<a> 标签、reference link、markdown 链接。实现位于 packages/muya/src/ui/linkTools/,修复了上游 issue #1415。

4. muya.getTOC() 公共 API(PR-15 / #228) 这是宿主程序获取文档大纲的标准入口。在 packages/muya/src/muya.ts 中,getTOC() 委托给 editor.jsonState.getTOC();核心逻辑在 packages/muya/src/state/getTOC.ts。从源码看,该实现有几个值得注意的设计点:

  • 只遍历 atx-headingsetext-heading 两类块节点;
  • 返回项 ITocItem 包含 content(纯文本)、lvl(标题级别)、sluggithubSlug
  • 标题按渲染后文本(剥离 **bold**label 等内联标记)生成 slug,而非原始源码——源码注释中明确说明这是为了保持 githubSlug 与 HTML 导出时基于 heading.textContent 注入的锚点 id 一致;
  • stableSlugWeakMap<Parent, string> 为每个标题块缓存唯一 id,保证多次调用结果稳定。

5. focus/blur 事件与格式光标跳转(PR-10 / #225) 编辑器新增 focus / blur 事件,并且应用粗体/斜体等内联格式后,光标会跳回文末(jump-to-end),避免格式标记包裹后光标位置漂移。

6. 代码块行号(PR-5a / #219) 新增编辑器选项 codeBlockLineNumbers(默认 false,见 packages/muya/src/config/index.tscodeBlockLineNumbers: false 一行,同时在 packages/muya/src/types.ts 的类型定义中声明)。实现细节在 packages/muya/src/utils/codeBlockLineNumbers.ts,源码注释揭示了三个性能与正确性考量:

  • computeLineCountcharCodeAt 循环计数而非正则,避免每次代码块更新(包括粘贴大段内容时)产生 match 数组分配;
  • syncLineNumbersSpans 是 O(delta) 增量增删 <span>,而不是全量重写 innerHTML——「在一行内输入时,一旦行数匹配,代价为零」;
  • repositionLineNumberSpans 用 Range API 测量每条逻辑行的实际视觉顶部位置,使行号在自动换行模式下(一条逻辑行跨多个视觉行)也能正确对齐,且必须在布局后(requestAnimationFrame)执行。

行号容器本身是 contenteditable="false"aria-hidden="true"<span>,不会被编辑交互或读屏器干扰。

7. 小图 class 与内联缩放条抑制(PR-11a / #224) 为小尺寸图片添加专属 class,并抑制内联 resize-bar 的显示,避免小图出现比例失调的缩放手柄。对应实现位于 packages/muya/src/ui/imageResizeBar/

8. CommonMark 0.31 + GFM 0.29-gfm 规范一致性基础设施(PR-6a / #218) 这是 0.2.0 中最重要的工程性变更,后文专门展开。

9. 回移上游解析器测试套件(PR-20 / #220,摘要版记载) 将 marktext muya 的解析器测试套件一并带回。

Bug 修复(Bug Fixes)

P0 崩溃修复(PR-1a / #208)normalizeTable 行计数错误与 loadImageAsync 失败缓存问题——后者意味着图片加载失败后结果被缓存,同一 URL 不会重试。关闭了上游 #4222、#4190、#3001、#3010 四个 issue。

XSS 防护(PR-1b / #209):针对 langInputContent(代码块语言输入)、超链接、Mermaid 图与代码块四类注入面加固。仓库中 DOMPurify 的使用入口是 packages/muya/src/utils/dompurify.ts,其核心就是对 DOMPurify() 实例化的 sanitizeisValidAttribute 的薄封装,依赖版本为 dompurify ^3.4.11(见 packages/muya/package.json)。

解析器 CommonMark/GFM 正确性(PR-2a / #212)与 stateToMarkdown 序列化基线(PR-2b / #213):这两条是「正确性修复 + 回归基线锁定」的组合拳,分别锁定了解析与双向序列化(state → markdown)的输出基线。

编辑器/光标/IME/自动配对/表格修复(PR-3 / #211):从自动记录版看,这批修复关闭了 #2960、#2331、#2816、#2842、#2330 等一串 IME 与光标相关 issue,是回移批次中体量最大的一块。

剪贴板/粘贴/复制正确性(PR-4 等 / #210、#215、#216、#217):CHANGELOG 自动记录版把这一簇拆成了具体条目,包括:

  • 粘贴多行文本到标题时保持标题完整(#671);
  • 将仅含文本的 <table> 剪贴板数据提升(promote)到 HTML 粘贴路径(#1271);
  • 无可复制内容时跳过剪贴板写入(#3130);
  • 从 HTML 响应体解析 <title>,而不是按 JSON 解析(#1344);
  • 「PR-7a list/paragraph/clipboard 4-pack」与「PR-7b 嵌套块边界 4-pack」:其中大量条目标注为 verified-not-applicable(验证后确认不适用),仍保留防御性测试——这是一种诚实的变更记录方式:上游修复经核验不适用于当前代码时,不硬改代码而是加防御测试锁定现状。

EventCenter 监听器泄漏与 once 监听器迭代变异(PR-17 / #230):事件中心是 muya 的通信骨架,实现位于 packages/muya/src/event/index.ts。该类维护 events(DOM 事件登记表)与 listeners(订阅表),attachDOMEvent 会先 _checkHasBind 去重、分配自增 event-<n> id 后登记,detachDOMEvent / detachAllDomEvents 负责成对移除。0.2.0 修复的是两个内存/状态隐患:监听器只增不减的泄漏,以及 once 监听器在事件触发迭代过程中对监听数组的变异。

内部改进(Internal)

CHANGELOG 摘要版单独列出了三个内部指标,这是评估该版本工程成熟度最直接的证据:

  • 测试覆盖从 1 个增长到 386 个(43 个文件)
  • 规范一致性基线锁定在 CommonMark 87.7% / GFM 86.3%,由 expected-failures.json 做回归门禁;
  • 残余项清理:XSS 评估 + 重构后拆分 + skipped tags(PR #223、#227)。

规范一致性基础设施:87.7% 是怎么锁住的

CHANGELOG 中「conformance baseline locked」这一行对应的是仓库中一整套可运行的验证体系,理解它对判断 muya 解析器行为边界很有价值。

运行方式pnpm --filter @muyajs/core test:spec(脚本定义在 packages/muya/package.jsontest:spec / test:spec:commonmark / test:spec:gfm)。packages/muya/test/spec/conformance.md 记录了基线数据:

套件 通过 总数 通过率
CommonMark 0.31 572 652 87.7%
GFM 0.29-gfm 580 672 86.3%

分节通过率也有明细:强调/强强调(Emphasis)在 CommonMark 下为 100%(132/132),代码跨度(Code spans)100%(22/22),而最薄弱的是 Tabs(1/11,9.1%)、Paragraphs(4/8,50%)、软换行(1/2,50%)与实体引用(5/17,29.4%)——这些正是使用 muya 处理含 tab 缩进或大量 HTML 实体内容时需要心里有数的场景。

回归锁定契约packages/muya/test/spec/expected-failures.json 的头部注释定义了「单向棘轮」规则——列在清单里的例子断言必须仍然失败,一旦开始通过,测试会报「unexpected pass」并要求维护者将其从清单移除;未列在清单中的例子则必须继续通过。净效果是「合规率只能升不能降」。packages/muya/test/spec/commonmark.spec.ts 中的断言逻辑正是这一契约的实现:命中 expected-failure 的例子断言 result.passedfalse,否则断言为 true。当前清单锁定 CommonMark 80 例、GFM 92 例的失败项。

HTML 归一化器:规范参考实现(cmark)与 marked 产出的 HTML 在属性顺序、自闭合写法、标签间空白等层面存在无语义差异,直接字符串对比会产生大量假阴性。packages/muya/test/spec/runner.tsnormalizeHtml 做了四件事:void 标签统一为 <br /> 形式、标签属性按字母序重排、仅折叠「纯空白」的标签间间隙(>WS<><,标签内的文本内容绝不动,<pre><code> 内的换行被完整保留——后者由 packages/muya/test/spec/runner.spec.ts 中「code blocks can contain literal blank lines」等用例显式保护)。

一个关键的测量前提:规范测试调用 renderToStaticHTML(..., { sanitize: false })packages/muya/test/spec/conformance.mdpackages/muya/test/spec/commonmark.spec.ts 的注释都解释了原因:这测的是解析器的规范符合性而非 DOMPurify 净化器——后者「正确地激进」,会剥离规范 §6.9「Raw HTML」明确测试要保留的原始 HTML(如未知标签 <bab>)。因此读这些百分比时应理解其口径:它是纯解析层数据,不代表带 sanitize: true 的最终渲染输出。

0.1.0:marked 状态泄漏与构建产物修复

CHANGELOG 记录 0.1.0(2026-05-20)包含两个修复:

1. 避免共享 Marked 状态在 renderHtml 调用间泄漏(#202) 从问题描述可以推断,此前 marked 实例或其解析状态被多处共享,一次渲染的配置/状态会影响后续渲染结果。0.1.0 之后该共享状态不再跨调用泄漏,对「同一页面多次渲染不同文档片段」这类用法是正确性保障。

2. 恢复 vite-plugin-dts v5 升级后的 lib/types 输出(build 修复) 升级 vite-plugin-dts v5 后类型声明产物一度丢失,该修复恢复了 lib/types 输出。这与 packages/muya/package.json 中的产物约定对应:main 指向 lib/cjs/index.jsmodule 指向 lib/es/index.jstypes 指向 lib/types/index.d.tspublishConfig 同时配置了 import / require / types 三个条件导出——对下游 TypeScript 用户来说,lib/types 缺失意味着类型导入直接断裂,这正是该修复被列为 Bug Fix 而非 Internal 的原因。devDependencies 中 vite-plugin-dts: ^5.0.2build: tsc && vite build 脚本即为该产物的构建来源。

0.0.33 ~ 0.0.39:早期迭代的变更明细

这一段历史以单点修复为主,逐版本对照 CHANGELOG 如下:

版本 日期 变更 说明
0.0.39 2025-11-17 修复 ResizeObserver loop completed with undelivered notifications 浏览器端 ResizeObserver 常见告警的处理
0.0.38 2025-11-17 必要时隐藏内联格式工具栏 内联格式工具栏(packages/muya/src/ui/inlineFormatToolbar/)的显示条件收敛
0.0.37 2025-11-13 列表样式 CSS 修复 列表样式调整的第二轮修正
0.0.36 2025-11-13 更新列表样式(Feature) 0.0.37 的前置样式改版
0.0.35 2025-11-06 修复表格选择器的 TS 错误 table picker 的类型问题
0.0.34 2025-11-06 修复部分 lint 错误 工程卫生
0.0.33 2025-10-28 输入斜杠时的撤销错误修复(#172);examples 中 @muya/core 更名为 @muyajs/core 该条同时说明包名在 0.0.33 期间发生了从 @muya/core@muyajs/core 的迁移

从源码结构看,这个区间正处于「包刚从 marktext 主仓库抽出、快速收敛 API 与命名」的阶段:0.0.33 的包名迁移与 0.1.0 的构建产物修复首尾呼应,而 0.2.0 才标志着回移批次与测试体系的成型。

面向使用者的版本能力对照

把 CHANGELOG 信息浓缩成集成视角的结论(均以仓库当前内容为准):

  • 依赖 @muyajs/core >= 0.2.0:才能使用 muya.getTOC() 公共 API、codeBlockLineNumbers 选项、focus / blur 事件、脚注闭环、引用链接/图片 roundtrip、以及针对 XSS 与 P0 崩溃的加固;
  • >= 0.1.0:多文档片段渲染不再受 shared Marked 状态影响,且 TypeScript 类型产物完整;
  • >= 0.0.33:包名为 @muyajs/core(此前示例代码可能仍写 @muya/core),斜杠输入的撤销行为正确;
  • 解析器行为边界:CommonMark 0.31 / GFM 0.29-gfm 规范通过率为 87.7% / 86.3%(纯解析层、sanitize: false 口径),失败例子清单即 packages/muya/test/spec/expected-failures.json,可逐条对照预期;
  • 运行环境:engines 要求 Node >= 20.19.0、npm >= 8.0.0(见 packages/muya/package.json)。

变更记录如何支撑工程决策

回看整份 packages/muya/CHANGELOG.md,它的价值不止于「改了什么」的记录:0.2.0 的双重记录方式(自动 commit 记录 + 人工 PR 摘要)让追溯粒度可以按需选择;「verified-not-applicable + defensive tests」的标注方式让每个上游修复的去留都有明确结论而非静默丢弃;expected-failures.json 的单向棘轮把规范符合性从一句口头承诺变成了 CI 可断言的契约(packages/muya/test/spec/runner.tspackages/muya/test/spec/commonmark.spec.ts)。对维护第三方 Markdown 编辑器内核的集成方而言,这三点恰好回答了选型时的三个问题:修复是否真实落地(commit/PR 可查)、不修的修复为何不修(防御测试留痕)、解析器行为边界在哪里(锁定基线 + 分节通过率,见 packages/muya/test/spec/conformance.md)。

登录后查看全文
热门项目推荐
相关项目推荐