进阶指南

调试技巧

开发面板调试工具、SSR 渲染调试、类型推导调试、网络请求调试与常见调试场景

掌握调试技巧可以快速定位问题、提升开发效率。本页讲解开发面板、SSR、类型推导和网络请求的调试方法。

开发面板调试

检查模式

点击元素定位到源码:

# 启动开发面板
pnpm template

# 访问 http://localhost:5180/__ktr/panel/
# 点击工具栏「定位」按钮,或按住 Shift+Alt 点击元素

检查模式由 code-inspector-plugin 实现:开启后点击模板中的元素会直接跳转到编辑器对应源码位置,不会在控制台输出额外日志。

React DevTools

在沙盒 iframe 中使用 React DevTools:

karin.template.ts
export default ({
  : {
    : {
      : '({ isDisabled: false })'
    }
  }
})

打开浏览器控制台 → Components 标签即可查看组件树。

沙盒通信日志

查看面板 ↔ 沙盒的 postMessage 通信:

packages/core/src/dev/sandbox.ts
// 开启调试日志
window.addEventListener('message', (event) => {
  if (event.data.type?.startsWith('ktr:')) {
    console.log('[沙盒 ← 面板]', event.data)
  }
})

window.parent.postMessage = new Proxy(window.parent.postMessage, {
  apply(target, thisArg, args) {
    console.log('[沙盒 → 面板]', args[0])
    return Reflect.apply(target, thisArg, args)
  }
})

数据流追踪

追踪数据从 JSON → 面板 → 沙盒 → 组件的流动:

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

interface UserCardData {
  : string
}

const  = ({ ,  }: <UserCardData>) => {
  // 1. 打印接收到的数据
  .('[组件] 接收数据:', )
  .('[组件] 运行时上下文:', )

  return <>{.}</>
}

export default ({
  : ,
  : ():  is UserCardData => {
    // 2. 打印校验过程
    .('[validate] 校验数据:', )
    return typeof  === 'object' &&  !== null && 'name' in  && typeof ( as UserCardData). === 'string'
  }
})

热更新调试

监听 HMR 事件:

ktr/template/user/card/index.tsx
if (import.meta.hot) {
  import.meta.hot.on('vite:beforeUpdate', (payload) => {
    console.log('[HMR] 即将更新:', payload)
  })

  import.meta.hot.on('vite:afterUpdate', (payload) => {
    console.log('[HMR] 更新完成:', payload)
  })

  import.meta.hot.accept((newModule) => {
    console.log('[HMR] 模块已接受:', newModule)
  })
}

SSR 渲染调试

渲染日志

在插件中记录渲染过程:

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

const : RenderPlugin = {
  : 'debug',
  : () => {
    .('[SSR] 开始渲染:', .)
    .('[SSR] 数据:', .(., null, 2))
    .('[SSR] 主题:', ..)
  },
  : () => {
    .('[SSR] HTML 大小:', ..)
    .('[SSR] 包含的类名:', (.))
  }
}

function (: string): string[] {
  const  = .(/class="([^"]*)"/g)
  const  = new <string>()
  for (const  of ) {
    [1].(/\s+/).(() =>  && .())
  }
  return .()
}

错误捕获

捕获 SSR 渲染错误:

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

const : RenderPlugin = {
  : 'error-catcher',
  : async () => {
    try {
      // 数据校验
      if (!.) throw new ('数据为空')
    } catch () {
      .(`[SSR 错误] ${.}:`, )
      // 记录到文件
      await (., )
      throw 
    }
  }
}

async function (: string, : unknown) {
  const  = await import('node:fs/promises')
  const  = new ().()
  const  = `[${}] ${}: ${( as Error).}\n`
  await .('ssr-errors.log', )
}

HTML 输出调试

保存中间 HTML 用于检查:

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

const : RenderPlugin = {
  : 'save-intermediate',
  : async () => {
    const  = .(., 'debug')
    await ..(, { : true })

    const  = `${..('/', '_')}_${.()}.html`
    await ..(.(, ), .)

    .('[调试] 保存 HTML:', )
  }
}

React 错误边界

在 SSR 中捕获组件错误:

ktr/template/components/ErrorBoundary.tsx
import React from 'react'

