快速开始

从初始化到在群里发出第一张 React 模板截图

快速开始

本页帮你在 Karin 插件项目中接入 @karinjs/template-react,用 React 组件写一个截图模板,并在群里发出第一张图片。每一步都明确了成功标志,如果没看到预期结果就停下来检查。

前置条件:Node.js 22+、pnpm、一个基于 Karin 官方 TypeScript 插件模板的项目。

第 1 步:初始化项目

在你的 Karin 插件项目根目录执行:

npx @karinjs/template-react init

命令会启动交互式向导,询问四个配置项:

  1. 样式方案:选择默认选项即可继承 HeroUI 主题,bg-surfacetext-accent 等语义类开箱可用
  2. 是否生成示例模板:建议选"是",会复制官方 7 个示例(问候卡片、成员榜单、宽屏仪表盘等)到 ktr/template/,照着改就能上手
  3. 是否生成 src/utils/render.ts:建议选"是",这是模板渲染接入 Karin 截图的胶水层(第 4 步会用到)
  4. 开发面板端口:保持默认或自定义端口

向导完成后,按提示安装依赖:

pnpm install

✓ 成功标志

  • 项目根目录出现 karin.template.ts 配置文件
  • 出现 ktr/template/ 目录(包含示例模板,如果选择了生成)
  • 出现 src/utils/render.ts 文件(如果选择了生成)
  • tsconfig.json 中添加了 "jsx": "react-jsx" 配置
  • package.json 中添加了依赖和 template 脚本
如果你还没有项目,用 npx @karinjs/template-react create <项目名> 从零创建,之后走同样的问答流程。

依赖管理注意事项:所有在模板中用到的依赖都需要放在 packages.jsondevDependencies 中,构建时会整体打包(包括 React 和本包运行时),生产环境即可不用安装有关前端的相关依赖;只有 node-karin 保持排除在外。不要手动修改依赖配置——组件和渲染器必须使用同一份 React 实例,否则 hooks 会直接报错。

遇到问题? 如果初始化失败或出现错误,查看 故障排查 页面的常见问题解决方案。

第 2 步:理解模板结构

让我们看一个完整的模板示例,理解它是如何工作的。以 ktr/template/hello/card/index.tsx 为例:

ktr/template/hello/card/index.tsx
import { ,  } from '@heroui/react'
import { , ,  } from 'lucide-react'

import { , type  } from '@karinjs/template-react'

/** Hello 卡片模板的数据结构,适合固定宽度的信息摘要截图。 */
type  = {
  /** 主标题。 */
  : string
  /** 可选副标题。 */
  ?: string
  /** 展示在卡片内容区的键值对。 */
  : <{ : string; : string }>
}

const  = ({ data }: <>) => (
data: HelloCardData

当前模板使用的数据,类型由 defineTemplate 的泛型决定。

< ="w-155 gap-0 overflow-hidden rounded-3xl border border-border bg-background p-0 text-foreground"> <. ="flex-row items-start justify-between gap-4 border-b border-border px-8 py-7"> < ="min-w-0"> < ="mb-3 flex items-center gap-2 text-sm font-medium text-accent"> < ={16} /> Karin 模板示例 </> <. ="text-3xl font-bold tracking-normal">{.}</.> {. && <. ="mt-2 text-sm text-accent">{.}</.>} </> < ="bg-accent text-accent-foreground" ="md" ="tertiary"> < ={14} /> HeroUI </> </.> <. ="grid gap-3 px-8 py-7"> {..((, ) => ( < ={.} ="flex items-center justify-between gap-4 rounded-lg border border-border bg-surface px-4 py-3"> < ="flex min-w-0 items-center gap-3"> < ="grid size-8 shrink-0 place-items-center rounded-md bg-accent-soft text-accent"> { === 0 ? < ={16} /> : < ={16} />} </> < ="truncate text-sm text-muted">{.}</> </> < ="text-base">{.}</> </> ))} </.> </> ) export default ({ : '问候卡片', : 'HeroUI + lucide-react 截图卡片', : })

关键要点

  1. 路径即路由ktr/template/<板块>/<模板>/index.tsx 自动注册为路由 hello/card;直接写 .tsx 文件(不在目录结构中)不会被注册
  2. 使用语义化样式类:使用 bg-backgroundtext-muted 等语义类,主题切换和明暗模式自动适配;避免写死颜色如 bg-white
  3. 类型安全TemplateProps<HelloCardData> 确保组件接收的数据结构类型安全
  4. JSON 示例数据:放在 data/ 子目录(如 ktr/template/hello/card/data/default.json),放在模板根目录不会被识别

