esbuild 压缩 ||= 后变量声明消失,运行时报 ReferenceError
逻辑赋值需要降级、局部变量随后不再读取时,旧 esbuild 压缩可能删掉声明却留下赋值。0.28.2 已修复引用计数,不能仅提高 target 放弃旧环境支持。
源码有 let x,产物中声明却没了,只剩 void 0 || (x = {}) 一类表达式,运行时抛 ReferenceError。若 esbuild 同时做语法压缩和逻辑赋值降级,应检查这两个阶段对局部变量的引用判断。
esbuild 0.25.12、0.28.1 在相应条件下会生成这种错误代码,正式 0.28.2 已修复。需要同时保留“目标要求转换 ||=”“进行相应压缩”“局部变量在该表达式之后不再被读取”等条件,不能把所有 ReferenceError 都归到这里。
降级多出一次使用,压缩却没有算进去
关键形态如下,consume 是调用方提供的函数:
export function buildOptions(consume) {
let options;
consume(options ||= {});
}
目标不支持逻辑赋值时,options ||= {} 需要变成读取 options、必要时再赋值的结构。旧转换复制了左边的标识符,却没有正确增加引用计数。后续压缩误以为只用了一次,可以把初始化值内联到读取处并删掉声明,赋值一侧却仍留下。
最终名字即使被压成其他字母,问题也一样:它已经没有局部声明。ESM 严格模式会抛异常。先比较源码与实际加载的产物,沿堆栈定位第一次未声明赋值;不要给 window 或 globalThis 添一个同名变量,那会改变本该局部隔离的状态。
保留兼容目标,采用正式修复
使用兼容的 esbuild 0.28.2,并核对框架或二次压缩步骤实际调用的版本。依赖发布包本身已含 ||= 时,应用打包可能再次对它降级;只看业务源码有没有这个运算符,可能漏掉真正输入。
暂不能升级时,可以在受控分支中停用这一步语法压缩,保留原目标重新生成。JavaScript API 的 minifySyntax 控制语法压缩;若同时设置了总开关 minify,也要检查最终有效配置。最保守的隔离是关闭该步骤所有压缩细项,代价是产物体积增加,并重新检查是否还有其他压缩器继续处理。
把 target 提高到不再转换 ||= 的环境,也可能避开这一条错误,但它会改变浏览器兼容要求。仍需支持旧环境的项目不能为了构建通过直接采用更高目标。给变量增加一次没有业务意义的读取也只是扰动优化,不适合作为长期修复。
验收经过触发该表达式的实际调用
检查局部声明与赋值仍相互对应,再执行应用原先会走到的逻辑,确认回调收到正确对象,后续流程没有静默中断。对依赖内部路径,应覆盖真正触发它的功能,不能只验证包可以 import 或页面首页能打开。
同一源码还应保留需要降级的目标配置回归。构建退出成功或换到现代环境后不再报错,都不能单独证明旧目标下的生成代码已经正确。


