运行时 API
RenderContext、capture 机制、运行时生命周期与 SSR 执行流程的完整说明
运行时指 SSR 渲染阶段(Node 侧)的行为和上下文,包括渲染器如何执行、如何捕获真实数据、以及组件能拿到哪些上下文信息。
RenderContext
模板组件通过 props 接收的运行时上下文,由渲染器注入:
interface RenderContext {
/** 当前渲染比例,由外壳统一对 #container 施加 zoom,模板无需自行缩放;默认 1。 */
: number
/** 调用方显式提供的主题变量;框架不发明默认值。 */
?: <ThemeContext>
/** 调用方自定义的扩展字段,原样透传给模板和插件。 */
[: string]: unknown
}scale
渲染比例因子,由外壳统一对 #container 施加 zoom(布局盒随缩放变化,截图边界跟着放大,产物分辨率即 scale 倍),开发面板沙盒行为一致。通常保持为 1,高清截图需求时可以传 2 或更高:
const = await ('hello/card', { : '高清截图' }, { : 2 })模板不要自行缩放根元素(会叠加成 scale²),也不要把宽高字体乘以 ctx.scale;组件内仍可读取它做比例感知逻辑,比如 2x 时替换高清素材。
theme
调用方显式提供的主题变量,类型为 Partial<ThemeContext>。框架不发明默认主题色,未传时为 undefined,此时组件库自身的默认主题生效。
const = ({ , }: <CardData>) => {
const = .?. === 'dark'
const = .?. ?? '#3B82F6' // 回退到自己的默认值
return (
< ={ ? 'dark' : 'light'}>
< ={{ : }}>{.}</>
<>{.}</>
</>
)
}RenderContextInput
渲染函数的第三个参数类型,即 Partial<RenderContext>,所有字段可选,自定义扩展字段同样原样透传:
interface RenderContextInput {
?: number
?: <ThemeContext>
[: string]: unknown
}调用时显式传入的字段会被注入到组件 props,缺省时框架保持为 undefined。
ThemeContext
主题变量的完整结构,调用方可以只传部分字段(由源码 JSDoc 自动生成):
Prop
Type
实际使用示例
// 只传明暗模式,其余颜色由组件库主题决定
await (
'hello/card',
{ : '夜间模式' },
{
: { : 'dark' }
}
)
// 完整自定义主题
await (
'hello/card',
{ : '品牌定制' },
{
: {
: 'light',
: '#FF6B6B',
: '#FFFFFF',
: '#F8F9FA',
: '#212529',
: '#DEE2E6'
}
}
)
// 传入自定义 CSS 变量
await (
'hello/card',
{ : '圆角定制' },
{
: {
: 'light',
: { '--radius': '16px', '--spacing': '2rem' }
}
}
)主题变量会被注入到 SSR HTML 的 <style> 标签中,作为 CSS 变量供模板使用。
Capture 机制
渲染器会把真实调用时传入的数据捕获到 ktr/template/<板块>/<模板>/data/captured.json,供开发面板展示。
触发条件
满足任一条件即捕获:显式配置了 captureDir;或未配置时处于开发环境(NODE_ENV=development),默认捕获到 ktr/template/:
const = (, {
,
,
: .(.(), 'ktr/template') // 显式指定捕获目录
})createTemplateRenderer 默认开启捕获,目录为 ktr/template(约定位置),除非显式覆盖 renderer.captureDir: undefined 关闭。
捕获规则
每次渲染成功后,按以下规则写入:
- 路径:
<captureDir>/<板块>/<模板>/data/captured.json,目录不存在时自动创建。 - 内容:渲染器会把本次的运行时上下文一并捕获,写成
{ data, ctx }完整快照的格式化 JSON(面板回放时能还原主题、缩放等上下文);直接使用saveCapturedData且不传ctx时才是纯 data 形状。 - 覆盖策略:每次渲染都覆盖写入,保留最新一次的数据。
captured.json 示例
{
"data": {
"title": "运行时捕获示例",
"items": [
{ "name": "张三", "score": 95 },
{ "name": "李四", "score": 88 }
]
},
"ctx": {
"scale": 1
}
}开发面板会自动加载 captured.json,作为「真实数据」标签页展示。插件源码不需要关心捕获逻辑,只管正常渲染即可。
禁用捕获
生产环境或无需面板开发时可以关闭:
const = (import.meta., {
: { : } // 关闭捕获
})运行时生命周期
从调用渲染函数到生成 HTML 的完整流程:
1. 惰性初始化(首次渲染)
createTemplateRenderer 返回的函数首次调用时:
- 定位包根:从
callerUrl向上最多 10 层查找package.json。 - 判定运行形态:产物 chunk 顶部有构建插件注入的
__KTR_BUNDLED__标记即为生产 bundle——跳过karin.template.ts(产物里没有,避免触发 tsx),直接用默认配置;否则读取karin.template.ts并合并默认值。 - 加载注册表:生产 bundle 直接用打包产物;开发态
.ktr源文件优先、缺失回退产物。 - 解析 CSS 路径:开发态用 dev 缓存
node_modules/.cache/ktr/style.css,生产态默认按 karin 惯例发现lib/style.css(可用renderer.cssPath覆盖)。 - 创建渲染器:调用
createRenderer,传入注册表和选项。
后续渲染直接复用已创建的渲染器。
2. 路由查找与数据校验
每次渲染时:
- 查找模板定义:按
templatePath从注册表取模板定义,未找到时返回{ success: false, error: 'Template is not registered: ...' }。 - 执行 validate:若模板定义了
validate函数,调用validate(data),返回false时返回{ success: false, error: 'Template data validation failed: ...' }。
3. 插件 beforeRender 钩子
按 enforce 顺序执行所有插件的 beforeRender 钩子:
enforce: 'pre'→enforce: 'normal'→enforce: 'post'- 同级插件按注册顺序执行
- 若插件的
apply返回false,跳过该插件
钩子接收 PluginContext,可以修改输出目录、检查数据等:
const : RenderPlugin = {
: 'log-plugin',
: async () => {
.(`[渲染前] 路由: ${.}, 数据:`, .)
}
}4. React SSR 渲染
- 构建完整 props:
{ data, ctx: { scale, theme } } - 渲染组件:
react-dom/server的renderToReadableStream流式 SSR,等allReady后一次性读出完整 HTML - 生成 HTML:包裹
<!doctype html>、<head>(内联 CSS + 主题变量)、<body>(#container+ 渲染内容)
5. 插件 afterRender 钩子
按相同顺序执行 afterRender 钩子,可以加工 HTML:
const : RenderPlugin = {
: 'inject-meta',
: async () => {
return ..('</head>', '<meta name="generator" content="ktr" /></head>')
}
}返回值非空时替换原 HTML,否则保持不变。
6. 写入文件
- HTML 输出:按
htmlFileName规则写入<outputDir>/<filename>.html。 - 数据捕获:若配置了
captureDir,写入<captureDir>/<path>/data/captured.json。
7. 返回结果
interface RenderResult {
: boolean
/** 成功时是生成的 HTML 文件路径;失败时为空字符串。 */
: string
?: string
}成功时返回 { success: true, htmlPath: '/path/to/output.html' },失败时返回 { success: false, error: '错误原因' }。
错误处理
渲染器不抛异常,所有错误都通过 RenderResult.error 返回:
| 错误情况 | error 内容 |
|---|---|
| 路由未注册 | Template is not registered: <路由> |
validate 未通过 | Template data validation failed: <路由> |
| React 渲染抛异常 | 原始异常消息 |
| 写入 HTML 失败 | 原始异常消息(如 ENOENT、EACCES) |
| 捕获数据写入失败(不阻塞) | 仅 console.warn 告警,不影响 success |
插件钩子(beforeRender / afterRender)内抛出的异常会中断渲染,error 为原始异常消息。
最佳实践
- 主题变量回退:组件内访问
ctx.theme时总是提供回退值,避免undefined导致样式失效。 - scale 无关设计:模板宽高直接写固定值(如
w-[640px]),不要乘以ctx.scale——截图工具会自动处理。 - validate 保守:只校验必需字段,避免过严校验导致动态数据渲染失败。
- 捕获开发态:生产部署时关闭捕获(
captureDir: undefined),减少 I/O 开销。 - 插件轻量化:
beforeRender/afterRender避免耗时操作,保持渲染性能。