现代化模板渲染
基于 React 与 TailwindCSS 的模板开发方案
本插件采用 React + TailwindCSS 技术栈进行图片渲染,将现代前端的工程化能力引入模板开发。
相比传统模板引擎在维护上的痛点——逻辑与视图耦合、全局 CSS 冲突、缺乏类型检查——本方案通过组件化构建、原子化样式和类型安全三条路径彻底解决了这些问题。
传统方案的维护陷阱
基于 art-template 的模板在项目迭代中往往会陷入维护泥潭: - 逻辑黑洞:业务逻辑混杂在 HTML 模板中,难以阅读和剥离。 -
样式冲突:全局 CSS 类名随时间推移不断堆积,修改一处可能导致多处崩坏。 -
重构风险:缺乏类型检查,修改字段名就像在"排雷",只能祈祷运行时不出错。
架构概览
整个渲染链路分为四层:
- 数据层(
@kkk/richtext):定义平台无关的富文本文档协议,作为 core 与 template 之间的数据边界。 - Core 解析层:将抖音、B 站等平台的原始 API 响应解析为标准的
RichTextDocumentJSON。 - Template 渲染层:React 组件消费 JSON 数据,TailwindCSS 处理样式,最终通过 SSR 输出静态 HTML。
- 输出层:Karin 框架调用 Puppeteer 截图,生成图片消息发送到群聊。
富文本文档(RichTextDocument)
@kkk/richtext 是 core 包与 template 包之间的共享子包,负责定义平台无关的富文本中间表示(IR)。
设计动机
不同平台的文本描述格式差异很大:抖音的富文本是一段带特殊标记的字符串,B 站则提供了结构化的内容节点。如果在 template 侧分别解析,会导致平台逻辑泄漏到渲染层,维护成本极高。
富文本文档将这一问题收拢到 core 层:core 负责把各平台的异构数据转换成统一的 JSON 节点树,template 只负责渲染。
节点类型
富文本文档支持两类节点:行内节点与块级节点。
行内节点用于描述段落内的文本片段:
| 节点类型 | 用途 | 示例 |
|---|---|---|
text | 普通文本,支持粗体/斜体/颜色/超链接 | 评论正文 |
emoji | 平台表情包图片 | [doge] |
mention / at | @用户 | @某某 |
searchKeyword | 搜索词高亮 | 带搜索图标的蓝色高亮文本 |
topic | 话题标签 | #某某话题# |
lottery | 抽奖信息 | 带抽奖图标的文本 |
webLink | 网页链接 | 带链接图标的标题 |
vote | 投票 | 带投票图标的标题 |
viewPicture | 查看图片提示 | 带相册图标的提示文本 |
lineBreak | 换行 | <br /> |
块级节点用于描述文档结构:
| 节点类型 | 用途 |
|---|---|
heading | 标题(1-6 级) |
paragraph | 段落 |
image | 图片 |
blockquote | 引用块 |
list / listItem | 有序/无序列表 |
codeBlock | 代码块(带语法高亮和行号) |
linkCard | 链接卡片 |
在 core 中使用
@kkk/richtext 的 parse 模块提供了一系列工厂函数,用于从平台原始数据中构建节点:
import { , , , } from '@kkk/richtext'
const = (
[
([
('这条视频太棒了', { : true }),
('doge', 'https://example.com/doge.png'),
('!推荐大家看看。')
])
],
{ : 'douyin' }
)core 侧只需要导入类型和节点创建方法,不依赖 React 运行时,输出的是可序列化的纯 JSON。
在 template 中使用
@kkk/richtext 的 react 模块提供了渲染器,将 RichTextDocument 转成 React 节点:
import { renderRichTextToReact } from '@kkk/richtext/react'
// 在平台模板组件中
const content = renderRichTextToReact(document, {
mention: { className: 'text-blue-500' },
topic: { className: 'text-pink-500' },
searchKeyword: { className: 'text-blue-600 bg-blue-100', iconClassName: 'text-blue-400' }
})
return <div className="text-base leading-relaxed">{content}</div>渲染器会自动处理以下细节:
- 文本转义:React 自动转义文本内容,防止 XSS。
- 图片安全:对
emoji和image节点的src做协议白名单校验(仅允许http://、https://和data:image/*;base64)。 - URL 自动识别:
text节点中的链接会被自动包裹为可点击的超链接。 - 行内样式:支持
bold、italic、strike、color、fontSize、link等行内样式。
为什么不用 dangerouslySetInnerHTML?
平台返回的富文本描述往往包含未经校验的 HTML。使用 renderRichTextToReact 将结构化 JSON 映射为 React 节点,可以避免直接注入 HTML
带来的安全风险,同时让样式完全由 Tailwind 接管。
节点归一化
createRichTextDocument 内部会自动调用 normalizeRichTextNodes,合并相邻的文本节点并丢弃空文本节点。这样 core 在解析时可以按匹配过程简单 push 节点,无需担心碎片过多的问题。
开发工作流
内置的可视化开发面板 (pnpm dev) 大幅提升模板开发效率:
- 实时预览:修改组件代码,浏览器即时刷新渲染结果(HMR 热更新)。
- 数据 Mock:可配置多套 JSON 数据,轻松测试文本超长、头像缺失等边缘情况。
- 快速调试:无需启动整个 Bot,仅需浏览器即可完成模板开发。
SSR 渲染引擎
core 包通过 Render 函数调用 template 子包的 reactServerRender,实现了插件逻辑与模板渲染的解耦。
入口与初始化:reactServerRender 接收渲染请求、输出目录和插件数组,首先确保输出目录存在,然后通过 ComponentAutoRegistry 扫描配置文件,按平台分组懒加载组件模块并绑定数据验证函数,最终存入 Map 注册表。
SSRRender 实例化:创建 ResourcePathManager 检测运行环境(开发/生产),解析包目录路径并处理 pnpm 符号链接;创建 HtmlWrapper 负责 HTML 包装;创建 PluginContainer 并按 enforce 字段(pre → normal → post)对插件排序。
渲染流程:构建 PluginContext 上下文和 RenderState 状态对象 → 执行 runBefore 前置插件(如二维码生成)注入额外 props → ComponentRendererFactory 从注册表查找组件、验证数据、合并 props、处理嵌套路径、调用 React.createElement → 执行 runDuring 渲染插件(可包装组件)→ 调用 renderToString 将 React 组件转为 HTML 字符串 → 执行 runAfter 后置插件(可修改 HTML)。
输出阶段:生成带时间戳的安全文件名 → HtmlWrapper.wrapContent 计算 CSS 和图片的相对路径、替换图片 src、注入 DOCTYPE/meta/CSS link/主题类名 → 写入文件系统 → 返回 HTML 文件路径供 Karin 框架截图。
性能优化
SSR 直接输出包含完整样式与内容的 HTML,Puppeteer 打开页面后无需等待 JavaScript 加载与执行即可立即截图,显著降低了渲染耗时与内存占用。
生态复用
得益于 React 标准,可以直接引入成熟的库来丰富静态画面的表现力:
- 排版布局:引入现代化的 UI 组件库,快速构建精美的卡片、列表。
- 数据可视化:使用专业图表库将复杂数据渲染为统计图表。
- 矢量图标:引入海量 SVG 图标库,支持任意缩放不失真。
Last updated on