cxuan-ai-labs

工具、资源与工作台 ·

我为什么又造了一个公众号排版工具

文章内容写完,只能算完成了一半。剩下一半,是怎么让它在手机上看起来像个人写的,而不是从某个后台系统里直接倒出来的说明书。

进入动态阅读模式 ↗

[toc]

对于写公众号来说,排版这事儿真的挺折磨人的。

文章内容写完,只能算完成了一半。剩下一半,是怎么让它在手机上看起来像个人写的,而不是从某个后台系统里直接倒出来的说明书。

这事我之前也没少折腾。

一开始用过 mdnice。优点很明显,模板多,一键渲染,初始体验确实不错。你把 Markdown 往里一贴,右边效果就出来了,刚开始会有一种“还可以”的感觉。

但用久了以后,问题也出来了。

模板风格比较固定,想细调就不太顺手;每次渲染也容易变慢;更重要的是,预览的时候是一回事,复制到公众号里又是另一回事。

后来又用过 md.gzcx.net。这个工具的好处是可以自定义 CSS,短期内挺爽。你想调字号、调间距、调标题样式,都能自己写。

但问题是,长期维护一套公众号 CSS,真不是啥轻松活。

今天调一下引用块,明天表格又不舒服;这篇文章代码块多,那篇文章图片多;再加上微信公众号编辑器自己还会过滤一部分样式,最后就变成了:我明明是在写文章,结果每天都像是在调兼容性 bug。

这您受得了吗。

所以针对这两个工具的痛点,下决心写了一个我自己的排版工具 --- Layweout

项目地址:https://github.com/crisxuan/layweout

线上地址:https://layweout.vercel.app

Layweout 是什么

Layweout 这个名字来自 layout + we

layout 是布局排版,we 是微信公众号,合起来就是一个专门给公众号场景做排版输出的工具。

这个名字我自觉有点小聪明,但还算逻辑自洽。

它不是一个单纯的 Markdown 渲染器,也不是一个模板展示器,而是一个面向公众号发布流程的本地工作台。

现在的工作流大概是这样:

左侧是样式区,选择文章模板。

中间是文本区,可以输入 MarkdownHTML,也可以直接粘贴富文本。

右侧是手机端阅读预览,用来快速判断排版节奏。

最后点击复制,工具会重新生成一份适合公众号编辑器粘贴的内联样式 HTML。

这里有个很关键的点:

Layweout 不直接复制预览 DOM。

因为浏览器预览和公众号编辑器根本不是一个环境。

浏览器里能跑的 CSS,公众号编辑器不一定认。你在本地看着背景、渐变、圆角、边距都挺好,粘进去以后可能直接没了。

所以 Layweout 的核心逻辑不是“把右边预览复制过去”,而是:

输入内容
  ↓
统一清洗和渲染
  ↓
浏览器预览
  ↓
重新生成公众号兼容 HTML
  ↓
复制到公众号编辑器

说白了,预览是给人看的,导出是给公众号编辑器看的。

这俩不能混在一起。

为什么我不满足于现成工具

mdnice 这类工具当然不是不好。

它适合快速排版,适合不想折腾的人。模板一选,文章一贴,基本就能出稿。

但我的问题是,我写的东西经常比较长,而且内容类型不固定。

有时候是 AI 工具体验,有时候是技术踩坑,有时候是产品思考。里面可能有代码块、表格、引用、截图、列表、分割线。

这时候模板如果只是“看起来漂亮”,是不够的。

它必须得扛得住真实内容。

我对排版工具的要求其实就三条:

第一,正文要舒服。

公众号大部分阅读都发生在手机上,正文的字号、行高、段间距,比花哨的标题重要多了。

第二,复杂元素不能炸。

代码块、表格、引用块、图片说明,这些东西一旦出问题,整篇文章立刻显得很业余。

第三,复制到公众号以后不能大变样。

预览再好看,粘进去崩了,那都是白搭。

现成工具最大的问题,就是第三点。

它们通常更重视预览效果,但对“公众号编辑器到底认不认这份 HTML”这件事,处理得不够彻底。

而 Layweout 反过来想。

先保证粘进去稳,再谈样式好不好看。

这个顺序很重要。

前端架构怎么设计

Layweout 现在的架构不复杂,但边界比较清楚。

大体分成几层:

core     负责输入内容清洗和转换
data     负责模板数据、分组和 schema
ui       负责页面结构
styles   负责工作台 UI 和浏览器预览样式
export   负责公众号兼容 HTML 输出
utils    负责剪贴板、统计等辅助能力

