karin-pluginkkk
其他

现代化模板渲染

基于 React 与 TailwindCSS 的模板开发方案

本插件采用 React + TailwindCSS 技术栈进行图片渲染,将现代前端的工程化能力引入模板开发。

相比传统模板引擎在维护上的痛点——逻辑与视图耦合、全局 CSS 冲突、缺乏类型检查——本方案通过组件化构建原子化样式类型安全三条路径彻底解决了这些问题。

传统方案的维护陷阱

基于 art-template 的模板在项目迭代中往往会陷入维护泥潭: - 逻辑黑洞:业务逻辑混杂在 HTML 模板中,难以阅读和剥离。 - 样式冲突:全局 CSS 类名随时间推移不断堆积,修改一处可能导致多处崩坏。 - 重构风险:缺乏类型检查,修改字段名就像在"排雷",只能祈祷运行时不出错。

架构概览

整个渲染链路分为四层:

  1. 数据层(@kkk/richtext:定义平台无关的富文本文档协议,作为 core 与 template 之间的数据边界。
  2. Core 解析层:将抖音、B 站等平台的原始 API 响应解析为标准的 RichTextDocument JSON。
  3. Template 渲染层:React 组件消费 JSON 数据,TailwindCSS 处理样式,最终通过 SSR 输出静态 HTML。
  4. 输出层: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/richtextparse 模块提供了一系列工厂函数,用于从平台原始数据中构建节点:

import { , , ,  } from '@kkk/richtext'

const  = (
  [
    ([
      ('这条视频太棒了', { : true }),
      ('doge', 'https://example.com/doge.png'),
      ('!推荐大家看看。')
    ])
  ],
  { : 'douyin' }
)

core 侧只需要导入类型节点创建方法,不依赖 React 运行时,输出的是可序列化的纯 JSON。

在 template 中使用

@kkk/richtextreact 模块提供了渲染器,将 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。
  • 图片安全:对 emojiimage 节点的 src 做协议白名单校验(仅允许 http://https://data:image/*;base64)。
  • URL 自动识别text 节点中的链接会被自动包裹为可点击的超链接。
  • 行内样式:支持 bolditalicstrikecolorfontSizelink 等行内样式。

为什么不用 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