运行时流程
本页把 ttsc 最重要的几条运行路径端到端追一遍:无插件构建、有插件构建、check/fix/format、编程式 transform、ttsx 执行、LSP。每条都标注关键源码位置,方便你对照实现。
流程一:无插件 ttsc 构建(最快路径)
要点:
runTtsc(src/launcher/internal/runTtsc.ts:32)按子命令分派:build/check/fix/format/clean/prepare/demo,裸 flag 或.ts/.json视作 build 别名(isBuildAlias)。- 无插件时
runBuildTimed(runBuild.ts:157)走最后一段:直接createTsgoBuildArgs+runTsgoBuild,把 tsgo 的TSFILE:行解析成emittedFiles再从用户可见输出里剥掉(除非用户自己转发了--listEmittedFiles)。 - emit 前的类型检查通过
--noEmitOnError在同一次 tsgo 调用里完成(除非转发了终止类 flag 如--showConfig)。
流程二:有插件 ttsc 构建
要点:
loadProjectPlugins(src/plugin/internal/loadProjectPlugins.ts:55)做发现、组合(composes)、贡献者合并(contributors),并对每个 Go 源调buildSourcePlugin惰性构建。详见 加载与发现。- 插件分两个阶段:
check(先跑,失败即止)与transform(在 emit 阶段注入),由orderNativePlugins排序(check 在前)。 - check 阶段顺序执行、首个非零退出即返回(
runBuild.ts::runNativeCheckPlugins)。 - transform 阶段选一个"共享宿主"二进制,其余插件经
--plugins-json与TTSC_LINKED_PLUGINS_JSON在同一进程内运行(buildWithNativeCompilerPlugins→selectSharedHostPlugin)。 - 一个微妙取舍:当存在 transform 编译器时,ttsc 不再单独跑 tsgo
--noEmit守卫——它信任原生宿主的prog.Diagnostics()。当 check 插件声明reportsTypeScriptDiagnostics时也跳过守卫,避免重复类型检查。
流程三:ttsc check / fix / format
这三者都走 runCompatibleBuild,但设 checkOnly = true、并分别置 fix/format 标志(runTtsc.ts:102):
- check:
emit=false,跑类型检查 + check 插件。 - fix:
emit=false、fix=true,把 lint 与 format 的 autofix 都写回磁盘,再重跑类型检查 + lint。拒绝--watch、单文件、--emit。 - format:
emit=false、format=true,只跑 format 类规则并写回磁盘,不做类型检查。
format 模式有一个关键短路(runBuild.ts:176):lint 旁车已经重写了源文件且没报任何东西后,ttsc 不会再跑 tsgo --noEmit 或 transform 编译器——否则会把无关类型错误当成 format 失败,或在已格式化的源上叠加 transform 重写,破坏"ttsc format 只格式化"的契约。无插件时 format 模式直接返回空成功结果,而非落到会暴露类型错误的 tsgo 路径。
lint 旁车的子命令由 nativeCheckSubcommand 选:format→format,fix→fix,否则 check(runBuild.ts:738)。
流程四:编程式 transform(TtscCompiler.transform())
transform() 是源到源 API,只返回 TypeScript 文本,绝不返回 JS/d.ts/sourcemap(src/TtscCompiler.ts:143)。原生 transform 源被期望往 stdout 写 { "typescript": { "src/file.ts": "..." } }。
无插件时走 transformProjectWithNativeHost(transformProjectInMemory.ts:70),它构建 cmd/ttsc 原生编译宿主二进制(buildNativeCompiler,用整段 go.mod 内容 + ttsc 版本做缓存键)并 spawn api-transform。api-transform 在 no-emit 模式建 Program,把每个非库源文件的文本以项目相对路径为键返回(cmd/ttsc/api_transform.go:30)。
流程五:ttsx 执行
ttsx(src/launcher/internal/runTtsx.ts:24)的核心思路:先用 prepareExecution(prepareExecution.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)
ttscserver(cmd/ttscserver/main.go)解析 flag、构造 NativePluginSource(读 TTSC_LSP_PLUGINS_JSON),委托 lspserver.RunLSPServer。Proxy(internal/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)。 - 诊断分类:
CountErrors(driver/program.go:161)决定哪些诊断翻退出码:lint 诊断按调用者设的 Severity,tsgo 诊断按其类别,纯文本诊断当作 error。