核心概念

约定式路由

文件扫描规则、路由生成流程和最佳实践

ktr 采用约定式路由设计:文件放到约定位置就自动注册,无需手写路由配置。

路由生成规则

基本规则

只有符合 <板块>/<模板>/index.tsx 模式的文件才会注册为路由:

ktr/template/
├── hello/
│   └── card/
│       └── index.tsx        ✅ 注册为路由 hello/card
├── user/
│   ├── profile/
│   │   └── index.tsx        ✅ 注册为路由 user/profile
│   └── card.tsx             ❌ 裸文件不注册
└── stats/
    └── dashboard/
        ├── index.tsx        ✅ 注册为路由 stats/dashboard
        └── components/
            └── chart.tsx    ❌ components 目录不扫描

路由字符串格式:<板块>/<模板>,对应文件路径:ktr/template/<板块>/<模板>/index.tsx

扫描规则详解

框架使用 fast-glob 扫描模板目录,模式为 **/index.tsx,但有以下过滤规则:

规则示例说明
必须是 index 文件card/index.tsx
card.tsx
目录深度不限,两级是惯例
忽略 components 目录card/components/badge.tsx模板内部组件
忽略下划线开头目录_shared/utils/index.tsx跨模板共享代码
// 框架内部扫描逻辑(简化)
import {  } from 'fast-glob'

async function (: string) {
  const  = await ('**/index.tsx', {
    : ,
    : [
      '**/components/**', // 忽略内部组件
      '**/_*/**' // 忽略下划线目录
    ]
  })

  return .(() => {
    // hello/card/index.tsx → hello/card
    return .(/\/index\.tsx$/, '')
  })
}

路由生成流程

触发时机

注册表生成在以下场景自动触发:

  1. ktr sync —— 手动刷新注册表
  2. ktr dev —— 启动开发服务器前
  3. 构建插件 —— Vite/Rolldown 构建前(buildStart 钩子)
// ktr dev 启动流程
const  = await ()
await () // ← 刷新注册表
const  = await ()

生成流程

完整流程分为 4 步:

1. 扫描文件系统
   ↓ discoverTemplateRoutes()
2. 生成 template-registry.ts
   ↓ generateTemplateRegistry()
3. 生成 mock-registry.ts
   ↓ generateMockRegistry()
4. 生成 registry-types.d.ts
   ↓ generateTypeAugmentation()

步骤 1:扫描文件系统

// conventions/registry.ts
import {  } from 'fast-glob'
import * as  from 'node:path'

async function (: string) {
  const  = await ('**/index.tsx', {
    : ,
    : ['**/components/**', '**/_*/**']
  })

  // 转换为路由字符串
  const  = .(() => {
    // 移除 /index.tsx 后缀
    return .(/\/index\.tsx$/, '')
  })

  return .() // 按字母排序
}

// 结果示例:['hello/card', 'user/profile', 'stats/dashboard']

步骤 2:生成 template-registry.ts

// 生成的 .ktr/template-registry.ts
import type {  } from '@karinjs/template-react'
import  from '../ktr/template/hello/card/index'
import  from '../ktr/template/user/profile/index'
import  from '../ktr/template/stats/dashboard/index'

// 透传模板文件的具名导出,下游可以直接从注册表模块导入
export * from '../ktr/template/hello/card/index'
export * from '../ktr/template/user/profile/index'
export * from '../ktr/template/stats/dashboard/index'

export const : <string, <any>> = {
  'hello/card': ,
  'user/profile': ,
  'stats/dashboard': 
}

export type  = typeof 

路由 key 为原始路径字符串,value 为静态 import 的组件 default 导出。

步骤 3:生成 mock-registry.ts

// 生成的 .ktr/mock-registry.ts

// 导出所有 TS mock(具名导出)
export * from '../ktr/template/hello/card/mock'
export * from '../ktr/template/user/profile/mock'

// JSON mock 文件清单(保留 .json 后缀的文件名字符串数组)
export const  = {
  'hello/card': ['default.json', 'variant.json'],
  'user/profile': ['basic.json']
} as 

