插件宿主子系统
插件宿主是 ttsc 最具特色的设计:插件是用户项目里的 Go 源码,描述符是 JS,ttsc 用打包的 Go 工具链按需把源码编译成缓存里的二进制。这一套横跨 JS 启动层(发现、加载、构建、缓存)与 Go driver(注册、配对、执行)。
本子系统的页面:
- 描述符协议:
ITtscPlugin契约、阶段、composes、contributors、能力。 - 加载与发现:
loadProjectPlugins全流程、tsconfig 与包自动发现、ttsx描述符加载、export 条件。 - go build 缓存:
buildSourcePlugin、缓存键、跨进程构建锁、贡献者合并、go.work 编排、全局缓存 GC。 - transform 派发:check/transform 阶段如何 spawn、共享宿主选择、
--plugins-json与链接 manifest。
一图看懂
三个核心概念
1. kind:executable vs linked
由 Go 包名判定(loadProjectPlugins.ts::resolveNativeSourceKind):package main → executable(独立旁车),否则 → linked(链接进通用宿主)。只有 transform 阶段能链接。
2. stage:check vs transform
- check:先跑,失败即止。
ttsc fix/format走 check 插件的 fix/format 子命令。@ttsc/lint是典型 check 插件。 - transform:在 emit 阶段注入 AST/文本。typia、nestia 是典型 transform 插件。默认 stage 是 transform(
resolvePluginStage,缺省即 transform)。
orderNativePlugins(loadProjectPlugins.ts:562)保证 check 在 transform 前。
3. 两种组合:composes(水平) vs contributors(垂直)
这是 ITtscPlugin 里两个易混的字段:
composes(水平):多个 plugin 条目派发到同一个二进制。每个条目仍是compilerOptions.plugins[]的顶层公民,有自己的生命周期槽。包自动发现可能找到多个必须共享一个 emit 宿主的 transform 包;一个描述符在composes里列出另一个时,ttsc 保留原 plugin config 但把被组合条目的源指向本描述符的原生源,让两者解析到同一二进制。contributors(垂直):一个二进制静态链接额外 Go 源,这些源不作为顶层插件条目出现。贡献的 npm 包通过宿主插件自己的配置文件发现(如@ttsc/lint的lint.config.ts)。@ttsc/lint的第三方规则插件就走这条路。
详见 描述符协议。
关键不变量
- JS 描述符,Go 逻辑。带
transformSource/transformOutput的描述符被rejectJsTransformFunctions拒绝——JS transform 函数不是公共契约。 - 源码 only,不接受预编译二进制。
source必须是 Go 包目录或go.mod,ttsc 自己go build。 - 缓存键覆盖一切能改变二进制的输入:ttsc/tsgo 版本、平台、entry、Go 编译器身份、Go 构建环境变量、overlay 模块源、插件源文件、贡献者源文件。一个 toolchain 升级会自动产生新二进制。
- 一个 cache key 只构建一次:跨进程
mkdir锁让 fan-out(并行测试套件、基准、worker pool)只跑一次go build,其余进程轮询等二进制出现。
设计取舍
接下来
从 描述符协议 开始读最自然,然后是 加载与发现、go build 缓存、transform 派发。