独立构建

使用 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.mjsReact、SSR runtime、模板和普通依赖默认打进单一 chunk
打包器集成已经使用 Vite、tsdown 的 npm/Karin 插件ktrBuildPlugin()输出到打包器 outDir 的 style.css跟随下游打包配置

已经有成熟打包流程时继续使用 构建发布 中的 ktrBuildPlugin()。宿主只想直接加载模板运行包时使用本页方案。

前置要求

  • 宿主运行环境为 Node.js 18+ ESM。
  • 模板路由文件必须是 ktr/template/<板块>/<模板>/index.tsx
  • 下游项目需要安装 typescript、React 和对应类型声明。
  • 模板 data 必须有明确类型,不能退化为 anyunknownnever
  • 第一阶段不支持 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 类型:

ktr/template/hello/card/index.tsx
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>({ :  })

配置

所有配置都有默认值,最小配置可以为空:

karin.template.ts
import {  } from '@karinjs/template-react'

export default ({})

完整的独立构建配置:

karin.template.ts
import {  } from '@karinjs/template-react'

export default ({
  : {
    : 'dist/ktr',
    : 'node18',
    : 'esm',
    : false,
    : false,
    : 'copy',
    : [],
    : true
  }
})
配置默认值说明
outDirdist/ktr独立运行包输出目录
targetnode18JavaScript 语法目标
formatesm当前固定为 ESM
minifyfalse是否压缩 JavaScript
sourcemapfalse是否生成 source map
assetscopyktr/public/ 复制到产物 assets/
external[]允许保留为运行时依赖的包名,适合 native 模块
singleChunktrue要求只有一个 JavaScript chunk;模板动态 import 会导致构建失败

执行构建

pnpm ktr build

也可以写入 package.json

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 宿主调用

app.js
// @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 错误检查:

app.js
// @ts-check
// 实际代码:import { renderTemplate } from './dist/ktr/index.mjs'
await ('hello/card', { : '正确', : [] })

// 路由不存在,编辑器和 tsc 会报错
await ('hello/missing', {})
Argument of type '"hello/missing"' is not assignable to parameter of type '"hello/card"'.
// title 类型错误,编辑器和 tsc 会报错 await ('hello/card', { title: 123, : [] })
Type 'number' is not assignable to type 'string'.

自定义运行时选项

需要调整 HTML 输出目录、文件名或插件时,使用产物导出的 createTemplateRenderer

app.js
// @ts-check
// 实际代码:import { createTemplateRenderer } from './dist/ktr/index.mjs'
import  from 'node:path'

const  = ({
  : .('runtime-html'),
  : 'timestamp',
  : .('captured-data')
})

const  = await ('hello/card', {
  : '自定义输出',
  : []
})

运行时可以覆盖 outputDircaptureDirhtmlFileNamepluginshtml 等 renderer 选项。CSS 已在构建时固定到运行包中,不能用 cssPathcssText 覆盖。

类型行为

生成的声明等价于以下精确映射:

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

karin.template.ts
import {  } from '@karinjs/template-react'

export default ({
  : {
    : ['sharp']
  }
})

宿主运行时需要自行安装这些 external 依赖。

没有单独的 style.css

这是预期行为。独立构建把最终 CSS 内嵌到 index.mjs;npm 打包器模式才会生成独立的 style.css

On this page