JS 压缩后线上报错,通常不是业务代码逻辑本身出了问题,而是压缩工具在默认配置下做了过度优化。最典型的情况包括:删除了被判定为“永远为假”的条件分支、改写了 eval 或 new Function 中引用的变量名、删除了看似未使用的函数参数,以及把非 ASCII 字符转义成超长序列。解决方法不是回退压缩,而是调整 compress、mangle 和 output 中的几个关键选项,并配合完整回归测试。
为什么压缩后线上会报错
生产环境的 JS 通常经过压缩,报错信息会集中在压缩后的 bundle 文件里,可读性很差。开启 sourcemap 后可以定位到具体位置,常见规律是:报错集中在条件判断代码或使用了 ES6+ 新语法的位置。这类问题的根源往往不是语法不被支持,而是压缩器在压缩阶段主动删除或改写了代码,导致运行时行为与源码不一致。
压缩工具默认配置中包含多项优化,例如 drop_debugger、compress 下的多种代码消除逻辑。这些优化对普通代码通常安全,但对历史较久、包含大量兼容判断或动态特性的老项目,可能直接破坏功能。
四类典型压缩副作用
条件分支被直接删除
压缩器会分析上下文,把某些条件判断判定为“永远为真”或“永远为假”,然后删除整个分支。例如:
压缩器可能认为 `window` 一定存在,于是把整个 `if` 块删除。但在某些 WebView 环境中,`window` 的某些属性确实访问不到,代码因此崩溃。
### 变量名改写与 eval 冲突
`mangle` 会把函数内变量名改短,例如把 `userName` 改成 `a`,把 `orderList` 改成 `b`。普通代码没有问题,但如果代码里使用了 `eval` 或 `new Function`,并且内部引用了外部变量名,压缩后变量名对不上,就会抛出 `ReferenceError`。
### 未使用参数被删除导致回调错位
`compress` 中的 `unused` 选项会删除“定义了但没用到”的参数。某些回调函数依赖固定签名,例如第三个参数才是有用的,前两个只是占位。压缩器删除前两个参数后,回调参数错位,调用时拿到 `undefined`,逻辑随之混乱。
### 非 ASCII 字符被转义
`output` 中的 `ascii_only` 如果设为 `true`,中文或 emoji 字符串会被转成 `\uXXXX` 转义序列。功能上等价,但字符串长度会显著增加,部分老浏览器对超长字符串解析可能出问题。
## 分步操作:修改压缩配置
以下步骤适用于基于常见 JS 压缩工具的配置调整,具体字段名以你项目实际使用的工具为准。
1. 关闭条件分支消除。在 `compress` 中设置 `conditionals: false` 和 `dead_code: false`。这可以阻止压缩器自作聪明删除代码分支。压缩率会略降,但兼容性判断得以保留。
2. 处理 eval 与 new Function。在 `mangle` 中设置 `eval: true`。含义是:当代码中检测到 `eval` 或 `new Function` 时,不去改写相关变量名,或保留这些函数内的变量不缩短。压缩效果会打折扣,但避免运行时引用错误。
3. 关闭未使用参数删除。在 `compress` 中设置 `unused: false`。防止回调函数因参数被删而错位。
4. 调整 ASCII 输出。在 `output` 中设置 `ascii_only: false`。如果代码包含中文或 emoji 字符串,避免它们被转义成超长序列。
5. 跑完整回归测试。修改配置后,重点测试路由懒加载、动态 `import`、依赖回调签名的模块,以及所有涉及兼容判断的分支。
## 适用与不适用场景
这套“少管闲事”的配置适合以下情况:项目历史较久、包含大量兼容性判断;代码中使用了 `eval` 或 `new Function`;回调函数依赖固定参数签名;字符串中包含中文或 emoji。
如果项目代码较新、没有动态特性、也没有兼容性分支,默认压缩配置通常可以正常工作,不必刻意关闭这些优化。压缩率与稳定性需要权衡:关闭部分优化后,打包文件体积可能增加,但线上错误率会明显下降。体积增加带来的流量成本,通常远低于线上报错带来的排查和修复成本。
## 常见问题
**关闭这些压缩选项后,文件会变大多少?**\n具体增幅取决于代码中条件分支、未使用参数和非 ASCII 字符串的数量,不同项目差异较大。需要以实际打包结果为准。
**为什么开了 sourcemap 还是很难定位?**\nsourcemap 能把压缩后的位置映射回源码,但如果报错由压缩器删除代码引起,映射位置可能指向被删除分支的附近,仍需结合压缩配置判断。
**eval 和 new Function 在项目中很少见,还需要设置 mangle.eval 吗?**\n如果确认代码中完全没有动态执行字符串的逻辑,可以不设置。但只要存在一处,压缩后就可能报 `ReferenceError`,建议保留该设置。
**ascii_only 设为 false 会不会导致兼容问题?**\n该设置只是保留原始字符,不改变字符串语义。部分老浏览器对超长转义序列解析较慢,保留原始字符通常更稳妥。
**改完配置后还需要注意什么?**\n必须跑完整回归测试,尤其是路由懒加载和动态 `import` 相关路径。压缩配置的改动影响的是整个 bundle,不能只验证单个模块。