@typeset/vite-plugin-static-component
在 Vite 转换阶段执行静态组件,将生成的 HTML 内联到调用方的 JSX 中。适合把排版计算、静态列表生成等工作提前完成,让浏览器直接使用结果。
本包提供框架无关的编译核心。应用接入请使用 React 适配包或 Solid 适配包。这些包目前都是仓库内的私有 workspace 包,直接导出 TypeScript 源码。
工作方式
- 在框架 JSX 编译前,查找
.tsx调用方导入的静态组件。 - 识别组件模块中从对应 runtime 导入的
staticComponent()标记。 - 提取每个 JSX 调用的静态 props、文本 children 和
renderWrapper。 - 创建独立的 Vite SSR 服务,执行适配层生成的虚拟模块,取得 HTML 字符串。
- 用 HTML 表达式替换 wrapper 参数的引用,再用 wrapper 的 JSX 替换原组件调用。
- 删除原静态组件 import,保留其使用到的样式和资源依赖。
例如,以下 Solid 调用:
<Greeting name="Typeset" renderWrapper={(html) => <section innerHTML={html} />} />
若组件生成 <p>Hello Typeset</p>,转换后的 JSX 等价于:
<section innerHTML={"<p>Hello Typeset</p>"} />
HTML 随调用方模块进入构建产物;插件不单独输出 HTML 页面。静态内容没有客户端组件实例,也不会恢复内部事件处理器。wrapper 仍由应用的框架正常编译,可以保留调用方的动态属性和事件。
开发模式也会在模块转换时执行组件。依赖修改后,当前实现通过整页刷新重新生成内容。
适配器接口
包入口导出 createStaticComponentPlugin(adapter): Plugin,以及 StaticComponentAdapter、StaticComponentRenderModuleInput 两个类型。
StaticComponentAdapter 的字段如下:
| 字段 | 用途 |
|---|---|
pluginName |
Vite 插件名,也用于隔离虚拟模块命名空间 |
runtimeModule |
标记函数的导入来源,分析器按完整模块名匹配 |
markerName |
标记函数的导出名,现有适配器均为 staticComponent |
createRenderModule(input) |
返回虚拟 TSX 模块源码;执行后必须默认导出 HTML 字符串 |
createRenderPlugins?() |
返回内部 SSR 服务所需的框架编译插件 |
createRenderModule 接收以下输入:
| 字段 | 内容 |
|---|---|
componentPath |
Vite 解析后的组件模块 ID |
componentName |
调用方使用的组件本地名称 |
componentExportName |
具名导出的名称;默认导出时为 undefined |
propsObjectSource |
可放入对象字面量中的属性源码,包含属性间的逗号 |
childrenSource |
已拼接并去除首尾空白的文本 children;这是文本值,不是 JS 表达式源码 |
实现适配器时可参考 React 和 Solid。内部 SSR 服务只加载渲染插件及选定的外层配置,不会完整复用应用的插件列表。
输入约束
当前支持范围以 analyze.ts 和 rewrite.ts 为准:
- 调用方模块 ID 必须以
.tsx结尾,组件文件也必须为.tsx。.html.tsx只是建议的命名方式。 - 组件从对应 runtime 导入
staticComponent后,通过它定义默认或具名导出。支持本地变量导出和别名;跨文件 barrel 重导出不在当前识别范围内。 - 一个 import 声明只能导入一个静态组件;导入绑定只能用于 JSX 标签,不能赋值给其他变量或直接调用。
- 普通 props 支持字符串、数字、布尔值和无插值模板字符串。变量、对象、数组、函数调用、
null以及负数表达式等不在当前支持范围内。 - spread props 的静态求值规则尚未完善,当前仅复制表达式源码;不要依赖它读取调用方的变量。
- JSX children 只支持文本及上述字面量,拼接后会去除首尾空白。不支持嵌套 JSX。非空 JSX children 会覆盖显式的
children属性。 renderWrapper必须是内联箭头函数,使用普通标识符接收 HTML,并用表达式函数体直接返回 JSX。不能使用{ return ... }函数体。- 当前转换不生成 source map。
这些限制针对调用处的静态输入。组件内部可以进行计算、导入本地辅助模块,并由适配器支持异步执行;执行环境是服务端,无法访问调用方的浏览器状态。
样式和资源
分析器递归检查使用到的静态 import,收集 CSS、CSS Modules 和图片等资源,再通过虚拟资源模块保留客户端依赖。未使用的绑定和纯类型 import 不参与这条收集路径。
对需要出现在 HTML 中的图片 URL,使用显式 ?url 导入,例如:
import photoUrl from "./photo.png?url";
当前 HTML URL 重写只处理显式 URL 导入,匹配项目根目录对应的开发路径,再替换成由 Vite 管理的客户端 URL 引用。发现某种资源不等于支持其所有 URL 写法;自定义 base、根目录外资源和其他 query 组合需按实际项目验证。依赖收集也不覆盖任意动态 import 或重导出链。
组件直接返回字符串时,适配器将其视为 HTML,不再按普通文本转义。应由生成该字符串的代码负责内容转义。
开发与验证
在仓库根目录执行,环境要求 Node.js >= 24 和仓库指定的 pnpm:
pnpm --filter @typeset/vite-plugin-static-component test
pnpm --filter @typeset/vite-plugin-static-component exec tsc --noEmit
涉及渲染、资源或适配器接口时,同时运行集成测试。单元测试覆盖导入分析、wrapper 作用域替换和依赖映射;真实构建验证由集成测试包负责。