interface Props {
  : React.
}

interface State {
  : boolean
  ?: Error
}

export class  extends React.<Props, State> {
  constructor(: Props) {
    super()
    this. = { : false }
  }

  static (: Error): State {
    return { : true,  }
  }

  (: Error, : React.) {
    .('[ErrorBoundary] 捕获错误:', , )
  }

  () {
    if (this..) {
      return (
        < ="p-6 bg-error-soft text-error">
          < ="text-lg font-bold mb-2">渲染错误</>
          < ="text-sm">{this..?.}</>
        </>
      )
    }

    return this..
  }
}

使用错误边界:

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

interface Props {
  : React.
}

interface State {
  : boolean
  ?: Error
}

class  extends React.<Props, State> {
  constructor(: Props) {
    super()
    this. = { : false }
  }

  static (: Error): State {
    return { : true,  }
  }

  () {
    if (this..) {
      return <>Error</>
    }
    return this..
  }
}

interface Data {
  : string
}

const  = ({  }: <Data>) => (
  <>
    <>{.}</>
  </>
)

export default ({
  : 
})

类型推导调试

检查推导链路

使用 TypeScript 编译器查看类型推导:

# 1. 生成类型声明文件
pnpm tsc --declaration --emitDeclarationOnly

# 2. 查看 .d.ts 文件
cat .ktr/registry-types.d.ts

类型断言调试

强制显示推导类型:

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

interface UserCardData {
  : string
  : string
}

const  = ({  }: <UserCardData>) => {
  // 类型断言:data 应为 UserCardData
  const _check: UserCardData = 
const _check: UserCardData
return <>{.}</> } const = ({ : }) // 检查 DataOf 推导 type ExtractedData = <typeof >
type ExtractedData = UserCardData

@ts-expect-error 测试

验证类型错误:

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

interface Data {
  : string
}

const  = ({  }: <Data>) => {
  // @ts-expect-error age 不在类型中
  .(.age)

  return <>{.}</>
}

export default ({
  : 
})

模块增强检查

验证 ProjectRegistry 是否生效:

test-registry.ts
import type {  } from '@karinjs/template-react'

// 检查注册表类型
type Registry = 
type Registry = {
    [x: string]: TemplateDef<any>;
}
// 检查是否有精确路由 type Paths = keyof
type Paths = string
// 检查特定路由的数据类型 type UserCardData = ['user/card'] extends { ?: infer } ? : never
type UserCardData = any

tsserver 日志

启用 TypeScript 服务器日志:

tsconfig.json
{
  "compilerOptions": {
    "plugins": [
      {
        "name": "typescript-plugin-css-modules"
      }
    ]
  },
  "ts-node": {
    "logError": true
  }
}

查看日志:

# VS Code 命令面板
# TypeScript: Open TS Server log

网络请求调试

Mock API 日志

开启 mock API 详细日志:

packages/core/src/dev/mock-api.ts
// 实际导出是 registerMockApi(server, config),在中间件里加日志即可追踪请求
export const registerMockApi = (server: ViteDevServer, config: ResolvedKtrConfig): void => {
  server.middlewares.use('/__ktr/api', (req, res, next) => {
    const start = Date.now()

    console.log(`[Mock API] ${req.method} ${req.url}`)

    res.on('finish', () => {
      console.log(`[Mock API] ${req.method} ${req.url} - ${Date.now() - start}ms`)
    })

    next()
  })
}

代理调试

记录代理请求:

karin.template.ts
export default ({
  : {
    : {
      : {
        '/api': {
          : 'http://localhost:8080',
          : true,
          : (, ) => {
            .('proxyReq', (, , ) => {
              .('[代理] 请求:', ., .)
            })
            .('proxyRes', (, , ) => {
              .('[代理] 响应:', ., .)
            })
            .('error', (, , ) => {
              .('[代理] 错误:', .)
            })
          }
        }
      }
    }
  }
})

SSE 调试

监听 Server-Sent Events:

test-sse.ts
const eventSource = new EventSource('http://localhost:5180/__ktr/api/stream')

eventSource.onmessage = (event) => {
  console.log('[SSE] 收到消息:', JSON.parse(event.data))
}