框架同时扫描每个模板目录下的:

  • mock.ts —— TS mock,直接 re-export
  • data/*.json —— JSON mock,按路由聚合成文件名字符串清单

步骤 4:生成 registry-types.d.ts

// 生成的 .ktr/registry-types.d.ts
declare module '@karinjs/template-react/registry-types' {
  interface ProjectRegistry {
    'hello/card': typeof import('../ktr/template/hello/card/index').default
    'user/profile': typeof import('../ktr/template/user/profile/index').default
    'stats/dashboard': typeof import('../ktr/template/stats/dashboard/index').default
  }
}

这是 TypeScript 模块增强(Module Augmentation),让 renderImagecreateTemplateRenderer 获得:

  1. 路由补全 —— 编辑器自动提示可用路由
  2. Data 类型推导 —— 根据模板的 Props 类型推导 data 参数

实际示例

示例 1:标准模板

ktr/template/
└── hello/
    └── card/
        ├── index.tsx          ← 模板组件
        ├── mock.ts            ← TS mock
        ├── components/
        │   └── badge.tsx      ← 内部组件(不扫描)
        └── data/
            ├── default.json   ← JSON mock
            └── captured.json  ← 捕获数据
// ktr/template/hello/card/index.tsx
import {  } from '@karinjs/template-react'
import type { HelloCardData } from './mock'

export default <HelloCardData>({
  : 'Hello 卡片',
  : '简单的信息卡片',
  : ({  }) => (
    < ="p-6 bg-surface rounded-lg">
      < ="text-xl font-bold">{.}</>
      < ="mt-4 space-y-2">
        {..( => (
          < ={.} ="flex justify-between">
            < ="text-muted">{.}</>
            < ="font-medium">{.}</>
          </>
        ))}
      </>
    </>
  )
})
// ktr/template/hello/card/mock.ts
import type {  } from './index'

export const  = {
  : 'Karin Template React',
  : [
    { : '渲染方式', : 'SSR HTML' },
    { : '样式方案', : 'Tailwind CSS v4' }
  ]
} satisfies 

export const  = {
  : '空状态',
  : []
} satisfies 

注册结果:

  • 路由hello/card
  • TS mockbasicempty(面板显示为只读数据源)
  • JSON mockdefaultcaptured(面板可编辑)

示例 2:共享代码组织

跨模板的工具函数和组件应放在下划线开头的目录:

ktr/template/
├── _shared/                    ← 不会被扫描
│   ├── components/
│   │   ├── avatar.tsx
│   │   └── badge.tsx
│   └── utils/
│       └── format.ts
├── user/
│   └── profile/
│       └── index.tsx           ← 可 import ../_shared
└── hello/
    └── card/
        └── index.tsx           ← 可 import ../_shared
// ktr/template/user/profile/index.tsx
import {  } from '@karinjs/template-react'
import {  } from '../../_shared/components/avatar'
import {  } from '../../_shared/utils/format'

export default ({
  : '用户资料',
  : ({  }) => (
    <>
      < src={.avatar} />
      <>{(.joinedAt)}</p>
    </div>
  )
})

示例 3:多级板块组织

路由深度不限——两级 <板块>/<模板> 只是惯例,更深的目录同样注册(如 demo/nested/deep/index.tsx → 路由 demo/nested/deep):

ktr/template/
├── user/
│   ├── profile/
│   │   └── index.tsx        → 路由 user/profile
│   ├── card/
│   │   └── index.tsx        → 路由 user/card
│   └── badge/
│       └── index.tsx        → 路由 user/badge
└── stats/
    ├── chart/
    │   └── index.tsx        → 路由 stats/chart
    └── table/
        └── index.tsx        → 路由 stats/table

开发面板会按板块(第一级目录名)分组显示模板列表。

最佳实践

1. 命名约定

  • 板块名:小写,用 - 分隔单词(如 user-profilestats-chart
  • 模板名:小写,描述性名词(如 cardprofiledashboard
  • 避免:中文路径、特殊字符、空格
✅ 推荐
ktr/template/user-info/profile/index.tsx    → user-info/profile
ktr/template/stats/daily-chart/index.tsx    → stats/daily-chart

❌ 不推荐
ktr/template/用户信息/个人资料/index.tsx
ktr/template/stats/Daily Chart/index.tsx

2. 目录结构建议

单个模板的推荐结构:

ktr/template/hello/card/
├── index.tsx              ← 模板入口,默认导出 defineTemplate
├── mock.ts                ← TS mock,具名导出多个示例
├── components/            ← 内部组件(只被本模板使用)
│   ├── header.tsx
│   └── footer.tsx
├── hooks/                 ← 自定义 hooks(可选)
│   └── use-animation.ts
└── data/                  ← JSON mock
    ├── default.json
    ├── variant.json
    └── captured.json

3. 类型安全实践

在模板文件中定义 Data 接口,mock 文件引用:

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

export interface HelloCardData {
  : string
  ?: string
  : <{
    : string
    : string | number
  }>
}

export default <HelloCardData>({
  : 'Hello 卡片',
  : ({  }) => {
    // data 有完整类型推导
    return <>{.}</>
  }
})
// ktr/template/hello/card/mock.ts
import type {  } from './index'

// satisfies 确保类型匹配,同时保留字面量类型
export const  = {
  : 'Karin Template React',
  : [{ : '渲染方式', : 'SSR HTML' }]
} satisfies 

// 或使用辅助函数
import {  } from '@karinjs/template-react'

export const  = <>({
  : '高级示例',
  : '带副标题',
  : [
    { : '版本', : '2.0.0' },
    { : '星标', : 1024 }
  ]
})

4. 何时刷新注册表

注册表会在以下情况自动刷新:

  • ktr dev 启动时
  • ✅ 开发面板每次请求模板列表前(自动 ensureTemplateRegistry 重扫,模板增删即时生效)
  • ktr build 构建时
  • ✅ Vite/Rolldown 插件 buildStart 钩子

开发态新建、删除、重命名模板目录,或增删 mock.ts / data/*.json,都无需手动 ktr sync,也无需重启 dev server——面板刷新页面即可看到变化。ktr sync 用于不走 dev server 和构建流程的场景(比如只想要 .ktr 类型增强时):

pnpm ktr sync

5. 调试注册表

注册表文件在 .ktr/ 目录,可直接查看:

# 查看注册的路由
cat .ktr/template-registry.ts

# 查看 mock 导出
cat .ktr/mock-registry.ts

# 查看类型增强
cat .ktr/registry-types.d.ts

如果发现路由未注册或类型不对,检查:

  1. 文件路径是否符合 <板块>/<模板>/index.tsx
  2. 是否在 components/_* 目录内
  3. 文件扩展名是否为 .tsx(只扫描 index.tsx
  4. 是否执行过 ktr sync

下一步

On this page