运行时流程

本页把 ttsc 最重要的几条运行路径端到端追一遍:无插件构建、有插件构建、check/fix/format、编程式 transform、ttsx 执行、LSP。每条都标注关键源码位置,方便你对照实现。

流程一:无插件 ttsc 构建(最快路径)

要点:

  • runTtscsrc/launcher/internal/runTtsc.ts:32)按子命令分派:build/check/fix/format/clean/prepare/demo,裸 flag 或 .ts/.json 视作 build 别名(isBuildAlias)。
  • 无插件时 runBuildTimedrunBuild.ts:157)走最后一段:直接 createTsgoBuildArgs + runTsgoBuild,把 tsgo 的 TSFILE: 行解析成 emittedFiles 再从用户可见输出里剥掉(除非用户自己转发了 --listEmittedFiles)。
  • emit 前的类型检查通过 --noEmitOnError 在同一次 tsgo 调用里完成(除非转发了终止类 flag 如 --showConfig)。

流程二:有插件 ttsc 构建

要点:

  • loadProjectPluginssrc/plugin/internal/loadProjectPlugins.ts:55)做发现、组合(composes)、贡献者合并(contributors),并对每个 Go 源调 buildSourcePlugin 惰性构建。详见 加载与发现
  • 插件分两个阶段:check(先跑,失败即止)与 transform(在 emit 阶段注入),由 orderNativePlugins 排序(check 在前)。
  • check 阶段顺序执行、首个非零退出即返回(runBuild.ts::runNativeCheckPlugins)。
  • transform 阶段选一个"共享宿主"二进制,其余插件经 --plugins-jsonTTSC_LINKED_PLUGINS_JSON 在同一进程内运行(buildWithNativeCompilerPluginsselectSharedHostPlugin)。
  • 一个微妙取舍:当存在 transform 编译器时,ttsc 不再单独跑 tsgo --noEmit 守卫——它信任原生宿主的 prog.Diagnostics()。当 check 插件声明 reportsTypeScriptDiagnostics 时也跳过守卫,避免重复类型检查。

流程三:ttsc check / fix / format

这三者都走 runCompatibleBuild,但设 checkOnly = true、并分别置 fix/format 标志(runTtsc.ts:102):

  • checkemit=false,跑类型检查 + check 插件。
  • fixemit=falsefix=true,把 lint 与 format 的 autofix 都写回磁盘,再重跑类型检查 + lint。拒绝 --watch、单文件、--emit
  • formatemit=falseformat=true跑 format 类规则并写回磁盘,做类型检查。

format 模式有一个关键短路(runBuild.ts:176):lint 旁车已经重写了源文件且没报任何东西后,ttsc 不会再跑 tsgo --noEmit 或 transform 编译器——否则会把无关类型错误当成 format 失败,或在已格式化的源上叠加 transform 重写,破坏"ttsc format 只格式化"的契约。无插件时 format 模式直接返回空成功结果,而非落到会暴露类型错误的 tsgo 路径。

lint 旁车的子命令由 nativeCheckSubcommand 选:format→format,fix→fix,否则 checkrunBuild.ts:738)。

流程四:编程式 transform(TtscCompiler.transform()

transform() 是源到源 API,只返回 TypeScript 文本,绝不返回 JS/d.ts/sourcemap(src/TtscCompiler.ts:143)。原生 transform 源被期望往 stdout 写 { "typescript": { "src/file.ts": "..." } }

无插件时走 transformProjectWithNativeHosttransformProjectInMemory.ts:70),它构建 cmd/ttsc 原生编译宿主二进制(buildNativeCompiler,用整段 go.mod 内容 + ttsc 版本做缓存键)并 spawn api-transformapi-transform 在 no-emit 模式建 Program,把每个非库源文件的文本以项目相对路径为键返回(cmd/ttsc/api_transform.go:30)。

流程五:ttsx 执行

ttsxsrc/launcher/internal/runTtsx.ts:24)的核心思路:先用 prepareExecutionprepareExecution.ts:21)对入口的所属项目做一次真正的类型检查 + emit(含 typia 等 transform 插件),把产物放进 PID 隔离的临时目录,再用一个虚拟文件系统布局(软链 node_modules、硬链文件)让 node_modules 软链与 import.meta.url 仍指向源码树。

随后 spawn node registerRuntimeHooks.js <source-entry>,子进程里安装的 同步模块钩子registerRuntimeHooks.ts + runtimeHooks.ts)按源 URL 服务那份已构建的 emit;任何其他原始 .ts 依赖按需构建其所属 tsconfig.json(一次性,带跨进程构建锁)。详见 ttsx 运行时钩子

--no-plugins 是 ttsx 的关键开关:ttsc 自己的 *.config.ts 加载器用它,因为那次构建只需类型检查并运行 config 文件,加载宿主项目的 transform/check 插件既浪费又错误(runTtsx.ts:55 注释)。

流程六:LSP(ttscserver

ttscservercmd/ttscserver/main.go)解析 flag、构造 NativePluginSource(读 TTSC_LSP_PLUGINS_JSON),委托 lspserver.RunLSPServerProxyinternal/lspserver/lsp_proxy.go)是一个字节级双泵代理:editor↔upstream 两个方向各一个 goroutine,拦截它关心的消息类型(publishDiagnostics 合并、codeAction 增强、executeCommand 处理 ttsc 命令、textDocument/formatting 格式化活缓冲区)。详见 LSP 代理设计

跨流程的不变量

  • noEmit 解析:tsconfig 级 noEmit: true 被当作分析-only,除非用户显式 ttsc --emit 覆盖(runBuild.ts::applyProjectNoEmit)。
  • 退出码语义:0 成功;2 用户错误/构建失败;3 emit/manifest 失败(Go 侧);JS launcher 顶层 catch 把异常映射成 2(runTtsx 映射成 2,binary-not-found 映射成 1)。
  • 诊断分类CountErrorsdriver/program.go:161)决定哪些诊断翻退出码:lint 诊断按调用者设的 Severity,tsgo 诊断按其类别,纯文本诊断当作 error。

接下来