这个分层的目的不是为了显得多高级。

而是为了避免以后每改一个模板,都要全项目到处找地方。

我最烦这种代码:

今天新增一个模板,要改数据。

还要改 UI。

还要改 CSS。

还要改导出逻辑。

还要改复制逻辑。

最后还不知道有没有把其他模板弄坏。

这种东西一开始写起来快,但后面维护起来很痛苦。

所以 Layweout 里把几个变化点拆开了。

输入层:先把内容收敛起来

用户输入可能来自三种地方:

  • Markdown
  • HTML
  • 富文本

这三种东西表面上都是文章,但结构完全不一样。

Markdown 要先通过 marked 转成 HTML。

HTML 和富文本要经过 DOMPurify 清洗,去掉危险或无意义的属性。

然后工具还会做一些规范化处理,比如把 b 转成 strong,把 i 转成 em,把散落的文本节点包成段落。

这个步骤看起来很基础,但特别重要。

输入不收敛,后面所有模板都是在赌。

你不能指望一堆来源不一致的 DOM,直接套样式还能稳定。

所以 Layweout 的第一步不是变漂亮,而是先把内容变干净。

模板层:模板不是一堆 CSS

Layweout 现在内置了 22 套带独立导出 profile 的模板,按出版、科技、品牌生活分组。

这 22 套模板不是简单换颜色。

每套模板都有自己的 tokens,比如:

  • 页面背景
  • 正文颜色
  • 强调色
  • 引用块背景
  • 代码块颜色
  • 表格样式
  • 字体
  • 行高
  • 间距
  • 外层容器背景

这些东西集中放在模板 registry 里。

大概是这个思路:

presets/
  groups.js    模板分组和治理状态
  registry.js  模板 token
  schema.js    模板校验和可见模板派生

这个设计解决了一个很实际的问题:

模板多了以后,需要治理。

不是所有模板都应该暴露给用户。

早期的一些模板可能能用,但没有独立导出 profile,也不够稳定。那就先隐藏,而不是硬塞到选择器里凑数量。

模板库不是越多越好。

打开以后每一个都能用,才是真的好。

预览层:让人判断阅读节奏

预览层负责浏览器里的展示效果。

这里可以用 CSS 文件,可以用 CSS 变量,可以做更舒服的手机阅读模拟。

Layweout 的预览不是为了 100% 复刻公众号编辑器,因为这个事情本身就不现实。

公众号编辑器会过滤样式,不同内容粘贴进去也可能有差异。

所以预览层的目标是:

尽量模拟公众号手机端阅读效果,让作者快速判断文章节奏。

标题是不是太重。

正文是不是太挤。

引用块是不是抢戏。

代码块是不是可读。

表格有没有溢出。

这些问题,在预览区就应该能看出来。

但最终复制效果,还是以导出模块重新生成的 HTML 为准。

这也是 Layweout 和很多排版工具不一样的地方。

它没有把预览当最终结果。

它把预览当一个中间环节。

导出层:真正的重点在这里

Layweout 最核心的地方,其实是 src/export/wechat.js

这个模块会重新生成一份公众号兼容 HTML。

流程大概是这样:

拿到渲染后的 HTML
  ↓
根据当前模板 profile 生成内联 style
  ↓
处理链接、列表、引用块等结构
  ↓
补充 background-color 兜底
  ↓
对背景和引用块做公众号兼容处理
  ↓
返回可以复制的 HTML

为什么一定要内联样式?

因为公众号编辑器不会完整保留外部 CSS。

你用 class 写得再优雅,复制进去以后可能什么都没了。

所以导出时要把关键样式写到元素的 style 属性里。

这听起来不优雅。

但公众号排版不是前端架构审美大赛。

能稳定粘贴,才是第一目标。

Layweout 还做了几个兼容策略。

第一,复杂背景会补 background-color

因为渐变和复杂背景可能丢,但纯色背景相对更稳。

第二,带背景的模板会做外层包裹。

这样可以尽量避免粘贴后块与块之间出现白缝。

第三,引用块会做额外处理。

有些引用块用普通 blockquote 在公众号里不稳,所以会转成更保守的表格结构。

这听着有点老派。

但没办法,公众号编辑器就吃这一套。

为什么不把标准模式暴露给用户

之前工具里做过“标准模式 / 兼容模式”。

标准模式输出更轻,兼容模式输出更稳。

