API 参考

插件 API

RenderPlugin 接口、beforeRender / afterRender 钩子、PluginContext 与自定义插件示例

渲染插件是 SSR 阶段的可插拔扩展,允许在渲染前后插入自定义逻辑,如日志记录、HTML 加工、资源注入等。

RenderPlugin

interface RenderPlugin {
  /** 插件名称,用于日志和错误提示。 */
  : string
  /** 执行顺序:pre(前置)→ normal(普通)→ post(后置),默认 normal。 */
  ?: 'pre' | 'normal' | 'post'
  /** 过滤器:返回 false 时跳过当前模板,不执行钩子。 */
  ?: (: string) => boolean
  /** 渲染前钩子:在 React SSR 之前执行。 */
  ?: (: PluginContext) => void | <void>
  /** 渲染后钩子:加工 HtmlWrapper 包装前的模板 HTML 片段。 */
  ?: (: PluginContext & { : string }) => string | void | <string | void>
}

enforce 字段

控制插件执行顺序:

  • 'pre':最先执行,通常用于前置检查、环境准备。
  • 'normal':默认顺序,大多数插件用这个。
  • 'post':最后执行,通常用于清理、日志输出。

同级插件按注册顺序执行。

apply 字段

过滤器函数,接收模板路由(如 'hello/card'),返回 boolean

  • true 或不提供:执行钩子。
  • false:跳过该模板,不执行 beforeRenderafterRender
const : RenderPlugin = {
  : 'user-only',
  : () => .('user/'), // 只对 user/ 开头的路由生效
  : () => {
    .(`[user-only] 渲染 ${.}`)
  }
}

beforeRender 钩子

在 React SSR 之前执行,可以:

  • 检查数据有效性(除了 validate 之外的业务逻辑校验)
  • 修改输出目录或上下文
  • 记录日志、发送监控
const : RenderPlugin = {
  : 'log-plugin',
  : async () => {
    .(`[渲染前] 路由: ${.}`)
    .(`[渲染前] 数据:`, .)
    .(`[渲染前] 上下文:`, .)
  }
}

不要修改 ctx.data:引用类型修改会影响后续插件和渲染逻辑,容易产生副作用。若需加工数据,应在调用渲染函数之前处理。

afterRender 钩子

在 React SSR 完成之后执行,ctx.htmlHtmlWrapper 包装前的模板 HTML 片段(片段随后才被包进完整 HTML 文档),可以:

  • 加工片段(包裹/追加节点、替换占位符、压缩等)
  • 记录渲染结果
  • 上传 HTML 到远程存储
const : RenderPlugin = {
  : 'watermark',
  : async () => {
    // 给模板片段追加一个角标节点
    return `${.}<div class="watermark">generated by ktr</div>`
  }
}

片段里没有 <head> / <body>,不要对 ctx.htmlreplace('</head>', ...) 这类整文档操作。需要往 <head> 注入 <meta><link> 等内容时,使用 html.headExtra 配置(见 配置与 CLI)。

返回值规则

  • 返回 string:替换原 HTML。
  • 返回 voidundefined:保持原 HTML 不变。
  • 返回 Promise:支持异步操作。

PluginContext

interface PluginContext {
  /** 模板路由(如 'hello/card')。 */
  : string
  /** 当前渲染的数据。 */
  : unknown
  /** 运行时上下文(scale、theme)。 */
  : RenderContext
  /** HTML 输出目录。 */
  : string
}

beforeRenderafterRender 钩子接收的上下文对象,afterRender 额外有 html: string 字段。

字段说明

Prop

Type

afterRender 的上下文额外带有 html: string 字段(生成的 HTML 字符串,可修改后返回)。

使用示例

import type { RenderPlugin } from '@karinjs/template-react'
import  from 'node:fs'
import  from 'node:path'

const : RenderPlugin = {
  : 'save-metadata',
  : async () => {
    const  = .(., `${..('/', '_')}_meta.json`)
    await ..(, .({ : ., : .., : .() }, null, 2))
  }
}

注册插件

通过 createRendererplugins 选项注册:

const  = (, {
  ,
  ,
  : [, ]
})

createTemplateRenderer 也可以通过 renderer.plugins 传入:

const  = (import.meta., {
  : {
    : []
  }
})

自定义插件示例

以下是几个常见的自定义插件写法,可以直接复制到插件里使用。

捕获数据插件

框架渲染器已内置数据捕获(captureDir 选项),这里演示如何用插件实现等效能力:

import type { RenderPlugin } from '@karinjs/template-react'
import  from 'node:fs'
import  from 'node:path'

function (: string): RenderPlugin {
  return {
    : 'capture-data',
    : 'post', // 最后执行,确保渲染成功
    : async () => {
      const [, ] = ..('/')
      const  = .(, , , 'data')
      await ..(, { : true })
      const  = .(, 'captured.json')
      await ..(, .(., null, 2))
      .(`[capture] 已捕获数据到 ${}`)
    }
  }
}

HTML 压缩插件

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