示例数据结构 ktr/template/hello/card/data/default.json

ktr/template/hello/card/data/default.json
{
  "title": "Karin Template React",
  "subtitle": "React + Tailwind CSS + TypeScript",
  "items": [
    {
      "label": "渲染方式",
      "value": "SSR HTML"
    },
    {
      "label": "预览",
      "value": "Iframe 沙盒"
    },
    {
      "label": "类型",
      "value": "注册表驱动"
    }
  ]
}

完整的目录约定和更多示例见 目录约定

提示:语义类如 bg-surfacetext-accentborder-border 等会根据用户配置的主题自动适配颜色,无需手动处理明暗模式。

第 3 步:启动开发面板

在项目根目录执行:

pnpm template

这个命令等价于 ktr sync && ktr dev

  • sync:扫描 ktr/template/ 目录生成 .ktr/ 注册表和类型定义
  • dev:启动开发面板服务器并打开浏览器

✓ 成功标志

  • 浏览器自动打开开发面板(通常是 http://localhost:5180/__ktr/panel/
  • 左侧列表显示所有可用模板(如"问候卡片")
  • 点击模板名称后右侧显示实时预览
  • 修改组件代码或 data/*.json 保存后,预览会在 1-2 秒内自动刷新
端口被占用? 如果默认端口被占用,ktr dev 会自动切换到其他可用端口,并在终端显示占用进程的详细信息。

遇到问题? 如果面板无法启动或预览不显示,检查: - 是否执行了 pnpm install - .ktr/ 目录是否存在(如果不存在,手动执行 npx ktr sync) - 浏览器控制台是否有报错 更多帮助见 故障排查

第 4 步:接入 Karin,发出第一张图

现在让我们把模板接入 Karin,在群里发出第一张图片。

4.1 渲染工具函数

如果你在第 1 步选择生成了 src/utils/render.ts,文件已经存在。让我们看看它的完整实现:

src/utils/render.ts
import  from 'node:path'

import { , , segment, type ImageElement } from 'node-karin'
import { , type , type  } from '@karinjs/template-react'

// ktr 侧按约定装配(包根定位、配置解析、注册表加载、CSS 定位、捕获目录);
// outputDir 是 karin 领域的位置,由插件显式指定。
const  = (import.meta., {
  : { : .(, 'karin-plugin-example') }
})

/** 注册表类型:.ktr/registry-types.d.ts 模块增强生效后为逐路由精确类型。 */
type  = 

/**
 * 渲染模板并交给 Karin Puppeteer 截图。
 * @param templatePath 模板路由,如 hello/card。
 * @param data 模板数据,类型由模板组件推导。
 * @param options 透传给 render.render 的额外截图参数。
 * @returns 可直接 reply 的图片消息元素。
 */
export const  = async < extends keyof  & string>(
  : ,
  : <[]>,
  ?: <string, unknown>
): <ImageElement[]> => {
  const { , ,  } = await (, )
  if (!) {
    throw new (`模板渲染失败 ${}:${}`)
  }

  const  = await .({
    : `karin-plugin-example/${}`,
    : ,
    : '#container',
    : 'png',
    : true,
    ...
  })

  const  = .() ?  : []
  return .(() => segment.(`base64://${}`))
}

关键点

  • createTemplateRenderer 创建渲染器实例,负责将 React 组件渲染为 HTML
  • renderImage 函数的 data 参数类型会根据 templatePath 自动推导,确保类型安全
  • 渲染完成后调用 Karin 的 render.render() 进行截图
  • 返回的 ImageElement[] 可以直接传给 event.reply()

4.2 创建测试指令

创建一个新的指令文件来测试模板渲染:

src/apps/template.ts
import {  } from 'node-karin'

import {  } from '../utils/render'

/** 触发指令:#测试模板 */
export const  = .('^(#)?测试模板$', async () => {
  // renderImage 的第二个参数 data 类型会根据 'hello/card' 自动推导
  const  = await renderImage('hello/card', {
renderImage<"hello/card">(templatePath: "hello/card", data: DataOf<LoadedRegistry["hello/card"]>, options?: Record<string, unknown>): Promise<ImageElement[]>

renderImage 完整类型签名

: '来自机器人的卡片', : [ { : '状态', : '渲染成功 ✓' }, { : '时间', : new ().('zh-CN') } ] }) await .() return true })

类型安全演示

// ✓ 正确:类型匹配
await ('hello/card', {
  : '标题',
  : [{ : '键', : '值' }]
})

// ✗ 错误:缺少必需字段 title
await ('hello/card', {
Argument of type '{ items: { label: string; value: string; }[]; }' is not assignable to parameter of type '{ title: string; items: { label: string; value: string; }[]; }'. Property 'title' is missing in type '{ items: { label: string; value: string; }[]; }' but required in type '{ title: string; items: { label: string; value: string; }[]; }'.
: [{ : '键', : '值' }] }) // ✗ 错误:title 类型错误(应该是 string) await ('hello/card', { title: 123,
Type 'number' is not assignable to type 'string'.
: [{ : '键', : '值' }] })

