一个人,10年,单挑 Google Docs

一个人,10年,单挑 Google Docs

富文本编辑器ProseMirrorGoogle Docs浏览器开源Wordgard

数据源:HN + Lobsters · HN

你在 ChatGPT 里敲下 prompt,在 Linear 里写 issue,在 Notion 里记笔记。这些输入框里平滑的光标移动、正确的撤销重做、不会莫名崩坏的格式——背后是一套叫 ProseMirror 的编辑器框架。而这个框架的作者,是一个荷兰人,一个人维护了十年。

2026 年 7 月 2 日,Marijn Haverbeke 发布了他的第六个编辑器:Wordgard 0.1。他不再只做框架——这次他要做一个完整的、面向终端用户的富文本编辑器。用他自己的话:“我仍然为 ProseMirror 感到骄傲,但它有太多让我每次触碰都皱眉的设计。”

Wordgard 标题图,手绘风格的艺术字 (图片来源:wordgard.net,插画由 Kamila Stankiewicz 创作)

这个荷兰人是谁

Marijn Haverbeke,独立开源开发者,住在柏林。如果你不是前端工程师,可能没听过这个名字,但你几乎一定用过他的代码。

他写了《Eloquent JavaScript》,这本书是整整一代前端开发者的入门教材。他写了 CodeMirror,浏览器内代码编辑器的标杆,Obsidian、Chrome DevTools 的代码面板都在用。他写了 Acorn,一个 JavaScript 解析器,webpack 和 Babel 在早期都依赖它。他写了 ProseMirror——而 ProseMirror 几乎承包了整个互联网的文本输入框。

ChatGPT 的输入区是 ProseMirror。Gemini 也是。LinkedIn 的动态编辑器是 ProseMirror。纽约时报的 CMS 后台是 ProseMirror。Zoho Writer(Google Docs 的竞品)、Atlassian 的 Confluence、GitLab 的 Wiki 编辑器——全是 ProseMirror。还有一个叫 Tiptap 的 YC 创业公司,整个商业模式就是给 ProseMirror 包一层用户友好的壳。

Hacker News 上有条评论说:“ProseMirror over the years has become the backbone of editors on the web.” 然后补了一句,“The thought that ProseMirror is no more in active development is scary.”

Marijn 本人出来澄清:ProseMirror 会继续维护。但他确实把精力转向了一个新项目。这是一次从零开始的架构重写。

为什么不做 ProseMirror 2.0

这是一个 Marijn 自己纠结过的问题。HN 讨论里有人问得很直接:“Why not just make it ProseMirror v2?”

Marijn 在博客里给出了三层理由。第一层是工程上的:ProseMirror 的很多设计问题根植于其核心抽象,要修复就必须打破兼容性。一个不兼容的 2.0 和一个新项目本质上没有区别,但会制造命名的混乱——人们说”ProseMirror”时到底指哪个版本?

第二层是美学上的:“I’m not all that fond of the ProseMirror pun anymore.” CodeMirror 是给代码的镜子,ProseMirror 是给散文的镜子——这个双关他玩腻了。

第三层最硬核:“Trying to graft stuff on in a backwards-compatible way as an 1.x version would produce a compromised win32-style mess.” 翻译一下:向后兼容的渐进式改造会产出一个 win32 级别的屎山。

所以他选择绿色田野(green field)重写。代码从零开始,不背负任何兼容包袱。但核心概念——基于 schema 的文档模型、token 计数的位置索引系统、通过 contenteditable 做输入层而非文档模型——全部保留。这是他十年踩坑积累下来的、被验证为正确的架构基石。

Wordgard 官网的手绘插画——一只大黄蜂,评论者形容为「Ghibli-esque」 (图片来源:wordgard.net,插画由 Kamila Stankiewicz 创作)

浏览器里做编辑器,到底难在哪

要理解 Marijn 为什么重写,得先理解一个基础事实:浏览器的 contenteditable 是一个烂到骨子里的 API。

Hacker News 上有个叫 nicoburns 的评论者说得最狠。他过去两年在从头写一个新的浏览器引擎,但他认为十五年前写的 WYSIWYG 编辑器项目可能更难——“albeit I was a lot less experienced then.” 另一个评论者 Tade0 补充:“For the longest time browser vendors couldn’t even agree on the details of how just text selection should work.”

