DEBUG.WIKI
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
OnlineSIM:在线接收短信,虚拟号码租用
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →

TypeScript 原生编译后导出函数变成数字,检查循环里的同名局部变量

原生 TypeScript 的特定 CommonJS 转换可能把循环局部同名变量写回模块导出,导致函数执行后导出被数字覆盖。以实际产物和作用域定位。

CommonJS 包刚加载时导出的函数正常,执行另一个函数后,该导出却变成数字;调用方随后报 is not a function。若项目刚切换到 TypeScript 原生编译器,先打开生成的 JavaScript,看循环内部有没有多出来的 exports.函数名 = 局部变量。

已确认的一条回归发生在原生编译器主分支提交 d61a7d23590,相同输入经 TypeScript 6.0.3 的 JavaScript编译器生成正常。不能仅凭“用了TypeScript 7”就断言所有安装版本都受影响;需结合实际编译器版本、产物与模块格式判断。

同名变量本来属于两个作用域

下面是触发结构,函数和变量名均为示例:

export function renderNumbers(values: number[]) {
  for (const value of values) {
    const mount = value;
    console.log(mount);
  }
}

function mount() {}
export { mount };

renderNumbers() 中的 mount 是函数内部、循环块内的局部常量。模块导出的 mount 是另一个函数,正常编译不能因为名字相同就把两者连起来。

受影响的 CommonJS 转换会在循环内生成额外赋值:

function renderNumbers(values) {
  for (const value of values) {
    const mount = value;
    exports.mount = mount; // 不应由这处局部变量产生
    console.log(mount);
  }
}

于是函数执行前,exports.mount 仍指向函数;执行循环后,它被循环里的数值覆盖。定位时要看包的实际输出文件,而不只是编辑器里的类型。类型检查通过,也不能保证这一编译回归没有改变运行时导出。

先用局部改名阻止错误关联

暂时不能换编译器时,可以在确认不改变业务含义的前提下,改掉循环局部变量与模块导出重复的名字:

export function renderNumbers(values: number[]) {
  for (const value of values) {
    const displayedNumber = value;
    console.log(displayedNumber);
  }
}

function mount() {}
export { mount };

重新生成后,检查循环中不再有对 exports.mount 的赋值,再从 CommonJS 包入口检查导出。不要直接编辑最终 .js:下一次构建会覆盖,发布包也可能来自另一个输出目录。

这条回归涉及函数内部的 for、for...in、for...of 处理,不应据此删除模块顶层所有 exports 赋值。模块顶层通过 var 声明并导出的变量,某些赋值原本就需要保留;修复的边界是正确识别作用域。

编译器替换要连同产物检查

2026年10月7日合入的修复将这些函数内循环的处理切回普通遍历,并增加不同循环形式与顶层导出的对照检查。合并说明了源码修正方向,不能直接推出某个已安装正式版或nightly一定包含它。

记录原构建脚本调用的可执行文件、锁文件与准确版本。若临时恢复6.0.3编译产物,应先确认项目没有依赖后续版本才支持的语法和检查行为;不要只在本机全局安装旧编译器,CI仍执行锁定的新版本。切到ESM输出也会改变消费方式,不适合作为未经兼容评估的快速开关。

验收要经过包的实际 CommonJS 入口:记录函数执行前后的导出类型,调用一次会进入循环的路径,确认原导出仍是同一个函数,再检查下游正常调用。撤回时恢复编译器依赖、锁文件及整份对应产物,避免发布源码、声明和JavaScript来自不同构建。