transform 派发

本页讲插件被构建之后,ttsc 如何把工作派发给原生进程:check 阶段怎么顺序跑、transform 阶段怎么选"共享宿主"、--plugins-jsonTTSC_LINKED_PLUGINS_JSON 怎么协作。涉及 src/compiler/internal/transformProjectInMemory.tssrc/compiler/internal/runBuild.tssrc/compiler/internal/sharedHostHelpers.ts

两个派发路径

ttsc 有两个会派发原生插件的入口,机制相似但产物不同:

路径入口产物文件
构建(emit JS)runBuildJS/d.ts/sourcemaprunBuild.ts
源到源转换transformProjectInMemoryTypeScript 文本transformProjectInMemory.ts

两者都遵循同一阶段顺序:先 check,后 transform

派发决策树

check 阶段:顺序、短路

runNativeChecks(transform 路径,transformProjectInMemory.ts:204)/ runNativeCheckPlugins(构建路径,runBuild.ts:764)顺序跑每个 check 插件,首个非零退出即返回,并用 appendBuildOutput 聚合诊断与输出。每个 check 插件用 createNativeCheckArgs 构造参数(check/fix/format 子命令 + --tsconfig= + --plugins-json= + --cwd=)。

check 阶段是 @ttsc/lint 的运行点:它作为 check 插件先于任何 transform 跑,把 lint 违规当作 emit 前的失败条件。

共享宿主选择(核心机制)

当存在多个 transform 插件时,它们必须共享一个 emit 进程(一次 emit pass 只能有一个二进制拥有 Program)。sharedHostHelpers.ts 三个函数管这件事:

// isLinkedTransform: stage === "transform" && kind === "linked"
// selectSharedHostPlugin: 优先选非 linked 的 (executable transform 拥有进程)
// linkedTransformPlugins: 收集所有 linked 的, 经环境变量塞进宿主进程
// assertSharedHostCompatibility: 校验去掉 linked 后只剩 <=1 个 owner 二进制

assertSharedHostCompatibilitysharedHostHelpers.ts:23):

  • 若所有 transform 插件解析到同一二进制(去重后 ≤1),通过。
  • 否则去掉 linked transform(它们会被链接进别的宿主),看剩下的 owner 二进制是否 ≤1;是则通过。
  • 否则报错。两个调用方措辞不同(pass 参数区分):emit 路径报 "multiple compiler native backends cannot share one emit pass",源到源路径报 "cannot share one source-to-source pass",并提示 "compose transform libraries through one aggregate native host"。

selectSharedHostPluginsharedHostHelpers.ts:57):选第一个 linked 的插件作为拥有进程的宿主;linked transform 源骑在用 driver.LoadProgram 的宿主里,所以有 executable transform 时它胜出,否则退到第一个(即 fallback driver host)。

两套 manifest 协作

派发一个 transform 宿主时同时传两样东西:

  1. --plugins-json(CLI 参数):序列化每个插件的 { config, name, stage }serializeNativePlugins,只传协议需要的字段保持参数短)。原生宿主据此知道有哪些插件、各自 config、阶段。
  2. TTSC_LINKED_PLUGINS_JSON(环境变量):只在 transform 阶段、且有链接源时设。nativePluginEnvrunBuild.ts:126 / transformProjectInMemory.ts:297)调 linkedTransformPlugins 收集链接插件并序列化进去。driver 在 loadLinkedPluginState 读它,按注册顺序配对(见 driver: 链接插件)。

所以一个有 typia(executable transform)+ 多个链接 transform 的项目里:typia 二进制拥有进程,--plugins-json 告诉它全部插件配置,TTSC_LINKED_PLUGINS_JSON 告诉它哪些链接源已编进自己进程、按什么顺序跑。

源到源 vs 构建的产物差异

源到源(transformProjectInMemory

无插件时走 transformProjectWithNativeHosttransformProjectInMemory.ts:70):构建 cmd/ttsc 原生编译宿主(buildNativeCompiler),spawn api-transform,解析 { typescript, diagnostics, dependencies? } 信封。有 transform 插件时 spawn 共享宿主的 transform 子命令;无 transform 插件但 check 通过时退回 api-transform

parseNativeTransformOutputtransformProjectInMemory.ts:333)严格校验信封:typescript 必须是 Record<string,string>,否则当协议错误抛出并带 stderr 上下文。可选 dependencies(每文件咨询过的源列表)作为 watch 元数据被转发,格式不对的条目被丢弃而非失败(它是 advisory 而非输出)。

构建(runBuild

构建路径在 emit 阶段更复杂(见 运行时流程 流程二)。关键分支(runBuild.ts:163):

  • 有 transform 编译器 → buildWithNativeCompilerPlugins(spawn 共享宿主 build 子命令,由它的 driver emit)。
  • 无 transform 编译器 → 视 reportsTypeScriptDiagnostics 与终止 flag 决定是否先跑 tsgo --noEmit 守卫,再 tsgo 构建。

format 模式的短路在这里很关键(见运行时流程页流程三):lint 旁车重写源后,绝不再跑 tsgo --noEmit 或 transform 编译器。

tsgo flag 转发

ttsc 没识别的 tsgo flag 经 --tsgo-args=<JSON> 转发给原生旁车(createNativeTsgoArgsrunBuild.ts:687)。nativeTsgoPassthroughArgs 会先剥掉 --diagnostics/--extendedDiagnostics(这些是 ttsc 自己的计时 flag,单独经 --diagnostics 转发给声明了 diagnosticsTiming 能力的宿主)。编码成单个 token,让旁车的未知 flag 过滤器保持它完整。

不变量与失败模式

检查失败信息位置
多个 owner 二进制争一个 passmultiple ... cannot share one ... passassertSharedHostCompatibility:45
spawn 旁车失败ttsc.transform: failed to spawn / ttsc.check: / ttsc.build:各 spawn 点
transform 信封形状错did not return a TypeScript source mapparseNativeTransformOutput:347
transform 无输出native transform host returned no outputparseNativeTransformOutput:362

维护者提示

  • check 失败短路是契约:check 阶段任一插件非零退出就停,不 emit。改派发顺序前确认这点。
  • nativePluginEnv 在构建与转换两路径镜像,改环境握手要两处同步改。
  • 线程 flag(--singleThreaded/--checkers)只转发给声明 threadingArgs 能力的 check 宿主(createNativeCheckThreadingArgsrunBuild.ts:618),别无条件转发——会让旧第三方宿主退出 2(#113 的教训)。

接下来