早期编辑器如 FCKEditor 和 TinyMCE 的做法很简单粗暴:开一个 contenteditable 的 div,用户随便改,编辑器监听 DOM 变化然后”修复”不想要的行为。结果呢?atombender 在 HN 上回忆:“The result was rife with bugs and inconsistencies.”

ProseMirror 做了一个根本性的方向转变:**不再把 contenteditable 当成文档模型,只把它当成输入层。**编辑器自己维护一个内部的文档树(document model),监听浏览器在 contenteditable 区域里的行为,翻译成对内部模型的修改,再重新渲染到 DOM。这层”翻译”的代码量极其庞大。

而 Wordgard 在这条路上走得更远。它在五个核心设计上推翻了 ProseMirror 的做法。

五个关键架构决策

1. 抛弃 Step,拥抱 Delta

ProseMirror 的变更表示是最让 Marijn 自己头疼的部分。一个编辑操作被拆成多个原子 Step,每个 Step 基于前一个 Step 产生的新文档来定义自己的操作。这意味着你在构建事务时必须手动补偿每个 Step 造成的文档偏移。Marijn 在博客里的原话是——“statements dreamed up by the utterly deranged.”

Wordgard 换成了从 CodeMirror 6 继承来的 Delta 格式。一次变更就是一个序列:[keep N] [replace N with “新内容”] [keep M] [update N +bold] … 所有变更都基于同一个起始文档描述,不再需要逐级补偿。单个事务永远只包含一个变更对象,可以轻松组合和变换。

这个设计得到了 Zoho Writer 团队成员的公开背书。lewisjoe 在 HN 上写道:“I’m part of the Zoho Writer team (Google Docs alternative). And the new architecture is very similar to Zoho Writer… I’ve always wondered why ProseMirror’s transactions & steps couldn’t be simplified further so I’m one vote up for the new design direction!”

2. 自己画光标

ProseMirror 把光标和选区的行为委托给了浏览器。想法很合理:处理双向文本和怪异 CSS 排版的光标移动实在太难了,交给浏览器自己做。但浏览器的实现太差——拒绝在某些内容前移动光标、有时干脆不画光标、有时画错位置、鼠标拖选在某些情况下直接崩。

Wordgard 咬碎了这颗硬骨头:**自己实现所有键盘和指针的选区逻辑。**这意味着它必须自己处理双向文本(RTL),自己建模布局,自己画光标。唯一保留的原生选区是触屏——因为重实现触屏选区会不可逆地破坏原生上下文菜单,在手机和平板上这不太能被接受。

3. Facet 扩展系统

ProseMirror 的插件系统是平面数组,每个插件做一堆不同的事,经常出现”希望这个插件在 hook A 上优先级低、在 hook B 上优先级高”的尴尬。

Wordgard 直接搬了 CodeMirror 6 的 Facet 系统。扩展是树状结构,每个 Facet 是一个类型化的扩展点,可以独立控制优先级。一个功能通常由一组互相协作的扩展组成,直接丢进配置就能用。第三方也可以定义自己的 Facet 作为扩展点。

4. 放松的 Schema

ProseMirror 的标志性特性是用正则表达式精确约束一个父节点可以包含哪些子节点、以及它们的顺序。Wordgard 不再支持这个特性。

Marijn 的理由有两个。首先,太强的约束让编写通用文档操作代码几乎不可能——代码必须针对特定 schema 才能知道什么操作合法。“If the person who designed the system cannot use it, that’s not a good sign.” 其次,硬锁定的文档形状会伤害编辑体验——用户通过一连串小编辑逐步逼近目标形状,如果编辑器不允许中间态,就会频繁打断用户。

替代方案是”Correction”:用程序化方式修正不合规的文档形状。因为是程序,可以比正则约束更智能、更上下文感知地处理问题——比如确保表格是矩形的,这件事连 ProseMirror 的约束都做不到。

5. Mark 替代 Node Attribute

ProseMirror 里,文字对齐、替代文本这类属性直接挂在节点上,这导致想给 schema 加一个”对齐”功能就必须手动给每个文本块节点加这个属性。Wordgard 把这类东西全部抽象为 Mark,可以独立于节点类型定义,schema 层面不需要修改节点本身就能添加新功能。

张力:一个人 vs 一支军队

Google Docs 背后是一个组织。Chrome 团队负责浏览器底层,Google Docs 团队负责上层应用,两拨人可以互相协调。Marijn 是一个人,他得同时关心浏览器的实现质量、编辑器的架构设计、npm 包的发布、文档的撰写、论坛的维护。

