故障排查

类型问题详解

.ktr/registry-types.d.ts 不生效、renderImage 类型丢失、TemplateProps 推导失败、tsx 配置问题的完整排查

类型问题详解

TypeScript 类型系统是 ktr 的核心优势——路由补全、data 字段检查全靠模块增强声明。本页讲类型失效的所有可能原因与解决方案。

.ktr/registry-types.d.ts 不生效

症状

renderImage 的第一个参数(路由)没有自动补全,第二个参数(data)类型是 any

src/apps/template.ts
// ❌ 'hello/card' 没有提示,data 类型丢失
await renderImage('hello/card', {/* 任意字段都不报错 */})

或者 VSCode 报红:

Cannot find module '@karinjs/template-react/registry-types' or its corresponding type declarations.

原因

.ktr/registry-types.d.ts 未生成、生成后 TypeScript 编译器未加载、或模块增强声明被 exclude 排除。

解决方案

1. 执行 sync 生成类型文件

pnpm ktr sync

检查 .ktr/ 目录是否有三个文件:

ls -la .ktr/
# 应输出:
# template-registry.ts
# mock-registry.ts
# registry-types.d.ts  ← 类型增强声明

registry-types.d.ts 的结构示例:

.ktr/registry-types.d.ts
// .ktr/registry-types.d.ts(框架自动生成)
export {}

declare module '@karinjs/template-react/registry-types' {
  interface ProjectRegistry {
    'hello/card': typeof import('../ktr/template/hello/card/index.tsx').
    'hello/list': typeof import('../ktr/template/hello/list/index.tsx').
  }
}

缺失时执行 pnpm ktr sync;存在但内容为空时检查 ktr/template/ 是否有模板文件。

2. 重启 TypeScript 服务器

VSCode 按 Ctrl+Shift+P(macOS Cmd+Shift+P),运行 TypeScript: Restart TS Server

或者重启 IDE。

3. 检查 tsconfig.json

确保 .ktr/ 没有被 exclude 排除:

tsconfig.json
// @errors: 18046
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "moduleResolution": "bundler",
    "types": []
  },
  "include": ["src", "ktr", ".ktr"],
  "exclude": ["node_modules", "lib"]
}

不要.ktr/ 从 TS 工程里排除——模块增强依赖 registry-types.d.ts 被 TypeScript 工程包含。被 exclude 排掉或未落入 include 范围时增强不生效,LoadedRegistry 会退化为宽松类型。

正确做法是让 include 覆盖 .ktr/

tsconfig.json(推荐)
{
  "compilerOptions": {
    "jsx": "react-jsx"
  },
  "include": ["src", "ktr", ".ktr"],
  "exclude": ["node_modules", "lib"]
}

模块增强声明通过 .d.ts 文件自动生效,下游不需要 import .ktr/ 里的任何文件。

4. 检查 types 字段

tsconfig.jsoncompilerOptions.types 如果是空数组,会禁用所有自动类型发现:

tsconfig.json(错误)
{
  "compilerOptions": {
    "types": [] // ← 禁用自动类型发现
  }
}

删掉该字段,或显式加上包名(不推荐):

tsconfig.json
{
  "compilerOptions": {
    "types": ["node", "@karinjs/template-react"]
  }
}

最佳实践是删掉 types 字段,让 TypeScript 自动发现。


renderImage 类型丢失

症状

renderImage 调用时路由和 data 都没有类型提示,IDE 不报错但运行时渲染失败:

src/apps/template.ts
import {  } from '../utils/render'

// ❌ 路由字符串任意写,data 结构错误也不报红
await ('hello/card', { : 123 })

原因

renderImage 函数签名未使用 LoadedRegistry 类型,或 DataOf 工具类型未正确提取数据类型。

解决方案

检查 src/utils/render.ts 的类型声明:

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

/**
 * 渲染模板并交给 Karin Puppeteer 截图,返回可直接 reply 的图片消息元素。
 */
export const  = async < extends keyof  & string>(
  : ,
  : <[]>,
  ?: <string, unknown>
): <ImageElement[]> => {
  const { , ,  } = await (, )
  if (!) {
    throw new (`模板渲染失败 ${}:${}`)
  }

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

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

关键点:

  1. type Registry = LoadedRegistry —— 拿到模块增强后的注册表类型
  2. <K extends keyof Registry & string> —— 路由必须是注册表的键
  3. data: DataOf<Registry[K]> —— 从对应模板定义里提取 data 类型

常见错误

src/utils/render.ts(错误)
// ❌ 泛型约束丢失:路由是任意 string,data 是 any
export const  = async (: string, : any) => {
  /* ... */
}

// ❌ 这样也不算数:keyof any 等于没有约束
// export const renderImage = async <K extends keyof any>(templatePath: K, data: any) => { ... }

TemplateProps 推导失败

症状

模板组件的 props 没有类型提示,dataany

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

interface CardData {
  : string
  : <{ : string; : string }>
}

// ❌ data 类型丢失:泛型写成了 any
const  = ({  }: <any>) => (
  <>{data.title}</>
data: any

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

)

原因

TemplateProps 的泛型参数写成了 any(或 props 完全没标注),data 退化为 any

解决方案

显式传入数据类型:

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

interface CardData {
  : string
  : <{ : string; : string }>
}

// ✅ 显式传入泛型参数
const  = ({  }: <CardData>) => (
  <>{.title}</>
CardData.title: string
) export default ({ : })

或者用组件泛型(React 18.3+):

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

interface CardData {
  : string
  : <{ : string; : string }>
}

// ✅ 组件泛型自动推导
const  = < extends CardData>({  }: <>) => <>{.}</>

export default ({ :  })

tsx 配置问题

症状

.tsx 文件报错:

Cannot use JSX unless the '--jsx' flag is provided.

或者运行时报错:

ReferenceError: React is not defined

原因

tsconfig.jsonjsx 字段未设置或设置为 preserve,导致 TypeScript 不转换 JSX 语法,或转换后需要手动 import React。

解决方案

tsconfig.json 里设置 jsx: "react-jsx"

tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  }
}

