TypeScript 检查压缩调用链时栈溢出,堆栈重复 getEffectsSignature
allowJs 项目混入压缩 bundle 时,长逗号调用链可能令 TypeScript 控制流检查递归耗尽栈。核对真实输入范围,排除误纳入的产物,并保留导入仍能拉回文件的边界。
tsc 没有输出常规类型诊断,反而以 RangeError: Maximum call stack size exceeded 退出,堆栈反复出现 getEffectsSignature、getTypeAtFlowCall。如果项目开启了 allowJs,检查输入里是否混进压缩后的 JavaScript bundle,尤其是把大量调用连在一条逗号表达式中的产物。
这一具体形态在 TypeScript 4.9.5、5.9.3、6.0.3 中有报告。正式 6.0.3 的控制流处理也保留相关分支;这里讨论该 JavaScript 实现的编译器路径,不把原生编译器更大的运行栈视为缺陷已修。
逗号连接的调用会形成另一种回溯
下面只展示结构,真实触发输入包含很长的连续调用链:
declare const a: { f(x: number): void };
var first = late;
a.f(0), a.f(0), a.f(0);
var late = a;
前面的引用会促使检查器先追踪后面的初始化。逗号表达式里的调用被纳入控制流后,解析当前调用目标又可能回溯到前一个调用,长链最终耗尽 JavaScript 栈。直接作为表达式语句的调用有另一条避免循环分析的处理路径,不能仅凭“函数调用数量很多”判断是同一问题。
先从失败日志找出正在检查的文件,再核对实际安装的 TypeScript 与调用入口。编辑器、打包器插件和 CI 可能各自带一份编译器;根目录依赖版本不能代替崩溃进程实际加载的版本。
让构建产物离开源码项目
如果压缩 bundle 只是输出产物,本来不应参加源代码检查,应在项目配置中明确源码范围。例如源码确实全部在 src 下、产物位于 dist 时:
{
"compilerOptions": {
"allowJs": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["dist", "coverage", "node_modules"]
}
应把这些范围合并到现有配置,保留原来的严格检查、模块设置和项目引用。这里没有关闭 JavaScript 检查,也没有让整个依赖树免于类型约束。
exclude 只过滤由 include 找到的根文件。源码中的 import、files 或其他引用仍可能把被排除的文件拉回项目。若业务直接导入自己的 dist/app.min.js,需要修正这条依赖关系,改用受支持的源码、未压缩开发入口或正式提供的声明文件;不能追加一条 exclude 就认为压缩包已消失。项目确实需要检查该文件时,也不能悄悄把它排除后宣称检查完整。
输入列表可用项目实际的编译器入口加 --listFilesOnly 查看。这个输出用于确认文件范围,不执行完整类型检查,因而“列举成功”本身不证明栈溢出已恢复。
必须保留压缩输入时
优先从自己维护的构建环节取得未压缩版本。若准备把逗号链改成独立语句,只能针对返回值确实被丢弃、求值顺序和作用域均可保留的位置;例如条件里的 (log(), isReady()) 有结果用途,不能全局把逗号替成分号。不要直接重写第三方已发布 bundle 或临时扩大 Node 栈来掩盖输入问题。
复核时用原项目入口完成一次真正的类型检查,既确认不再崩溃,也确认应有的普通错误仍被报告。示例中的使用先于赋值关系不因避开栈溢出就自动变正确。采用后续编译器时,也要按实际安装版本与同一输入复查。


