从零自研Markdown编辑器:如何兼顾极致美学与强悍性能
发布时间:2026/9/19 18:57:08 作者:尧图编辑部 阅读量:1,286

作为一个把 Markdown 当日常书写工具的骨灰级用户我对编辑器的要求其实早就超过了「能写字」这个层面。这几年市面上的 Markdown 编辑器我基本都试过一轮要么界面花哨但不实用要么功能够强但实在谈不上好看真正能做到鱼和熊掌兼得的很少。折腾到最后我决定自己动手做一个既好看又彪悍的 Markdown 编辑器。这篇文章不写什么宏大的愿景就从一个 Markdown 重度用户的角度把我从需求分析、方案选型到核心功能实现、美化思路、踩坑排查的完整过程记录下来。如果你也是一个被编辑器「逼疯」过的 Markdown 用户或者正在考虑自己造轮子这里面的取舍逻辑和实践经验应该能帮你少走不少弯路。1. 需求分析与产品定位为什么我决定自研编辑器1.1 从重度用户视角出发的核心痛点Markdown 语法最大的优点是简单最大的缺点也是简单。用习惯了之后你的书写效率会变得很高但对编辑器的期待也会水涨船高。我用过的不少 Markdown 编辑器写短小的笔记没问题一旦文档变长、章节变多问题就全出来了。首先是性能瓶颈。几万字的文档在部分编辑器里输入一个字符都要卡半秒滚动页面时明显掉帧。其次是排版审美问题很多编辑器渲染出来的预览效果跟发布到博客平台后的样子差距太大字间距、行高、代码块样式都差点意思写的时候还挺满意导出到别处一看完全不是那么回事。再就是功能缺失频繁插入图片、管理本地文件、快速搜索替换这些基本操作在某些工具里做得非常反人类你需要反复切换鼠标和键盘手感极其断裂。正是这些痛点让我意识到我需要的是一个能覆盖「本地写作、实时预览、文档管理、发布输出」全流程的工具。既要有原生桌面应用的手感和性能又要具备 Web 技术带来的颜值灵活性还要在长期使用中扛得住超大文档和复杂排版。1.2 方案选型为什么用 Electron 而不是纯 Web 或 Tauri技术选型是项目开始时第一个绕不开的决策。我考虑过三个方向纯 Web 应用、Electron 桌面应用和 Tauri 桌面应用。纯 Web 应用天然避开了安装分发的成本但文件系统访问受限你很难像本地编辑器一样自由读写磁盘上的 Markdown 文件离线体验也糟糕。Tauri 是后起之秀打包体积小、内存占用低但它的前端与后端通信链路在频繁读写文件、高频渲染的场景下会有明显的性能瓶颈。综合权衡下来我选择了 Electron。理由很直接Node.js 环境下文件读写毫无阻碍Chromium 内核的渲染能力和调试工具成熟社区生态极其庞大踩到坑能快速找到解决方案。当然 Electron 的槽点也很清楚打包体积大、内存占用高。但作为一款面向创作者的工具型产品稳定和功能完整比省那几百兆内存重要得多。实际开发中我用 Vite 管理渲染进程配合 electron-builder 打包整体工程结构不算复杂。1.3 面向的使用场景与目标人群这个编辑器我定位为「给创作型选手的本地写作工具」目标用户是 Markdown 重度用户、技术文档写作者、博客维护者以及喜欢用纯文本管理知识的笔记党。核心场景有三个长文写作、内容整理、多端同步。长文写作场景要求编辑器具备流畅的滚动、稳定的光标定位和快速响应的输入体验内容整理场景需要你有强大的文件树导航、全文搜索和标签能力多端同步场景依赖对本地 Markdown 目录的直接监控和友好处理。说白了我不打算做一个功能臃肿的「全家桶」而是把高频刚需做到极致让每个细节都经得起推敲。2. 编辑器整体架构设计与核心模块拆解2.1 基于 CodeMirror 6 构建编辑内核编辑内核是整个项目的灵魂我选的是 CodeMirror 6。你可能会问为什么不用 Monaco 或 ProseMirrorMonaco 是 VS Code 的内核功能很强但它本质上为大型 IDE 场景服务体积大、定制成本高作为一个专注于 Markdown 的编辑器有些杀鸡用牛刀。ProseMirror 是富文本编辑器框架虽然能直接操作文档树但 Markdown 的纯文本输入模型与它并不完全匹配。CodeMirror 6 恰好站在一个很好的平衡点上它提供的是一个高度模块化的编辑器核心把文档状态、视图渲染、输入处理拆成了清晰的模块你可以按需组合也可以轻松扩展。它默认就是增量渲染和虚拟滚动几千行文档滑动起来依然流畅。这套架构让「好看」和「彪悍」都有了底层保障前端的任何美化都不会拖累编辑性能。2.2 解析渲染链路从 Markdown 源码到预览视图Markdown 的预览体验要想好核心是解析渲染链路不能断。我的实现方式是在编辑器监听文档变更将 Markdown 文本实时交给 marked 库解析成 HTML再用 DOMPurify 做一轮安全过滤最后注入到预览面板中。这套链路看似简单但实际开发中要处理三个细节。第一是滚动同步编辑区和预览区必须保持对应关系我采取的方式是监听编辑区的滚动位置根据行号与渲染节点的映射关系计算出预览区对应内容的偏移量。第二是图片路径解析当文档中插入时需要自动拼接当前文件的目录路径让预览区直接显示本地图片。第三是防抖策略输入时不立即解析而是等 300ms 停顿后再重新渲染避免大文档下每次击键都触发全量解析导致的卡顿。2.3 文件树与多目录工作区设计Markdown 写作者的文件管理习惯往往是「一个目录就是一个项目」所以我做了一个多目录工作区的设计可以同时把多个文件夹挂载进来每个目录独立展开文件树支持新建、重命名、删除、拖拽移动等基本操作。关键在于文件监控。我使用 Node.js 的 fs.watch 递归监听目录变化当外部新增或修改了 Markdown 文件时文件树和编辑器标签页会同步更新。这里有一个非常容易踩的坑fs.watch 在不同操作系统上的行为不完全一致Linux 下目录监听容易丢事件解决办法是用 chokidar 这个库替换原生监听。起初我在 Linux 测试环境上频繁遇到文件变更丢失换成 chokidar 之后基本稳了这个细节直接决定了工具在多平台下的可靠性。2.4 数据持久化与自动保存策略对于写作工具数据安全永远是第一位的。我做了三层防护第一层是常用编辑器的自动保存文档内容变化后 800ms 内写入磁盘第二层是自动备份每隔 5 分钟把当前打开文件的上一版本复制到.backup目录第三层是退出拦截如果检测到未保存的更改会提示用户确认。自动保存看似简单但要处理好一个冲突场景当外部修改了文件而编辑器内部也有未保存的内容时直接覆盖会把外部修改弄丢。我的处理策略是检测到文件变更时先读取磁盘内容与编辑器当前内容比对如果内容不一致就在编辑器内弹出冲突提示让用户选择保留哪个版本。这个细节做不好写作者辛辛苦苦写的内容可能一眨眼就没了。3. 「好看」的落地外观设计与主题系统3.1 设计原则内容优先与克制的视觉层级好看这件事见仁见智但我始终认为编辑器的好看应该服从于一个目标让内容本身成为视觉焦点。我的设计原则有两条一是内容区必须干净通透二是功能区域必须安静不抢眼。编辑器整体采用三栏布局最左侧是文件树中间是编辑区右侧是预览区。文件树底色比主区深一档编辑区和预览区保持近乎纯白的底色让视线一打开应用就会落到文字上。界面上尽量不出现无意义的装饰线、渐变和投影宁可用留白来区分区域也不用阴影强刷存在感。这些设计决策看起来不难实际上要把「克制」落到每个像素上需要不断打磨。3.2 字体与排版中文写作的细节打磨Markdown 的大段文字阅读体验很大程度上取决于字体和排版。正文我最推荐「思源宋体」与「霞鹜文楷」的组合西文搭配 Inter 或 Source Serif代码字体用 JetBrains Mono。行高设置在 1.7 到 1.8 之间段落间距控制在 1.2em最大宽度限制在 780px避免长行文本造成阅读疲劳。中英文混排的处理是很多编辑器不太注意的细节。我手动实现了中文与英文、数字之间的自动间距调整也就是「盘古之白」让文字在视觉上更加透气。代码块的样式同样做了精细处理背景色要比正文底色深一档但不发黑圆角控制在 6px代码字号略小于正文字号避免代码喧宾夺主。这些细节单独看都不起眼组合在一起就是所谓的高级感。3.3 主题系统CSS 变量驱动的动态换肤主题系统的高频操作是换肤我的实现思路是用 CSS 变量统一管理所有颜色切换主题时只需要替换一组变量即可。比如定义--bg-primary、--bg-secondary、--text-primary、--text-secondary、--accent-color等十几个核心变量预览区的所有样式都引用这些变量。这样做的好处是用户完全可以不写一行代码只需要编辑一个简单的 JSON 配置文件就能定制出属于自己的主题。内置了亮色、暗色、护眼模式三套默认主题暗色模式下所有颜色都经过了对比度校验保证长时间写作不会刺眼。护眼模式则是把背景色调成淡绿色适合夜晚或长时间写作的场景。3.4 预览区的排版即最终发布效果预览区的渲染效果直接决定了「所见即所得」的可信度。我不仅把 Markdown 转换为 HTML还引入了一份精心调校的 GitHub Markdown 风格样式并在此基础上做了一些中文排版的优化标题自动加上分隔线、引用块改用左侧色条而不是背景色、表格做 zebra striping、图片默认 max-width 为 100% 并加圆角。预览的字体和间距与常见博客平台高度接近这样你在编辑器里看到的效果基本就是发布后的效果不会出现「编辑器里美如画平台上一团糟」的割裂感。为了进一步还原发布环境我还支持在设置里调整预览区的最大宽度和字体大小适配不同的平台排版习惯。4. 「彪悍」的支撑核心功能实现与性能优化4.1 大文档性能从卡顿到流畅的关键优化手段作为重度用户大文档的性能是我的底线。几万行、几十万字的 Markdown 文档放入编辑器后如果输入迟滞、滚动卡顿那其他功能再好看也没用。CodeMirror 6 本身的增量渲染机制已经解决了大部分问题但我还需要处理预览区和校验逻辑的性能瓶颈。我做三件事解析预览的防抖、大文档的虚拟滚动、以及逻辑分块渲染。解析防抖上面提过了滚动方面预览面板在文档超过 200 行后自动启用 IntersectionObserver 进行懒加载看不到的内容不渲染。编辑器输入监听则通过 requestIdleCallback 调度确保核心输入操作永远优先响应。实际测试中一份 5000 行左右的 Markdown 文档输入延迟稳定在 20ms 以内滚动全程不掉帧。4.2 行内样式与语法高亮的实现细节Markdown 语法高亮看似简单实际上坑很多。最常见的是嵌套语法比如一段加粗文字内部又有行内代码或者标题行内包含链接正则表达式处理起来非常痛苦。我采用的做法是基于 CodeMirror 6 的 lezer 语法解析器为 Markdown 编写了完整的语法规则文件让编辑器真正「理解」Markdown 的结构而不是靠正则猜。代码块的语法高亮我用的是 Lezer 搭配 language 包支持常见编程语言。这里有一个值得注意的细节高亮的颜色一定要在不同背景下都有足够对比度亮色模式下代码高亮用了深色调暗色模式下降则自动切换为亮色调保证两种主题下的可读性。4.3 全文搜索与替换编辑器效率的试金石本地写作工具的搜索体验直接影响日常效率。Markdown 编辑器里的搜索有几种层次普通字符串搜索、大小写敏感切换、正则搜索、以及跨文件搜索。我全部实现了。跨文件搜索的底层逻辑很直接遍历当前工作区内所有 Markdown 文件使用 ripgrep 这个命令行工具做快速匹配把结果按文件路径和行号聚合点击结果时直接打开对应文件并跳转到指定行。相比纯 Node.js 实现ripgrep 的搜索速度几乎是秒出上千个文件的目录也毫无压力。这个选型实测下来搜索体验非常接近 VS Code。4.4 扩展能力自定义渲染器与命令面板一个真正「彪悍」的编辑器不能让用户只能用它内置的能力还得支持使用者自己扩展。我预留了两类扩展点自定义渲染器和命令面板。自定义渲染器是这么运作的你可以提供一个函数接收 Markdown 解析出来的 AST 节点返回自定义的 HTML 字符串。比如你可以在文档里写一段特殊的代码块语法渲染器识别后把它变成一个包含交互元素的组件。命令面板则是类似 VS Code 的 CtrlShiftP把所有功能暴露成可搜索的命令条目提高键盘流用户的操作效率。这些扩展让一个通用编辑器变成了「你的」编辑器。5. 常见问题与调试实录你大概率也会踩的坑5.1 中文输入法导致的组合输入异常这是我开发过程中遇到的最折磨人的问题。在 Chromium 内核中通过中文输入法打字时编辑器会先接收到一个 compositionstart 事件此时输入框里的内容处于「组合中」状态如果编辑器在这个时候去做文档内容校验或格式化会把组合中的文字弄乱。解决办法很标准但必须写对在 composition 事件序列中不要触发任何 doc 变更监听逻辑所有格式化、自动补全在这段时间内挂起。等 compositionend 事件触发后再统一执行一次完整的解析。很多编辑器在这块处理得不够细致用户用中文输入时会出现字符丢失、光标乱跳的问题我这个编辑器从根上避免了。5.2 大文件打开慢与内存溢出的排查实录有大文件必然有内存压力。有一次我测试一份约 2 万行的 Markdown 文档打开时编辑器卡了好几秒内存直接飙到 800MB。排查后发现两大元凶一是打开文件时一次性读了整个文件又同时做了重复的语法树解析二是预览区对全文做了 HTML 渲染字符串拼接大量临时对象撑爆了内存。事后我做了两个调整读取文件后先快速渲染前 100 行给用户即时反馈再在 requestIdleCallback 里做全量解析预览区则改成按需渲染只有滚动到对应区域时才生成对应 DOM 节点。优化后同样的文档打开时间降到 1 秒以内内存占用稳定在 200MB 左右观感好了非常多。5.3 Windows 与 macOS 的文件路径兼容性问题文件路径兼容性是个下来极其烦人的问题。Windows 系统使用反斜杠作为路径分隔符macOS 和 Linux 使用正斜杠很多字符串操作在处理路径时容易出 bug。我在插入图片、导出文件、文件关联等模块都遇到了这类坑。最终解决方案是统一封装了一个路径处理工具函数在读取路径时统一转换成/分隔的格式写入磁盘时再根据当前平台转换回来。对于 Markdown 文档中的相对路径引用也一律使用正斜杠写入这样用户在 Windows 和 macOS 之间来回切换时文档内容不会因为路径分隔符而报错。这个小细节重度用户跨平台写作时会非常感谢。5.4 实时预览与光标定位的同步修复方案编辑区滚动到某个位置预览区也要跟着走这看起来简单实现起来涉及到滚动容器高度、文档偏移量、渲染节点位置三者之间的换算很容易出现「差一行」或者「跳来跳去」的问题。我的方案是给每个标题元素生成一个唯一的锚点 ID在 Markdown 渲染时把它们映射到对应的行号。编辑区的光标位置变化时反向查找它所属的标题锚点然后让预览区滚动到该锚点。这样可以保证滚动同步是稳定的而不是通过估算行高换算出来的。因为标题在整个文档中的位置是明确对应的很少出现偏差。6. 发布与后续规划从自用工具到开源项目6.1 打包分发与用户反馈收集项目打磨到一定阶段后我把它打包成 Windows、macOS、Linux 三个平台的安装包。用 electron-builder 做多平台构建过程中遇到最多的还是图标尺寸和安装包签名问题。macOS 的签名需要开发者证书对个人开发者来说成本比较高我先以未签名版本提供给社区测试用户自行右键打开即可。发布后最惊喜的是收到不少真实用户的反馈比如有人提出预览区的代码行号、有人希望支持 Emoji 快捷键、还有人需要 vim 模式。这些来自真实使用场景的诉求比自己闭门造车高效得多。我有选择地吸收这些建议把高价值的合并进迭代计划。6.2 未来迭代方向插件系统与移动端适配目前编辑器的扩展能力还停留在「配置文件驱动」的阶段下一步我计划做一个完整的插件系统让用户可以通过 npm 安装第三方插件。插件可以注册新的渲染器、添加命令面板条目、甚至修改编辑器的菜单栏。移动端适配则是另一个话题。平板和手机上的 Markdown 写作需求其实在增长但编辑器的交互模型需要大幅调整目前主要聚焦在文件同步和预览阅读体验上完整的移动端输入方案还在调研中。无论后续怎么发展核心原则不会变好看和好用缺一不可。最后再分享一个小技巧。如果你也打算开发自己的编辑器别一开始就纠结「大而全」先把你最高频的 20% 功能做到极致剩下的用插件或扩展去补齐。一个编辑器让人愿意天天打开靠的不是功能列表有多长而是每一次敲击键盘的手感、每一眼看到排版时的舒适度。我在这个项目里最大的收获就是明白了这个道理。