4.3 测试渲染

  1. 启动 Karin 开发服务器:
pnpm dev
  1. 在群聊或私聊中发送指令:
#测试模板

✓ 成功标志

  • 机器人回复一张卡片图片,显示"来自机器人的卡片"标题和状态信息
  • 开发面板的数据列表中自动出现 captured.json(记录了本次真实渲染的数据)
  • 可以在开发面板中选中 captured.json 查看捕获的数据结构

数据捕获功能:每次通过 renderImage 渲染模板时,传入的数据会自动保存到 ktr/template/<模板路径>/data/captured.json,方便你在开发面板中重现真实场景。

遇到问题? 常见问题排查: - 机器人无响应:检查指令格式是否正确,确认机器人已启动 - 渲染失败:查看终端错误信息,确认 pnpm template 已执行过(生成注册表) - 类型报错:执行 npx ktr sync 重新生成类型定义 更多帮助见 接入 Karin故障排查

第 5 步:生产构建与发布

开发完成后,需要把插件打包成纯 JS 产物以便发布。产物目录由你的打包配置决定(karin 插件惯例是 lib/,本文示例都用它)。

如果宿主应用只是普通的 JavaScript Node 程序,不希望维护 Vite 或 tsdown 入口,可以改用 ktr build,直接生成可被 Node 导入的独立运行包。

执行构建命令:

pnpm build

或者如果你使用的是 Vite:

vite build

构建原理

构建成功的前提是 vite.config.ts 中配置了 ktrBuildPlugin()(来自 @karinjs/template-react/plugin)。这个插件会:

  1. 打包前自动执行 ktr sync 刷新注册表和类型定义
  2. 打包后将所有 CSS 编译到 <产物目录>/style.css(跟随打包器 outDir)
  3. 由打包器生成 template-registry.js,供生产渲染器发现模板组件

✓ 成功标志

  • 产物目录(如 lib/)包含以下文件:
    • apps/*.js(你的指令文件)
    • utils/render.js(渲染工具)
    • template-registry.js(模板注册表)
    • style.css(编译后的样式)
  • 发布后用户只需安装你的插件包,无需 ktr/template/ 源码目录

mock-registry.js 不是普通生产渲染的必需产物。只有插件代码明确调用 loadMockRegistry()、希望在生产环境读取示例数据时才把它加入打包入口。

配置示例 vite.config.ts

vite.config.ts
import {  } from 'vite'
import {  } from '@karinjs/template-react/plugin'

export default ({
  : [()]
})

完整的构建配置和发布流程见 构建发布

打包优化:所有依赖(包括 React)都会打包进产物,用户安装后无需额外安装依赖。生产环境只需要 node-karin 作为外部依赖。

构建失败? 常见问题: - 找不到 ktrBuildPlugin:确认已安装 @karinjs/template-react 并在 vite.config.ts 中正确导入 - 类型错误:执行 npx ktr sync 重新生成类型定义 - CSS 缺失:确认构建配置挂了 ktrBuildPlugin() 且没有传 { css: false } 更多帮助见 构建发布故障排查

下一步

恭喜!你已经完成了从初始化到发出第一张图的完整流程。现在你可以:

开发建议: - 在开发面板中调试模板样式和数据结构,确认无误后再接入 Karin 指令 - 使用 captured.json 收集真实场景的数据,作为测试用例 - 遵循语义化样式类约定,确保主题切换正常工作

On this page