MarkText 的 muya 编辑器内核 ROADMAP 解读:从 2020 年块系统到 2023 年 TypeScript 重构的完整演进
本篇技术文章以 packages/muya/docs/ROADMAP.md 为核心,完整梳理 muya(marktext 的 Markdown 编辑内核,包名 @muyajs/core)的路线图:2020 年 6–8 月按月推进的块(block)能力构建目标,以及 2023 年 1.0.0 版本围绕 TypeScript 化、代码设计、浏览器兼容、文档与 CI/CD、测试五大方向的规划清单。读完本文,你能对照源码逐项验证路线图的实际落地情况,并掌握在本仓库中运行构建、单元、规范符合性与 Playwright E2E 测试的完整命令。
muya 在 MarkText 中的定位
muya 是 marktext monorepo 中的编辑器引擎,位于 packages/muya,由上游 JS 实现迁移为 TypeScript 重写并作为 @muyajs/core 组织。桌面端渲染器直接消费 @muyajs/core 作为编辑内核,而遗留的 JS 引擎 packages/muyajs 正在退役。根据 packages/muya/CLAUDE.md 的描述,packages/muya 是自闭环包:拥有自己的工具链(ESLint/antfu、stylelint、madge、vitest),仓库根 ESLint 明确忽略 packages/muya/**。
从源码结构看,muya 的核心架构与路线图条目直接对应:
- 入口 packages/muya/src/muya.ts 导出
Muya类,UI 插件通过静态Muya.use(Plugin, options)注册,muya.init()时实例化; Editor.init()调用 registerBlocks(),在构建根ScrollPage前注册全部块类型;- 块继承体系为
TreeNode → Parent → (Content | Format),具体块分布在src/block/{commonMark,gfm,extra,content}; - 文档状态
JSONState基于ot-json1暴露invert/compose/transform,架构上为 OT 协同编辑预留了空间; - 行内渲染走自定义
lexer/rules管线并经snabbdom渲染虚拟 DOM,集成 KaTeX、Prism、Mermaid、Vega/Vega-Lite 与 PlantUML。
2020 年月目标:块系统的三阶段演进
ROADMAP 下半部分记录了 2020 年 6 月、7 月、8 月三个月的阶段性目标。这一部分是 muya 从"能输入"到"全功能"的能力清单,且绝大多数条目已完成(勾选)。下文按月份继承原清单,并结合当前源码标注佐证。
2020 年 6 月:三种基础块
原始目标清单:
- 支持 GFM 与 CommonMark 规范的行内样式;
- 行内图片(不支持本地图片)与图片编辑菜单;
- atx 与 setext 两种标题;
- 引用块(blockquote);
- 水平分割线;
- 行内样式格式化方式与格式工具箱;
- 各类事件处理:backspace、delete、arrow、tab、enter、input 等;
- 多段落选中与删除;
- 段落拖拽;
- 普通段落。
对照当前源码,src/block/commonMark/ 下即对应这批基础块:paragraph、atxHeading、setextHeading、blockQuote、thematicBreak、codeBlock、html 等,统一经 src/block/index.ts 中 ScrollPage.register(...) 注册。事件处理方面,Editor 持有经 RxJS 合并的 DOM 事件流(click、input、keydown、keyup、compositionstart/end),并路由到活跃块的处理函数——这正是"各类事件处理"条目在 TS 重写后的实现形态。
2020 年 7 月:更多块,支持输入与输出
原始目标清单:
- 行内与段落的复制粘贴;
- 多选段落复制粘贴;
- 有序列表、无序列表、任务列表;
- 列表拖拽,并把完成项自动移至末尾;
- 代码块;
- HTML 块;
- 表格块;
- 历史记录(undo/redo);
- 与其他文件类型(markdown 与 html)的输入输出。
这些能力在当前代码中的落点:
- 列表与任务列表:src/block/gfm/taskList.ts 同目录下的
orderList、bulletList、listItem(commonMark 侧)与TaskListItem、TaskListCheckbox附件节点; - 表格块:
src/block/gfm/table/下的Table、TableInner、TableRow、Cell、TableCellContent; - 代码块与 HTML 块:
src/block/commonMark/codeBlock与src/block/commonMark/html(含htmlContainer、htmlPreview); - 历史记录:
src/history/模块,并配有src/history/__tests__/单测; - 输入输出:
src/state/下的markdownToState.ts(经marked解析)、stateToMarkdown.ts(序列化回 Markdown)、markdownToHtml.ts/htmlToMarkdown.ts(经turndown+joplin-turndown-plugin-gfm桥接 HTML); - 剪贴板:
src/clipboard/模块,包含paste.ts、copyData.ts、cut.ts及 27 个单测文件。
2020 年 8 月:全功能版本
原始目标清单:
- 数学公式块;
- mermaid;
flowchart、sequence(划掉,后续弃用);- footnote(原清单未勾选);
- front matter;
- 上标、下标、数学公式等行内元素;
- 段前菜单(paragraph front menu);
- 快速插入菜单(quick insert menu)。
对照现状:
- 数学公式块:
src/block/extra/math/(MathBlock、MathContainer、MathPreview),KaTeX 在 package.json 依赖中; - 图表块:
src/block/extra/diagram/(DiagramBlock、DiagramContainer、DiagramPreview),图表渲染集成在src/utils/diagram/; - front matter:
src/block/extra/frontmatter/,并有src/block/__tests__/frontMatter.spec.ts单测; - footnote:原 2020 清单中唯一未勾选项。当前 CHANGELOG.md 0.2.0 版本记录了 "footnote complete — block class + UI tool + click wiring + HTML backref",且 src/block/index.ts 已注册
Footnote(来自src/block/extra/footnote),e2e 中另有e2e/tests/blocks/footnote-scenarios.spec.ts场景测试——可以推断该遗留项已在 TS 重写后补齐; - 段前菜单与快速插入菜单:对应 UI 插件
src/ui/paragraphFrontButton/、src/ui/paragraphFrontMenu/、src/ui/paragraphQuickInsertMenu/,经Muya.use(...)由宿主注册。
2023 年 1.0.0 目标:逐项对照当前落地状态
ROADMAP 上半部分是 2023 年 v1.0.0 的规划,分为 Optimization、Better Code Design、Compatibility、Documents、CI and CD、Test 六个方向。下面逐项继承原清单,并给出仓库内的可验证证据。
Optimization:支持 TypeScript
原清单两项:
- [x] 将 JS 代码转换为 TS 代码(P0)——已完成;
- [ ] 补全所有缺失类型(no any),支持 TS 编译严格模式(eslint --fix 无错误)(P0)。
证据:整个 packages/muya/src 已是 TypeScript,构建脚本为 tsc && vite build(见 package.json 的 build 与 lint:types: tsc --noEmit)。关于第二项的"no any",从 e2e 侧规范看,e2e/README.md 明确写道该项目禁止 any(ts/no-explicit-any: 'error'),并且 e2e 类型声明通过 e2e/types.d.ts 以 Window.muya?: Muya 的形式避免 window as any。主包侧则由 antfu 配置的 ESLint 与 tsc --noEmit 把关,因此"补全类型"这一项可以认为在工程规范层面已实质推进,具体完成度以 pnpm -C packages/muya lint:types 的实际输出为准。
Better Code Design:更好的代码设计
原清单七项:
- [x] 把最新 marked 打补丁并入 muya(P0);
- [x] 移除 axios、XMLHttpRequest 等;
- [x] 用 constructor mixin 替换 property mixin;
- [x] 图表:弃用 sequence 与 flowchart;
- [x] 优化 turndown service 以获得更好的粘贴体验;
- [ ] 优化 markdownToHtml 的 UI 样式,移除无用代码;
- [ ] 是否使用 DI(依赖注入);
- [ ] 是否用 TSX 替换 snabbdom(生态更好、更易读)。
逐项源码佐证:
- marked 补丁化:packages/muya/src/utils/marked/ 是一个成体系的本地化 marked 集成,包含
lexBlock.ts、walkTokens.ts、frontMatter.ts、compatibleTaskList.ts、extensions/等,而非直接调用 npm 包默认行为——即"patch the latest marked to muya"的落地形态。marked与marked-highlight仍出现在 package.json 依赖中,补丁层构建在其上。 - 移除 axios / XMLHttpRequest:在
packages/muya/src内检索axios|XMLHttpRequest无任何命中,证实该条目完成——引擎不再在核心中做网络请求。 - constructor mixin 替换 property mixin:packages/muya/src/block/mixins/containerQueryBlock.ts 与 packages/muya/src/block/mixins/leafQueryBlock.ts 即这两个 constructor mixin,应用于块类以提供
queryBlock/路径解析;CLAUDE.md 也明确指出 "this was a deliberate switch away from property mixins",与 ROADMAP 条目互相印证。 - 弃用 sequence 与 flowchart:当前图表链路保留 mermaid(含 sequence/flowchart 的 e2e 测试
e2e/tests/diagrams/中仍有sequence.spec.ts、flowchart.spec.ts等场景用于回归),从源码结构看,2020 年清单中划掉的旧 flowchart/sequence 独立实现(对应 muyajs 时代的 Snap.svg 方案)已被弃用,统一收敛到src/utils/diagram/的渲染器集成。 - turndown 优化:
src/utils/turndownService/目录承载定制化的 turndown 封装,配合src/state/htmlToMarkdown.ts完成 HTML→Markdown 的粘贴转换;turndown与joplin-turndown-plugin-gfm均在依赖中。 - 未决项:markdownToHtml 样式清理、DI、TSX 替换 snabbdom 三项在原清单中未勾选。从当前源码结构看,渲染层仍为
snabbdom+snabbdom-to-html(依赖列表可见),UI 插件体系仍围绕src/ui/baseFloat/组织,即 TSX 替换尚未发生。
Compatibility:浏览器兼容
原清单三项:
- [x] 兼容 Firefox(P0);
- [ ] Safari(P0);
- [ ] Edge(P0)。
证据:E2E 配置 packages/muya/e2e/playwright.config.ts 配了 Chromium/Firefox/WebKit 三浏览器矩阵,e2e/README.md 说明了本地 pnpm --filter muya-e2e e2e:firefox / e2e:webkit 的运行方式;CI(muya-e2e.yml)目前只跑 Chromium,Firefox + WebKit 在 WebKit 相关的引擎无关改写(BACKLOG Phase 3)落地前暂不纳入 CI 矩阵。Safari 与 Edge 在仓库中未见专门的适配/测试痕迹,与原清单"未完成"状态一致。
Documents:文档
原清单项:官网(docs、demo)(P0)、文档(P0)、代码注释。
从 monorepo 结构看,packages/website/ 目录提供了文档站(含 content/docs/end-user/ 21 篇用户文档与 content/docs/dev/ 12 篇开发文档)与首页演示,packages/muya/examples/ 是消费 @muyajs/core 的 Vite 演示工程。muya 自身则通过 CLAUDE.md、README.md、CHANGELOG.md 与 e2e 的 README.md / BACKLOG.md 构成文档面。因此 ROADMAP 中"website(docs, demo)"一项在仓库层面已有对应实体,是否视为完成取决于原项目对"对外发布站点"的定义。
CI and CD:构建与发布
原清单三项:
- [x] 优化构建流程,用 vite 替换 webpack 并用 rollup 构建;
- [x] 加快开发流程,用 vite 替换 webpack;
- [ ] 新增 GitHub Action:合并/推送 master 后自动修改 package.json 版本号、打 tag 并发布新版本到 npm。
证据:
- vite 替换 webpack:packages/muya/vite.config.ts 即当前构建配置,package.json 的
build脚本为tsc && vite build,产物为lib/{es,umd,cjs}与lib/types(vite-plugin-dts负责声明文件,@laynezh/vite-plugin-lib-assets负责图标与字体资源路由);开发侧examples与e2e/host均跑 Vite dev server。 - CI 侧:仓库
.github/workflows/下已有muya-build.yml、muya-test.yml、muya-spec.yml、muya-e2e.yml、muya-lint.yml、muya-circular.yml等一整套 muya 专属流水线;madge 循环依赖检查(pnpm -C packages/muya check-circular,即madge --circular src/index.ts)也在 CI 中强制执行。 - npm 自动发布:CLAUDE.md 明确说明上游的 release-it 发布链路未迁入本 monorepo——"marktext 不使用 husky/commitlint,
@muyajs/core不从本仓库发布"。因此原清单最后这一项在当前仓库语境下不适用/未完成,版本号(package.json 中0.2.0)与 CHANGELOG.md 目前由上游节奏维护。
Test:测试
原清单两项:
- [ ] 单元测试(P1);
- [ ] e2e 测试(Optional)。
这两项在原 ROADMAP 时间点尚未完成,但在当前仓库中均已实质建成,且规模远超"Optional"级别:
- 单元测试:vitest 单测与源码同目录放置在
src/**/__tests__/下,覆盖 block、clipboard(27 个文件)、selection(11 个文件)、state(26 个文件)、history、event、inlineRenderer、search、utils 等模块。CHANGELOG.md 0.2.0 记录了 "test coverage 1 → 386 tests (43 files)"。运行方式:pnpm -C packages/muya test,单文件pnpm -C packages/muya exec vitest run path/to/file.test.ts。 - 规范符合性测试:
test/spec/是 CommonMark 0.31 + GFM 0.29-gfm 的 fixture 套件(test:spec脚本,独立 vitest 配置 vitest.spec.config.ts)。基线由 test/spec/expected-failures.json 锁死——清单内用例若突然通过、或清单外用例若开始失败,套件都会报错,"合规度只允许上升"。基线数据见 test/spec/conformance.md:CommonMark 87.7% / GFM 86.3%(PR-6a 时点)。 - E2E 测试:packages/muya/e2e/ 是基于 Playwright 的真实浏览器套件,自带独立宿主页
e2e/host/(#editor+ 工具栏按钮),测试按smoke/、typing/、inline/、ui/、editing/、blocks/、diagrams/、drag/、export/、stability/、a11y/等分类组织。常用命令(从仓库根执行):
pnpm install # 一次性安装,拉取 @playwright/test
pnpm e2e # 全矩阵 — Chromium + Firefox + WebKit
pnpm e2e:ui # Playwright UI 模式(推荐用于调试)
pnpm --filter muya-e2e e2e:firefox # 仅 Firefox
pnpm --filter muya-e2e exec playwright install firefox webkit # 一次性下载浏览器
pnpm --filter muya-e2e exec playwright show-report # 查看失败报告
e2e README 还沉淀了四条针对 muya contenteditable 场景的实战约定,值得写 Playwright 测试的读者注意:
page.keyboard.type在delay: 0时会丢字符——muya 的 content-change 管线每次按键同步重渲染,超过 4 个字符的输入应使用tests/helpers/keyboard.ts中的slowType()(每字符 30ms);- 浮动插件用
opacity: 0隐藏而非display: none,expect(...).toBeHidden()无效,需断言计算后的 opacity; getMarkdown()在最后一次击键后异步读取 state,读 Markdown 前先用expect(domNode).toContainText(...)作为同步屏障;- 优先用公共 API(
getMarkdown/getState/getTOC)断言,而非 DOM 正则——Markdown 序列化是确定性的,snabbdom 输出则可能漂移。
路线图全景核对表
把 ROADMAP 原清单汇总为一张当前状态核对表(状态以本仓库源码与文档为准):
| 方向 | 条目 | 原状态 | 当前仓库证据 |
|---|---|---|---|
| Optimization | JS 转 TS | 完成 | 整个 src/ 为 TS,tsc && vite build |
| Optimization | no any / strict | 未完成 | e2e 侧 ts/no-explicit-any: error;主包以 lint:types + ESLint 把关 |
| Code Design | marked 补丁化 | 完成 | src/utils/marked/ 本地集成层 |
| Code Design | 移除 axios/XHR | 完成 | src/ 内无相关引用 |
| Code Design | constructor mixin | 完成 | src/block/mixins/*.ts |
| Code Design | 弃用 sequence/flowchart | 完成 | 图表统一收敛至 src/utils/diagram/ + mermaid 等 |
| Code Design | turndown 优化 | 完成 | src/utils/turndownService/ + GFM 插件 |
| Code Design | markdownToHtml 清理 / DI / TSX | 未完成 | 渲染层仍为 snabbdom,UI 体系未变 |
| Compatibility | Firefox | 完成 | e2e firefox 矩阵 + CI 配置 |
| Compatibility | Safari / Edge | 未完成 | 仓库中无相应适配证据 |
| Documents | 官网 / 文档 | 推进中 | monorepo 含 packages/website 与 examples |
| CI/CD | vite 替换 webpack | 完成 | vite.config.ts、e2e/examples 均 Vite |
| CI/CD | GitHub Action 自动发 npm | 未完成 | @muyajs/core 不从本仓库发布(CLAUDE.md) |
| Test | 单元测试 | 已完成 | vitest,386 tests / 43 files(0.2.0 changelog) |
| Test | e2e 测试 | 已完成 | Playwright 全套件,Chromium/Firefox/WebKit 矩阵 |
验证路线:如何在本仓库亲手核对
以下命令均从仓库根目录执行,用于验证本文所述各项落地情况:
# 类型检查与 lint(对应 "no any / strict" 条目)
pnpm -C packages/muya lint:types
pnpm -C packages/muya lint
# 循环依赖检查(CI 强制项)
pnpm -C packages/muya check-circular
# 单元测试 / 覆盖率
pnpm -C packages/muya test
pnpm -C packages/muya coverage
# CommonMark/GFM 规范符合性(含 expected-failures.json 回归门)
pnpm -C packages/muya test:spec
pnpm -C packages/muya test:spec:commonmark
pnpm -C packages/muya test:spec:gfm
# E2E(需要一次 playwright install firefox webkit)
pnpm -C packages/muya/e2e e2e
# 构建产物(lib/{es,umd,cjs} + lib/types)
pnpm -C packages/muya build
# 演示工程
pnpm -C packages/muya/examples dev:demo
环境要求:Node ≥ 20.19(与 marktext 根工程一致),构建目标 chrome70,来自 CLAUDE.md 与 package.json 的 engines 声明。
小结
packages/muya/docs/ROADMAP.md 记录的不只是一份待办清单,而是 muya 内核从 2020 年"逐月堆块能力"到 2023 年"面向 1.0 的系统性重构"的完整演进轨迹:2020 年的三个阶段目标已在 src/block/ 的块注册体系、src/state/ 的 Markdown/HTML 双向转换、src/clipboard/ 与 src/history/ 中留下清晰对应物;2023 年的六大方向中,TypeScript 转换、marked 补丁化、constructor mixin、turndown 优化、vite 构建、单元与 E2E 测试均已落地并有可执行命令验证,而 Safari/Edge 兼容、markdownToHtml 清理、DI 与 TSX 化、npm 自动发布则仍是明确标注的开放项。沿本文给出的源码路径与命令逐条核对,即可把路线图与实现现状一一对齐。
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 StartedRust0622
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