首页
/ MarkText 的 muya 编辑器内核 ROADMAP 解读:从 2020 年块系统到 2023 年 TypeScript 重构的完整演进

MarkText 的 muya 编辑器内核 ROADMAP 解读:从 2020 年块系统到 2023 年 TypeScript 重构的完整演进

2026-09-04 15:45:30作者:卓艾滢Kingsley

本篇技术文章以 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/ 下即对应这批基础块:paragraphatxHeadingsetextHeadingblockQuotethematicBreakcodeBlockhtml 等,统一经 src/block/index.tsScrollPage.register(...) 注册。事件处理方面,Editor 持有经 RxJS 合并的 DOM 事件流(clickinputkeydownkeyupcompositionstart/end),并路由到活跃块的处理函数——这正是"各类事件处理"条目在 TS 重写后的实现形态。

2020 年 7 月:更多块,支持输入与输出

原始目标清单:

  • 行内与段落的复制粘贴;
  • 多选段落复制粘贴;
  • 有序列表、无序列表、任务列表;
  • 列表拖拽,并把完成项自动移至末尾;
  • 代码块;
  • HTML 块;
  • 表格块;
  • 历史记录(undo/redo);
  • 与其他文件类型(markdown 与 html)的输入输出。

这些能力在当前代码中的落点:

  • 列表与任务列表:src/block/gfm/taskList.ts 同目录下的 orderListbulletListlistItem(commonMark 侧)与 TaskListItemTaskListCheckbox 附件节点;
  • 表格块:src/block/gfm/table/ 下的 TableTableInnerTableRowCellTableCellContent
  • 代码块与 HTML 块:src/block/commonMark/codeBlocksrc/block/commonMark/html(含 htmlContainerhtmlPreview);
  • 历史记录: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.tscopyData.tscut.ts 及 27 个单测文件。

2020 年 8 月:全功能版本

原始目标清单:

  • 数学公式块;
  • mermaid;
  • flowchartsequence(划掉,后续弃用);
  • footnote(原清单未勾选);
  • front matter;
  • 上标、下标、数学公式等行内元素;
  • 段前菜单(paragraph front menu);
  • 快速插入菜单(quick insert menu)。

对照现状:

  • 数学公式块:src/block/extra/math/MathBlockMathContainerMathPreview),KaTeX 在 package.json 依赖中;
  • 图表块:src/block/extra/diagram/DiagramBlockDiagramContainerDiagramPreview),图表渲染集成在 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.jsonbuildlint:types: tsc --noEmit)。关于第二项的"no any",从 e2e 侧规范看,e2e/README.md 明确写道该项目禁止 anyts/no-explicit-any: 'error'),并且 e2e 类型声明通过 e2e/types.d.tsWindow.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(生态更好、更易读)。

逐项源码佐证:

  1. marked 补丁化packages/muya/src/utils/marked/ 是一个成体系的本地化 marked 集成,包含 lexBlock.tswalkTokens.tsfrontMatter.tscompatibleTaskList.tsextensions/ 等,而非直接调用 npm 包默认行为——即"patch the latest marked to muya"的落地形态。markedmarked-highlight 仍出现在 package.json 依赖中,补丁层构建在其上。
  2. 移除 axios / XMLHttpRequest:在 packages/muya/src 内检索 axios|XMLHttpRequest 无任何命中,证实该条目完成——引擎不再在核心中做网络请求。
  3. constructor mixin 替换 property mixinpackages/muya/src/block/mixins/containerQueryBlock.tspackages/muya/src/block/mixins/leafQueryBlock.ts 即这两个 constructor mixin,应用于块类以提供 queryBlock/路径解析;CLAUDE.md 也明确指出 "this was a deliberate switch away from property mixins",与 ROADMAP 条目互相印证。
  4. 弃用 sequence 与 flowchart:当前图表链路保留 mermaid(含 sequence/flowchart 的 e2e 测试 e2e/tests/diagrams/ 中仍有 sequence.spec.tsflowchart.spec.ts 等场景用于回归),从源码结构看,2020 年清单中划掉的旧 flowchart/sequence 独立实现(对应 muyajs 时代的 Snap.svg 方案)已被弃用,统一收敛到 src/utils/diagram/ 的渲染器集成。
  5. turndown 优化src/utils/turndownService/ 目录承载定制化的 turndown 封装,配合 src/state/htmlToMarkdown.ts 完成 HTML→Markdown 的粘贴转换;turndownjoplin-turndown-plugin-gfm 均在依赖中。
  6. 未决项: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.mdREADME.mdCHANGELOG.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。

证据:

  1. vite 替换 webpackpackages/muya/vite.config.ts 即当前构建配置,package.jsonbuild 脚本为 tsc && vite build,产物为 lib/{es,umd,cjs}lib/typesvite-plugin-dts 负责声明文件,@laynezh/vite-plugin-lib-assets 负责图标与字体资源路由);开发侧 examplese2e/host 均跑 Vite dev server。
  2. CI 侧:仓库 .github/workflows/ 下已有 muya-build.ymlmuya-test.ymlmuya-spec.ymlmuya-e2e.ymlmuya-lint.ymlmuya-circular.yml 等一整套 muya 专属流水线;madge 循环依赖检查(pnpm -C packages/muya check-circular,即 madge --circular src/index.ts)也在 CI 中强制执行。
  3. npm 自动发布CLAUDE.md 明确说明上游的 release-it 发布链路未迁入本 monorepo——"marktext 不使用 husky/commitlint,@muyajs/core 不从本仓库发布"。因此原清单最后这一项在当前仓库语境下不适用/未完成,版本号(package.json0.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.typedelay: 0 时会丢字符——muya 的 content-change 管线每次按键同步重渲染,超过 4 个字符的输入应使用 tests/helpers/keyboard.ts 中的 slowType()(每字符 30ms);
  • 浮动插件用 opacity: 0 隐藏而非 display: noneexpect(...).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/websiteexamples
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.mdpackage.jsonengines 声明。

小结

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 自动发布则仍是明确标注的开放项。沿本文给出的源码路径与命令逐条核对,即可把路线图与实现现状一一对齐。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384