eventSource.onerror = (error) => {
  console.error('[SSE] 连接错误:', error)
}

文件监听调试

追踪文件变更:

packages/core/src/dev/data-watch.ts
// 实际导出是 registerDataWatch(server, config),复用 Vite 内置 watcher,不另起 chokidar 实例
export const registerDataWatch = (server: ViteDevServer, config: ResolvedKtrConfig): void => {
  server.watcher.add(config.mockDataDir)

  server.watcher.on('all', (event, file) => {
    if (!file.endsWith('.json')) return
    console.log('[文件监听] 事件:', event, '路径:', file)
  })
}

常见调试场景

场景 1:模板未注册

问题:开发面板看不到新建的模板。

调试步骤

# 1. 检查文件位置
ls -la ktr/template/user/card/
# 应包含 index.tsx

# 2. 手动刷新注册表
pnpm ktr sync

# 3. 查看生成的注册表
cat .ktr/template-registry.ts
# 检查是否包含 'user/card'

# 4. 检查是否被忽略
# 文件名不是 index.tsx?
# 目录名以 _ 开头?
# 在 components/ 下?

解决方案

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

// ✅ 正确:导出 defineTemplate
export default ({
  : () => <>User Card</>
})

// ❌ 错误:直接导出组件
// export default () => <div>User Card</div>

场景 2:数据类型不匹配

问题:运行时数据与类型定义不一致。

调试步骤

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

interface UserCardData {
  : string
  : number
}

const  = ({  }: <UserCardData>) => {
  // 1. 运行时检查
  .('数据类型:', typeof )
  .('数据内容:', )
  .('是否有 age:', 'age' in )

  return <>{.}</>
}

export default ({
  : ,
  // 2. 添加 validate
  : ():  is UserCardData => {
    if (typeof  !== 'object' ||  === null) {
      .('[validate] 数据不是对象:', )
      return false
    }
    if (!('name' in ) || typeof ( as any).name !== 'string') {
      .('[validate] 缺少 name 字段或类型错误')
      return false
    }
    if (!('age' in ) || typeof ( as any).age !== 'number') {
      .('[validate] 缺少 age 字段或类型错误')
      return false
    }
    return true
  }
})

场景 3:CSS 未生效

问题:Tailwind 类名不起作用。

调试步骤

# 1. 检查 CSS 入口
cat ktr/template/style.css
# 应包含 @import 'tailwindcss'

# 2. 检查构建产物(产物目录随打包器 outDir,这里以 lib/ 为例)
cat lib/style.css
# 检查是否包含对应的 CSS 规则

# 3. 检查 HTML(默认输出目录是 dist/template/html/)
cat dist/template/html/user_card.html
# 检查 <style> 标签内容

解决方案

ktr/template/style.css
/* ✅ 正确 */
@import 'tailwindcss';
@import '@karinjs/template-react/styles';
@source '../ktr/template';

/* ❌ 错误:缺少 @source */
@import 'tailwindcss';

场景 4:图片加载失败

问题:开发环境图片正常,SSR 渲染后图片 404。

调试步骤

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

interface Data {
  : string
}

const  = ({  }: <Data>) => {
  // 1. 打印图片路径
  .('图片路径:', .)

  // 2. 检查路径格式
  // ✅ 正确:/assets/avatar.png
  // ❌ 错误:./assets/avatar.png
  // ❌ 错误:assets/avatar.png

  return < ={.} ="头像" />
}

export default ({ :  })

解决方案

// ✅ 正确:使用 / 开头的路径(等于 <dir.assets>/ 下的相对路径)
<img src="/assets/avatar.png" alt="" />

// ❌ 错误:使用相对路径
<img src="./avatar.png" alt="" />

SSR 产出 HTML 时框架会自动改写这些引用:不超过 html.assetsInlineLimit(默认 4096 字节)的内联为 base64,超过的转为 file:// 绝对路径,开发和生产都不需要手写路径转换插件。如果图片仍然 404,按顺序检查:

  1. 引用路径是否以 / 开头(./assets/x.pngassets/x.png 都不会被处理);
  2. 文件是否真实存在于 <dir.assets>/ 下的对应位置(如 ktr/public/assets/avatar.png);
  3. 生产态确认产物目录下 assets/ 存在(如 lib/assets/,由构建时的 copyAssets 复制)。