react-jsx 模式下不需要手动 import React,组件直接写:

ktr/template/hello/card/index.tsx
// ✅ 不需要 import React
import { defineTemplate, type TemplateProps } from '@karinjs/template-react'

const Card = ({ data }: TemplateProps<any>) => <div>Hello</div>

export default defineTemplate({ component: Card })

常见错误配置

tsconfig.json(错误)
{
  "compilerOptions": {
    "jsx": "preserve" // ← 不转换 JSX,留给 Babel,但 ktr 没有 Babel
  }
}
tsconfig.json(错误)
{
  "compilerOptions": {
    "jsx": "react" // ← 旧模式,需要手动 import React
  }
}

ktr init 会自动给 tsconfig.json 补上 jsx: "react-jsx",手动创建项目时容易遗漏。


动态路由无类型提示

症状

路由来自用户输入(如指令参数),renderImage 无法推导类型:

src/apps/template.ts
// ❌ route 类型是 string,不是字面量类型,无法匹配模板路由
const  = ..('#渲染 ', '')
await (route, {/* 任意字段 */})
Argument of type 'string' is not assignable to parameter of type '"hello/card"'.

原因

泛型约束 K extends keyof Registry & string 要求字面量类型,变量 route 是宽泛的 string,类型推导失败。

解决方案

方案 1:类型断言(不安全)

src/apps/template.ts
const  = ..('#渲染 ', '') as keyof 
await (, {/* data 类型仍是 any */})

类型断言只骗过编译器,data 类型仍无法推导。

方案 2:运行时枚举(推荐)

白名单模式,用 switch 或 Map 把路由映射到类型安全的调用:

src/apps/template.ts
const  = ..('#渲染 ', '')

switch () {
  case 'hello/card':
    await .(
      await ('hello/card', {
        : '卡片标题',
        : [{ : '状态', : '正常' }]
      })
    )
    break
  case 'hello/list':
    await .(
      await ('hello/list', {
        : '列表标题',
        : [{ : '用户1', : '角色1', : 100 }]
      })
    )
    break
  default:
    await .('未知模板路由')
}

每个 case 里的 renderImage 调用都有完整类型检查。

方案 3:validate 兜底(半安全)

放宽类型约束,靠模板的 validate 函数做运行时兜底:

src/apps/template.ts
const  = ..('#渲染 ', '')

// 类型不安全,data 是 unknown
const  = await (, {/* 任意数据 */})

if (!.) {
  throw new (`渲染失败:${.}`)
}

模板定义时必须写 validate

ktr/template/hello/card/index.tsx
export default ({
  : ,
  : ():  is CardData =>
    typeof  === 'object' &&  !== null && typeof ( as CardData). === 'string' && .(( as CardData).)
})

validate 返回 false 时 SSR 直接报错 Template data validation failed,不会生成 HTML。


monorepo 里类型失效

症状

在 monorepo(pnpm workspace、Turborepo、Nx)里,类型增强声明完全不生效,LoadedRegistry 退化为 AnyRegistryRecord<string, TemplateDef<any>>),失去逐路由精确类型。

原因

TypeScript 的模块解析策略在 monorepo 里可能找不到 .ktr/registry-types.d.ts,或 workspace 协议导致包路径解析错误。

解决方案

1. 确保 tsconfig.json 在插件包根目录

packages/my-plugin/
├── tsconfig.json  ← 必须在这里
├── ktr/
│   └── template/
└── .ktr/
    └── registry-types.d.ts

2. 检查 TypeScript 项目引用

如果用了 TypeScript 项目引用(references),确保插件包的 tsconfig.json 没有被父配置覆盖:

packages/my-plugin/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "composite": false // ← 禁用复合项目模式
  },
  "include": ["src", "ktr"]
}

3. 重新生成类型声明

cd packages/my-plugin
pnpm ktr sync

检查 .ktr/registry-types.d.tsdeclare module 目标和接口名是否正确:

.ktr/registry-types.d.ts
declare module '@karinjs/template-react/registry-types' {
  interface ProjectRegistry {
    /* ... */
  }
}

该文件由 ktr 生成,模块路径固定为 '@karinjs/template-react/registry-types',增强的接口是 ProjectRegistry。如果内容被手动改过,直接删除后重新执行 pnpm ktr sync 生成。


VSCode 重启后类型又失效

症状

每次打开项目都要手动 Restart TS Server 才能恢复类型提示。

原因

VSCode 的 TypeScript 插件缓存了旧的类型状态,.ktr/ 更新后没有自动失效。

解决方案

1. 清理 VSCode 缓存

关闭 VSCode,删除项目的 .vscode 缓存:

rm -rf .vscode

2. 启用 TypeScript 的 watchOptions

tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx"
  },
  "watchOptions": {
    "watchFile": "useFsEvents",
    "watchDirectory": "useFsEvents"
  }
}

3. 在 package.json scripts 里加钩子

每次启动开发服务器时先 sync:

package.json
{
  "scripts": {
    "dev": "ktr sync && karin dev",
    "template": "ktr sync && ktr dev"
  }
}

相关资源

On this page