调试技巧
开发面板调试工具、SSR 渲染调试、类型推导调试、网络请求调试与常见调试场景
掌握调试技巧可以快速定位问题、提升开发效率。本页讲解开发面板、SSR、类型推导和网络请求的调试方法。
开发面板调试
检查模式
点击元素定位到源码:
# 启动开发面板
pnpm template
# 访问 http://localhost:5180/__ktr/panel/
# 点击工具栏「定位」按钮,或按住 Shift+Alt 点击元素检查模式由 code-inspector-plugin 实现:开启后点击模板中的元素会直接跳转到编辑器对应源码位置,不会在控制台输出额外日志。
React DevTools
在沙盒 iframe 中使用 React DevTools:
export default ({
: {
: {
: '({ isDisabled: false })'
}
}
})打开浏览器控制台 → Components 标签即可查看组件树。
沙盒通信日志
查看面板 ↔ 沙盒的 postMessage 通信:
// 开启调试日志
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 → 面板 → 沙盒 → 组件的流动:
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 事件:
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 中捕获组件错误:
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..
}
}使用错误边界:
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 =
return <>{.}</>
}
const = ({
:
})
// 检查 DataOf 推导
type ExtractedData = <typeof >@ts-expect-error 测试
验证类型错误:
import { , type } from '@karinjs/template-react'
interface Data {
: string
}
const = ({ }: <Data>) => {
// @ts-expect-error age 不在类型中
.(.age)
return <>{.}</>
}
export default ({
:
})模块增强检查
验证 ProjectRegistry 是否生效:
import type { } from '@karinjs/template-react'
// 检查注册表类型
type Registry =
// 检查是否有精确路由
type Paths = keyof
// 检查特定路由的数据类型
type UserCardData = ['user/card'] extends { ?: infer } ? : nevertsserver 日志
启用 TypeScript 服务器日志:
{
"compilerOptions": {
"plugins": [
{
"name": "typescript-plugin-css-modules"
}
]
},
"ts-node": {
"logError": true
}
}查看日志:
# VS Code 命令面板
# TypeScript: Open TS Server log网络请求调试
Mock API 日志
开启 mock API 详细日志:
// 实际导出是 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()
})
}代理调试
记录代理请求:
export default ({
: {
: {
: {
'/api': {
: 'http://localhost:8080',
: true,
: (, ) => {
.('proxyReq', (, , ) => {
.('[代理] 请求:', ., .)
})
.('proxyRes', (, , ) => {
.('[代理] 响应:', ., .)
})
.('error', (, , ) => {
.('[代理] 错误:', .)
})
}
}
}
}
}
})SSE 调试
监听 Server-Sent Events:
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)
}文件监听调试
追踪文件变更:
// 实际导出是 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/ 下?解决方案:
import { } from '@karinjs/template-react'
// ✅ 正确:导出 defineTemplate
export default ({
: () => <>User Card</>
})
// ❌ 错误:直接导出组件
// export default () => <div>User Card</div>场景 2:数据类型不匹配
问题:运行时数据与类型定义不一致。
调试步骤:
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> 标签内容解决方案:
/* ✅ 正确 */
@import 'tailwindcss';
@import '@karinjs/template-react/styles';
@source '../ktr/template';
/* ❌ 错误:缺少 @source */
@import 'tailwindcss';场景 4:图片加载失败
问题:开发环境图片正常,SSR 渲染后图片 404。
调试步骤:
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,按顺序检查:
- 引用路径是否以
/开头(./assets/x.png、assets/x.png都不会被处理); - 文件是否真实存在于
<dir.assets>/下的对应位置(如ktr/public/assets/avatar.png); - 生产态确认产物目录下
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>解决方案:
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解决方案:
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/**/*"]解决方案:
{
"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 扩展
{
"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日志库
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</>
}调试配置
启用详细日志
export default ({
: {
: 'info', // 'error' | 'warn' | 'info' | 'silent'
: false // 不清屏,保留历史日志
}
})Source Map
export default ({
: {
: {
: true // 生成 source map 用于调试
}
}
})错误堆栈
Error.stackTraceLimit = 50 // 增加堆栈深度