function (): RenderPlugin {
  return {
    : 'minify-html',
    : 'post',
    : () => {
      // 简单压缩:移除多余空白符和换行
      return .
        .(/\s+/g, ' ') // 多个空白符合并为一个
        .(/>\s+</g, '><') // 标签间空白移除
        .()
    }
  }
}

生产环境推荐使用专业的 HTML 压缩库(如 html-minifier-terser),上面的简单正则可能破坏 <pre> / <code> 等标签内容。

性能监控插件

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

function (): RenderPlugin {
  const  = new <string, number>()

  return {
    : 'perf-monitor',
    : () => {
      .(., .())
    },
    : () => {
      const  = .(.)
      if () {
        const  = .() - 
        .(`[性能] ${.} 渲染耗时: ${}ms`)
        .(.)
      }
    }
  }
}

条件渲染插件

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

function (): RenderPlugin {
  return {
    : 'conditional-render',
    : () => {
      // 仅在开发环境渲染 dev/ 开头的模板
      if (.('dev/')) {
        return .. === 'development'
      }
      return true
    },
    : () => {
      .(`[条件渲染] 允许渲染 ${.}`)
    }
  }
}

异常处理

插件内抛出的异常会中断渲染流程,返回 { success: false, error: '...' }。渲染器不对错误信息做包装,error 就是异常的原始 message

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

const : RenderPlugin = {
  : 'unsafe-plugin',
  : () => {
    if (!.) {
      throw new ('数据为空') // 渲染失败,error 为 '数据为空'
    }
  }
}

若插件异常不应中断渲染,需在插件内 try-catch

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

const : RenderPlugin = {
  : 'safe-plugin',
  : async () => {
    try {
      // 可能失败的操作
      await ('https://api.example.com/log', {
        : 'POST',
        : .({ : . })
      })
    } catch () {
      .('[safe-plugin] 上报失败,但不影响渲染:', )
    }
  }
}

插件最佳实践

1. 命名清晰

插件 name 用于日志和错误提示,应清晰表达功能:

// ✅ 好的命名
{
  name: 'inject-analytics'
}
{
  name: 'compress-html'
}
{
  name: 'upload-to-oss'
}

// ❌ 不好的命名
{
  name: 'plugin1'
}
{
  name: 'my-plugin'
}

2. 避免副作用

不要修改 ctx.datactx.ctx

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

// ❌ 错误:修改上下文
const : RenderPlugin = {
  : 'bad-plugin',
  : () => {
    ;(. as any).extra = 'injected' // 影响后续插件和渲染
  }
}

// ✅ 正确:只读取,不修改
const : RenderPlugin = {
  : 'good-plugin',
  : () => {
    const  = 'title' in (. as any)
    .(`数据包含 title: ${}`)
  }
}

3. 合理使用 enforce

  • 前置检查、环境准备enforce: 'pre'
  • 核心逻辑enforce: 'normal'(默认)
  • 清理、日志、上传enforce: 'post'
import type { RenderPlugin } from '@karinjs/template-react'

const : RenderPlugin = {
  : 'check-env',
  : 'pre', // 最先执行
  : () => {
    if (!..) {
      throw new ('缺少 API_KEY 环境变量')
    }
  }
}

const : RenderPlugin = {
  : 'upload-html',
  : 'post', // 最后执行
  : async () => {
    // 上传 HTML 到 OSS
  }
}

4. 过滤不需要的路由

apply 减少不必要的执行:

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

const : RenderPlugin = {
  : 'expensive-operation',
  : () => .('report/'), // 只对 report/ 生效
  : async () => {
    // 耗时操作
  }
}

5. 异步操作用 async

钩子支持 Promise,耗时操作应用 async

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

const : RenderPlugin = {
  : 'async-plugin',
  : async () => {
    // 等待异步操作完成
    await new (() => (, 100))
    return `${.}<div data-loaded="true"></div>`
  }
}

6. 保持插件轻量

插件在每次渲染时都会执行,避免:

  • 重复读取文件(在插件初始化时缓存)
  • 同步阻塞操作(用 async + 异步 API)
  • 复杂正则替换(HTML 加工尽量简单)
import type { RenderPlugin } from '@karinjs/template-react'
import  from 'node:fs'

// ❌ 错误:每次渲染都读文件
const : RenderPlugin = {
  : 'slow-plugin',
  : () => {
    const  = .('/path/to/template.html', 'utf-8') // 慢
    return ..('{{content}}', )
  }
}

// ✅ 正确:插件初始化时缓存
function (): RenderPlugin {
  const  = .('/path/to/template.html', 'utf-8') // 只读一次

  return {
    : 'fast-plugin',
    : () => ..('{{content}}', )
  }
}

调试插件

开发插件时可以打印 PluginContext 查看字段内容:

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

const : RenderPlugin = {
  : 'debug-plugin',
  : () => {
    .('[DEBUG] PluginContext:', .(, null, 2))
  },
  : () => {
    .('[DEBUG] HTML length:', ..)
    .('[DEBUG] HTML preview:', ..(0, 200))
  }
}

渲染时添加该插件,查看日志输出即可了解插件接收到的完整上下文。

On this page