但这种不对等里藏着一个有趣的对称。Lewisjoe(Zoho Writer)指出 Wordgard 的架构和 Zoho Writer 非常相似——而 Zoho Writer 是 Google Docs 的直接竞品。换句话说,Marijn 一个人重新发明的架构,和一支专业团队多年打磨出来的方案在核心思路上趋同了。

另一层张力在许可模型上。Wordgard 是 MIT 许可——完全自由,包括商用。Marijn 在博客里承认他认真考虑过更严格的许可模式,但最终选择了”丰饶模型”(model of abundance):给世界提供足够多的价值,即使只有一小部分回流,也足够生活。

但他也写了一长段愤怒的话:“regardless of license, it is clear to me that the ‘AI’ vultures will slurp up my code and all the hard-won ideas it contains moments after I publish this. I wish a pox upon them and their slop machines.”

网站页脚有一行小字:“Contains 0% AI.” Lobsters 上点赞最高(15 分)的评论就是对着这句话竖大拇指:“Not having PRs is an effective way to nip the problem of people submitting LLM-generated slop code in the bud. And nice art made by a real, human artist.”

Marijn 做了一个可能引发争议的实验:**不接受 Pull Request。**他解释的理由很直白——review 一个大改动、协商各种调整以满足他的预期,通常比他自己实现这个改动更费时间。“now that the cost of generating code has gone down dramatically, the arrangement where people can dump code on me and I’m expected to review and maintain it has become even less attractive.”

社区反应:兴奋与质疑并存

HN 的话题拿到了 234 分、85 条评论。Lobsters 上 64 分、20 条评论。Marijn 本人出现在两个讨论区里逐条回答问题。

赞誉集中在这几个方向:有人对艺术风格赞不绝口——插画师 Kamila Stankiewicz 的手绘风格被形容为”Ghibli-esque”。Zoho Writer 的工程师公开背书架构方向。有人感谢他写了 Eloquent JavaScript。

Wordgard 在线试用页面的编辑器截图 (图片来源:wordgard.net/examples/)

质疑也坦率直接。有人问”为什么不直接用 Lexical(Meta 的开源编辑器)?“——引来了一轮关于 Lexical 维护状态的辩论。有人反馈移动端 bug:Firefox Android 上全选后删除会导致编辑器彻底崩溃。Marijn 在几分钟内就开了 bug ticket。

最多人关心的是迁移成本。Tiptap、纽约时报的 react-prosemirror 等生态项目怎么办?Marijn 的回答非常务实:“If you’re using ProseMirror and it works for you and your system is stable, just stick with it. If you’re feeling its limitations and/or are starting something new, once Wordgard is stabler, you’ll probably want to reach for that.” 版本号 0.1 本身就是态度:至少一年内不会稳定,欢迎测试,但别上生产。

不换框架的人也该读读这篇博客

即使你不用 Wordgard,Marijn 那篇发布博客也值得花半小时读完。它本质上是一个十年编辑器架构经验的复盘文档:哪些抽象被证明是错的,哪些是对的,为什需要推倒重来——每一条判断背后都是生产环境的真实教训。

比如他说 ProseMirror 通过监听 DOM 变化来推断用户编辑行为的”hack”——在 2026 年的浏览器里终于可以通过 beforeinput 事件正规化处理了。这背后是 9 年浏览器标准的演进。

比如他说 CRDT 集成树形富文本文档”not actually a solved problem”——所有已知方案要么把文档拍平成块序列(限制 schema),要么在强制结构约束时有收敛问题,要么在 split/join 节点时产生不必要的巨大变更。这不是学术争论,是实打实的工程取舍。

最后

写完这篇的时候,笔者反复确认了一件事:ProseMirror 还在维护。Marijn 在做一件罕见的事——在十年经验的基础上,用一套干净的 slate 重做一遍自己最擅长的东西。

Wordgard 能不能成为 Google Docs 的替代品?0.1 版本说这个话题还太早。但这个项目的存在本身就传递了一个信号:在 2026 年的浏览器里,一个人仍然可以挑战一群人的领地。


参考链接:


本文素材来自 Hacker News 和 Lobsters 的相关讨论、Wordgard 项目官网及 Marijn Haverbeke 个人博客。文中涉及的技术判断均来自原始讨论参与者和 Marijn 本人的公开表态,不代表笔者立场。