场景 5:端口被占用

问题ktr dev 启动失败,提示端口被占用。

调试步骤

# 1. 查看端口占用
# Windows
netstat -ano | findstr :5180

# macOS/Linux
lsof -i :5180

# 2. 查看进程详情
# Windows
tasklist /FI "PID eq <PID>"

# macOS/Linux
ps -p <PID>

# 3. 杀死进程
# Windows
taskkill /F /PID <PID>

# macOS/Linux
kill -9 <PID>

解决方案

karin.template.ts
export default ({
  : {
    : 5181 // 换个端口
  }
})

场景 6:HMR 不工作

问题:修改代码后画布不更新。

调试步骤

# 1. 检查 Vite 日志
# 应看到 [vite] page reload 或 [vite] hot updated

# 2. 检查浏览器控制台
# 应看到 [vite] connecting...
# 不应看到 WebSocket connection failed

# 3. 检查文件是否被忽略
cat karin.template.ts
# 检查 vite.server.watch.ignored

解决方案

karin.template.ts
export default ({
  : {
    : {
      : {
        // 确保没有忽略模板目录
        : ['!**/ktr/template/**']
      }
    }
  }
})

场景 7:类型推导失效

问题render('user/card', data) 没有类型提示。

调试步骤

# 1. 检查注册表类型
cat .ktr/registry-types.d.ts
# 应包含 ProjectRegistry 的增强

# 2. 重启 TypeScript 服务器
# VS Code: 命令面板 → TypeScript: Restart TS Server

# 3. 手动刷新
pnpm ktr sync

# 4. 检查 tsconfig.json
cat tsconfig.json
# 应包含 "include": [".ktr/**/*"]

解决方案

tsconfig.json
{
  "include": [
    "src/**/*",
    ".ktr/**/*" // 包含生成的类型文件
  ]
}

场景 8:插件不执行

问题:自定义插件的钩子没有被调用。

调试步骤

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

const : RenderPlugin = {
  : 'my-plugin',
  : () => {
    // 1. 添加日志
    .('[插件] beforeRender 被调用')
    .('[插件] 路径:', .)
  },
  : () => {
    // 2. 检查是否返回
    .('[插件] afterRender 被调用')
    .('[插件] HTML 长度:', ..)

    // ⚠️ 必须返回 HTML 或 undefined
    return .
  }
}

常见错误

const  = (, {
  ,
  ,
  : [] // 渲染插件在这里注册
})

调试工具推荐

VS Code 扩展

.vscode/extensions.json
{
  "recommendations": ["dbaeumer.vscode-eslint", "bradlc.vscode-tailwindcss", "esbenp.prettier-vscode", "msjsdiag.vscode-react-native"]
}

浏览器扩展

  • React Developer Tools:查看组件树
  • Redux DevTools:状态管理调试
  • Lighthouse:性能分析

命令行工具

# 端口占用检查
npx kill-port 5180

# 依赖分析
npx depcheck

# 类型检查
pnpm tsc --noEmit

# ESLint
pnpm eslint . --ext .ts,.tsx

日志库

src/utils/logger.ts
export const logger = {
  debug: (...args: any[]) => {
    if (process.env.NODE_ENV === 'development') {
      console.log('[DEBUG]', ...args)
    }
  },
  info: (...args: any[]) => console.log('[INFO]', ...args),
  warn: (...args: any[]) => console.warn('[WARN]', ...args),
  error: (...args: any[]) => console.error('[ERROR]', ...args)
}

使用:

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

const  = () => {
  .('组件渲染')
  return <>Hello</>
}

调试配置

启用详细日志

karin.template.ts
export default ({
  : {
    : 'info', // 'error' | 'warn' | 'info' | 'silent'
    : false // 不清屏,保留历史日志
  }
})

Source Map

karin.template.ts
export default ({
  : {
    : {
      : true // 生成 source map 用于调试
    }
  }
})

错误堆栈

src/utils/render.ts
Error.stackTraceLimit = 50 // 增加堆栈深度

On this page