听起来挺合理。

但后来我想明白了,这个按钮不应该给用户看。

因为用户来用 Layweout,不是为了研究 HTML 输出策略的。

他只想知道:我复制到公众号里,会不会变形。

那默认就应该给最稳的兼容导出。

所谓标准模式,保留在内部测试里就行了。比如做快照对比、排查导出结构变化时有用。

但放在界面上,只会制造选择负担。

很多产品的问题,不是功能不够,而是把本该系统承担的复杂性,甩给了用户。

这事我现在特别警惕。

用户不应该理解你的兼容策略。用户只应该得到一个稳定结果。

快照测试:防止改一个模板,炸一片模板

做模板工具,还有一个很容易忽略的问题:回归测试。

22 套模板,看起来不算特别多,但如果每次都手动测试,根本不现实。

你改了一个 blockquote 的导出逻辑,可能影响所有模板。

你改了背景兼容策略,可能让某几个模板出现断层。

你改了列表标记,可能把有序列表和无序列表一起弄乱。

所以 Layweout 加了导出快照测试。

测试会拿一份覆盖比较全的文章样例,里面有标题、正文、链接、引用、列表、代码块、表格、图片,然后对所有可见模板生成导出 HTML 快照。

以后改导出逻辑,只要跑:

npm run test:snapshots

如果快照变了,就说明输出确实发生变化。

这时候再判断,是预期修改,还是误伤。

这个东西没有什么视觉冲击力,但很实用。

因为排版工具最怕的不是今天有 bug。

最怕的是你今天修了一个模板,顺手把其他 21 个模板打坏了。

Layweout 的优势到底在哪

总结一下,Layweout 的优势不是“模板更多”。

其实 22 套模板也不算特别夸张。

它真正的优势在四个地方。

第一,本地工作台。

不用每次依赖一个在线编辑器状态,打开就能写,输入、预览、复制都在一个地方完成。

第二,输入来源更宽。

Markdown、HTML、富文本都能进来,而不是强迫你必须用某一种写作方式。

第三,预览和导出分离。

浏览器预览负责看效果,公众号导出负责兼容性。这两个边界一拆开,问题就清楚很多。

第四,模板有治理。

模板不是随便堆 CSS,而是有 token、有 profile、有分组、有 schema、有快照测试。

这些东西放在一起,最后带来的体验是:

你可以更放心地写长文。

不用每次都担心复制以后排版变形。

不用每篇文章都手调 CSS。

不用把注意力浪费在“这个引用块为什么又没背景了”这种问题上。

其他排版工具的问题,不是不好,而是不适合我的长期场景

这里还是要说一句,mdnice 和 md.gzcx.net 这类工具并不是没价值。

它们解决的是快速排版问题。

如果只是偶尔发文,或者对模板细节要求不高,这些工具完全够用。

但我的场景不是这样。

我需要长期写。

需要反复改。

需要不同类型文章都能保持稳定。

需要代码、表格、引用、图片这些复杂内容都能扛住。

这时候,传统排版工具的问题就会变明显:

预览和复制结果不一致。

模板看着多,但风格很固定。

自定义 CSS 前期爽,后期维护累。

复杂背景和布局进公众号以后容易丢。

文章越长,手动修补成本越高。

所以我不是想做一个“更花哨的 mdnice”。

我想做的是一个更可控的公众号发布工作台。

最后

排版这事,说小也小。

它不像模型能力,不像产品架构,不像数据库性能,看起来没那么硬核。

但对内容创作者来说,它非常现实。

一篇文章写得再认真,如果排版乱七八糟,读者的阅读成本会立刻升高。

尤其公众号这种场景,大部分人都在手机上看。

屏幕就那么大。

正文挤一点,标题重一点,引用块乱一点,读者可能就划走了。

所以我现在越来越觉得:

排版不是装饰,排版是内容的交付方式。

Layweout 做的事情,本质上就是把这条交付链路变稳定。

从输入,到预览,到模板,到复制,到公众号编辑器,每一步都尽量收敛。

不追求花哨。

先追求稳定。

稳定之后,再追求好看。

这也是我现在做工具越来越明确的一个判断:

好工具不是把所有选项都摊开给用户,而是把复杂性消化掉,然后给用户一条最顺的路。

Layweout 现在还不算完美。

但至少它已经解决了我最头疼的问题:

文章写完以后,我不用再和公众号编辑器斗智斗勇了。

这就已经很值了。