为什么要在 markdownToHtml 出口统一压掉标签间空白
先说结论:如果多个平台共用同一条 Markdown 转 HTML 渲染链,最稳的修法通常不是在单个平台适配器里补丁,而是把结构噪声在共享出口一次清干净。
这次 OmniPost 修的就是一个很典型的共享层问题。问题最早在微信新版编辑器里暴露:列表项之间的换行空白会被解释成空列表项,结果 4 项列表能显示成 9 项,奇数编号全空。但真正重要的不是“微信出 bug 了”,而是这份 HTML 本来就会被知乎、头条、搜狐、WordPress、Ghost 等多个 HTML 平台复用。
为什么修复点要上移
如果根因来自共享 HTML,那么把补丁留在单个平台里,只能解决局部问题:
- 微信安全了;
- 其它 HTML 平台仍然可能收到带结构空白的内容;
- 新平台以后还得再补一次;
- 维护会退化成“哪个平台先炸就补哪个平台”。
而把修复上移到 markdownToHtml 出口之后,收益会直接放大:
- marked 路径和 fallback 路径一起生效;
- 所有共用渲染链的 HTML 平台自动继承;
- preview 和正式发布更容易保持一致;
- 以后扩平台时不用重新回忆历史坑补在哪。
这次到底压掉了什么
OmniPost 新增的 collapseStructuralWhitespace 并不是粗暴删掉所有换行,而是只处理结构标签边界上的空白,例如:
<ul>、<ol>、<table>、<thead>、<tbody>、<tr>开标签后的空白;- 这些容器闭标签前的空白;
</li>、</tr>、</td>、</th>之后到下一个标签之间的空白。
这类 whitespace-only 文本节点,对守规范的 HTML 消费方来说本就应该被忽略;但富文本编辑器未必按浏览器规则解释,所以提前清理更稳。
为什么不会误伤代码块
这次修复最关键的边界,是“清结构空白”不能伤到代码块。仓库测试里专门补了两类断言:
- 列表/表格结构标签之间不得再有空白;
代码块里的换行不能被破坏。
所以目标不是“压缩整份 HTML”,而是只清掉最容易被编辑器误解释的结构空白。代码文本本身仍然会保留。
为什么微信适配器里还留了一道兜底
因为还有一条路径可能绕过 markdownToHtml:用户可以直接提供 article.html。于是这次改动没有把 weixin 里的逻辑彻底删掉,而是让它委托共享函数,专门兜住用户直供 HTML 的场景。
这个分层值得记住:默认路径的问题在共享层修;绕过共享层的特殊输入,在适配器里留兜底。
一个很值得复用的判断规则
如果多个下游共享同一份中间产物,而问题来自这份中间产物的结构噪声,那么优先在产物出口统一净化,而不是在每个下游各自容错。
这类修法对内容分发工具特别值钱,因为它一次解决的不是某个平台的单点 bug,而是一整条内容管线的未来维护成本。
常见问题
为什么不只修微信?
因为问题不是微信独有字段,而是共享 HTML 输出里的结构空白。既然多个平台都吃同一份 HTML,就应该优先在共享出口统一处理。
所有 HTML 平台都会出空列表项吗?
不一定。更准确地说,是平台编辑器行为并不完全可预测,所以提前做防御性修复更稳。
把空白压掉会改变语义吗?
对守规范的消费方来说,这些结构边界上的空白本就应该被忽略,所以通常是语义无损的。真正需要保护的是代码块和正文文本,而测试已经把这个边界锁住了。
preview 和这次修复是什么关系?
preview 解决的是“你看到的内容像不像将要提交的内容”;这次修复解决的是“平台编辑器会不会把结构空白错认成内容节点”。
本文首发于 OmniGoAI 官网:https://omnigoai.com/zh/blog/omnipost-whitespace-fix-at-render-exit/ ——OmniPost,把内容一键分发到 30+ 平台。