独立构建
使用 ktr build 生成包含模板、React SSR runtime、CSS 和精确类型声明的 Node ESM 运行包
独立构建适合宿主应用本身不使用 Vite、tsdown 等打包器的场景。例如业务程序只是普通的 node app.js,但模板作者仍希望使用 React、Tailwind CSS 和严格 TypeScript。
ktr build 只构建模板运行包,不接管宿主应用。开发阶段仍然使用 ktr dev 和现有开发面板。
与 npm 打包器模式的区别
| 模式 | 适用项目 | 构建入口 | CSS | 运行时 |
|---|---|---|---|---|
| 独立构建 | 普通 Node ESM、JavaScript 宿主 | ktr build | 内嵌在 index.mjs | React、SSR runtime、模板和普通依赖默认打进单一 chunk |
| 打包器集成 | 已经使用 Vite、tsdown 的 npm/Karin 插件 | ktrBuildPlugin() | 输出到打包器 outDir 的 style.css | 跟随下游打包配置 |
已经有成熟打包流程时继续使用 构建发布 中的 ktrBuildPlugin()。宿主只想直接加载模板运行包时使用本页方案。
前置要求
- 宿主运行环境为 Node.js 18+ ESM。
- 模板路由文件必须是
ktr/template/<板块>/<模板>/index.tsx。 - 下游项目需要安装
typescript、React 和对应类型声明。 - 模板 data 必须有明确类型,不能退化为
any、unknown或never。 - 第一阶段不支持
index.jsx和 CJS。模板动态 import 已支持:standalone.singleChunk: false开启代码分割后,额外 chunk 落在chunks/[name]-[hash].mjs;仅默认singleChunk: true时会因产生多个 chunk 报构建错误。
宿主业务代码可以是 JavaScript;TypeScript 只用于模板开发和构建检查。
目录结构
项目根目录/
├── app.js
├── karin.template.ts
├── package.json
└── ktr/
├── public/
│ └── logo.png
└── template/
├── style.css
└── hello/
└── card/
└── index.tsx模板需要暴露明确的 data 类型:
import { , type } from '@karinjs/template-react'
interface CardData {
: string
: <{ : string; : string }>
}
const = ({ }: <CardData>) => (
< ="flex w-160 flex-col gap-4 p-8">
< ="text-3xl font-bold">{.}</>
{..(() => (
< ={.} ="flex justify-between">
<>{.}</>
<>{.}</>
</>
))}
</>
)
export default <CardData>({ : })配置
所有配置都有默认值,最小配置可以为空:
import { } from '@karinjs/template-react'
export default ({})完整的独立构建配置:
import { } from '@karinjs/template-react'
export default ({
: {
: 'dist/ktr',
: 'node18',
: 'esm',
: false,
: false,
: 'copy',
: [],
: true
}
})| 配置 | 默认值 | 说明 |
|---|---|---|
outDir | dist/ktr | 独立运行包输出目录 |
target | node18 | JavaScript 语法目标 |
format | esm | 当前固定为 ESM |
minify | false | 是否压缩 JavaScript |
sourcemap | false | 是否生成 source map |
assets | copy | 把 ktr/public/ 复制到产物 assets/ |
external | [] | 允许保留为运行时依赖的包名,适合 native 模块 |
singleChunk | true | 要求只有一个 JavaScript chunk;模板动态 import 会导致构建失败 |
执行构建
pnpm ktr build也可以写入 package.json:
{
"type": "module",
"scripts": {
"template:dev": "ktr dev",
"template:build": "ktr build"
}
}默认产物:
dist/ktr/
├── index.mjs
├── index.d.mts
└── assets/ # 存在静态资源时生成index.mjs包含模板 Registry、React JSX runtime、React SSR runtime、ktr renderer、模板依赖和编译后的 CSS。index.d.mts保留模板路由与 data 的一一对应关系。assets/是ktr/public/的副本。
模板里 <img src="/xxx"> 这类标记资源由入口内置的渲染函数自动处理:以入口同级的 assets/ 为根(相对关系在构建期烘死,单 chunk 或 chunks/ 子目录都不影响),按 html.assetsInlineLimit 内联为 base64 或转为 file:// 绝对路径。dir.copyAssets: false(资源随包发布)时则不生成 assets/,构建期把 dir.assets 相对产物目录的位置烘进入口,渲染时直接定位随包发布的那份——包里只有一份资源。
standalone CSS 内嵌在 index.mjs,因此不会额外生成 style.css。构建过程也不会生成 HTML;只有实际调用渲染函数时,renderer 才会写出
RenderResult.htmlPath 对应的 HTML 文件。
构建时还会在缓存目录生成 .ktr/standalone-entry.ts。它是 ktr 交给 TypeScript 和 Rolldown 的中间入口,内部汇总所有模板、检查每个路由的 data 类型、嵌入编译后的 CSS,并导出最终的渲染函数。它不属于 dist/ktr/ 发布产物,已被 .gitignore 忽略;再次执行 ktr build 时会自动覆盖,不要手动编辑。
JavaScript 宿主调用
// @ts-check
// 实际代码:import { renderTemplate } from './dist/ktr/index.mjs'
const = await ('hello/card', {
: '运行状态',
: [{ : '服务', : '正常' }]
})
if (!.) {
throw new (.)
}
.(.)默认 HTML 输出到 dist/ktr/html/,每个路由使用固定文件名并覆盖旧文件,例如 hello/card 对应 hello_card.html。
JavaScript 项目启用 checkJs 后,也能从 index.d.mts 获得路由补全和 data 错误检查:
// @ts-check
// 实际代码:import { renderTemplate } from './dist/ktr/index.mjs'
await ('hello/card', { : '正确', : [] })
// 路由不存在,编辑器和 tsc 会报错
await ('hello/missing', {})
// title 类型错误,编辑器和 tsc 会报错
await ('hello/card', { title: 123, : [] })自定义运行时选项
需要调整 HTML 输出目录、文件名或插件时,使用产物导出的 createTemplateRenderer:
// @ts-check
// 实际代码:import { createTemplateRenderer } from './dist/ktr/index.mjs'
import from 'node:path'
const = ({
: .('runtime-html'),
: 'timestamp',
: .('captured-data')
})
const = await ('hello/card', {
: '自定义输出',
: []
})运行时可以覆盖 outputDir、captureDir、htmlFileName、plugins 和 html 等 renderer 选项。CSS 已在构建时固定到运行包中,不能用 cssPath 或 cssText 覆盖。
类型行为
生成的声明等价于以下精确映射:
type = {
'hello/card': CardData
'user/profile': ProfileData
}
declare function < extends keyof >(: , : []): <RenderResult>因此非法路由、缺少字段、字段类型错误和不同路由之间的 data 串用都会在构建或消费端类型检查时报错。
第一阶段的 index.d.mts 会引用项目内的模板 TSX 源文件。运行时加载 index.mjs 不依赖源码,但希望保留 TypeScript / checkJs 类型提示时,应同时保留 ktr/template/。
常见构建错误
data 退化为 any 或 unknown
为组件声明明确的 TemplateProps<Data>,并把同一个 Data 传给 defineTemplate<Data>()。
生成多个 chunk
默认 singleChunk: true 要求产物只有一个 chunk。模板里需要保留动态 import 时,设置 standalone.singleChunk: false 开启代码分割,额外 chunk 落在 chunks/ 子目录即可。
未声明的外部依赖
普通 JavaScript 依赖默认会被打进运行包。native 模块或必须由宿主提供的包加入 standalone.external:
import { } from '@karinjs/template-react'
export default ({
: {
: ['sharp']
}
})宿主运行时需要自行安装这些 external 依赖。
没有单独的 style.css
这是预期行为。独立构建把最终 CSS 内嵌到 index.mjs;npm 打包器模式才会生成独立的 style.css。