@ttsc/unplugin
@ttsc/unplugin(packages/unplugin)把 ttsc 插件接进 unplugin 支持的打包器:Vite、Rollup、Rolldown、esbuild、Webpack、Rspack、Next.js、Farm、Bun。当打包器拥有你的构建时,用它在打包器里跑 ttsc 插件(typia 等)。
设计:一个 transform hook
@ttsc/unplugin 的核心是一个 unplugin factory(src/core/index.ts:38),它在每个 TypeScript/JSX 模块上调 ttsc 的 transform,把结果交回打包器:
factory(unpluginFactory,src/core/index.ts:38):解析一次 raw options、建 per-build transform 缓存、经 vite.configResolved hook 捕获 Vite alias 配置(转发进生成的 tsconfig overlay)。缓存在每次 buildStart 清,避免 watch 重建跨次陈旧。
enforce: "pre" 让 ttsc transform 在打包器自己的 TS 处理前跑。transformInclude + isTransformTarget(src/core/index.ts:100)排除虚拟模块(NUL 前缀)、.d.ts、node_modules。
各打包器适配
src/index.ts 导出统一 unplugin 实例(带命名适配器 .vite/.rollup/…)与裸 factory。各 per-bundler 入口(@ttsc/unplugin/vite 等)直接 re-export 对应适配器,让 bundler 特定构建精简。
transform 缓存(性能核心)
src/core/transform.ts(~870 LOC)的 TtscCachedProjectTransform(transform.ts:53)按项目输入文件的 SHA-256 哈希缓存整个编译结果:
inputHashes:每个项目相对输入路径在 transform 时的 SHA-256。- 后续 transform 重哈希项目、与
inputHashes比,mismatch 触发完整重转换。
注释(transform.ts:48)记录一个真实 bug:两侧必须哈希同一组文件(项目目录 walk),只在一侧 key 编译器的 out-of-walk 输出路径会让缓存每个模块都 miss。
alias 转发
打包器(尤其 Vite/webpack/Rspack)有自己的路径 alias。tsconfigPaths.ts 把它们转成生成 tsconfig 的 paths overlay,让 ttsc 的 transform(建在 tsgo Program 上)也认识这些 alias。TtscTransformAlias(transform.ts:35)归一化 Vite 数组 alias 与 webpack/Rspack 对象 alias 成 { find, replacement }。
watch 依赖注册
transform hook(src/core/index.ts:65)把插件报告的 dependencies(transform 信封的 dependencies 列表,见 transform 派发)经 this.addWatchFile 注册——type-only 输入变化时让该模块在 watch 模式失效。因为打包器会从自己的模块图里抹掉 type-only import,否则会服务陈旧的生成代码。
与 metro 的关系
@ttsc/unplugin 覆盖 unplugin 生态的打包器;React Native/Expo 用 Metro 打包,unplugin 够不着,由 @ttsc/metro 单独覆盖(见 @ttsc/metro)。两者都依赖 ttsc 包的 TtscCompiler/TtscService。
不变量
- transform 在打包器 TS 处理前跑(
enforce: "pre")。 - 缓存两侧哈希同一文件组(项目 walk),否则每模块 miss。
- 排除虚拟模块、
.d.ts、node_modules。 - type-only 依赖经 addWatchFile 注册,防 watch 陈旧。
维护者提示
- 加打包器适配 = 加
src/<bundler>.tsre-export 对应适配器,core 逻辑不动。 - 改缓存 key 时记住两侧文件组对称这个坑。
- alias 转发是 ttsc transform 认识打包器 alias 的唯一途径,改它影响所有打包器。