MarkText muya 编辑器核心版本演进:从 @muyajs/core 0.0.x 到 0.2.0 的变更全解与源码实证
本文以 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-heading与setext-heading两类块节点; - 返回项
ITocItem包含content(纯文本)、lvl(标题级别)、slug与githubSlug; - 标题按渲染后文本(剥离
**bold**、label等内联标记)生成 slug,而非原始源码——源码注释中明确说明这是为了保持githubSlug与 HTML 导出时基于heading.textContent注入的锚点 id 一致; stableSlug用WeakMap<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.ts 中 codeBlockLineNumbers: false 一行,同时在 packages/muya/src/types.ts 的类型定义中声明)。实现细节在 packages/muya/src/utils/codeBlockLineNumbers.ts,源码注释揭示了三个性能与正确性考量:
computeLineCount用charCodeAt循环计数而非正则,避免每次代码块更新(包括粘贴大段内容时)产生 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() 实例化的 sanitize 与 isValidAttribute 的薄封装,依赖版本为 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.json 的 test: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.passed 为 false,否则断言为 true。当前清单锁定 CommonMark 80 例、GFM 92 例的失败项。
HTML 归一化器:规范参考实现(cmark)与 marked 产出的 HTML 在属性顺序、自闭合写法、标签间空白等层面存在无语义差异,直接字符串对比会产生大量假阴性。packages/muya/test/spec/runner.ts 的 normalizeHtml 做了四件事: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.md 与 packages/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.js、module 指向 lib/es/index.js、types 指向 lib/types/index.d.ts,publishConfig 同时配置了 import / require / types 三个条件导出——对下游 TypeScript 用户来说,lib/types 缺失意味着类型导入直接断裂,这正是该修复被列为 Bug Fix 而非 Internal 的原因。devDependencies 中 vite-plugin-dts: ^5.0.2 与 build: 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.ts 与 packages/muya/test/spec/commonmark.spec.ts)。对维护第三方 Markdown 编辑器内核的集成方而言,这三点恰好回答了选型时的三个问题:修复是否真实落地(commit/PR 可查)、不修的修复为何不修(防御测试留痕)、解析器行为边界在哪里(锁定基线 + 分节通过率,见 packages/muya/test/spec/conformance.md)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00