在使用Webpack、Vite等前端构建工具进行生产环境打包时,默认会启用Terser进行代码压缩混淆。当项目运行在模块模式下,Terser可能会误判未被模块内部直接引用的全局调用函数为无用代码并移除,导致运行时出现函数未定义的错误。

问题产生的原因
Terser的默认压缩逻辑会分析代码的引用关系,对于没有被其他代码显式引用的函数,会被标记为无用代码(dead code)并移除。在模块模式下,全局函数如果没有被模块内部的导入导出链路关联,即使是在全局作用域下调用,也可能被判定为无引用而被删除。
比如下面这段在模块中定义的全局函数,在默认Terser配置下就可能被移除:
// 模块内定义的全局函数
function globalHelper() {
console.log('全局辅助函数执行');
}
// 全局作用域调用
window.globalHelper = globalHelper;
globalHelper();
解决策略
1. 调整Terser的压缩配置
可以在构建工具的Terser配置中关闭unused选项,或者配置side_effects保留相关代码。以Webpack为例,在optimization.minimizer中修改Terser配置:
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
// 关闭无用代码移除
unused: false,
// 保留有副作用的代码
side_effects: true
}
}
})
]
}
};
这种方式会全局关闭无用代码移除,适合项目规模较小、不需要精细控制的场景,但可能会增加打包体积。
2. 为函数添加保留标记
Terser支持通过注释标记保留特定代码,在需要保留的全局函数前添加/*#__PURE__*/的反向标记,或者添加@preserve注释:
// 添加保留注释,告诉Terser不要移除该函数
/*#__NO_SIDE_EFFECTS__*/
function globalHelper() {
console.log('全局辅助函数执行');
}
window.globalHelper = globalHelper;
globalHelper();
也可以直接在函数调用处添加标记,明确告知Terser该调用有副作用:
function globalHelper() {
console.log('全局辅助函数执行');
}
// 标记该调用有副作用,不会被移除
window.globalHelper = globalHelper;
/*#__PURE__*/ globalHelper();
3. 修改函数定义方式
将全局函数定义为window对象的属性,而不是独立的函数声明,这样Terser会认为该函数被window对象引用,不会被判定为无用代码:
// 直接定义为window的属性
window.globalHelper = function() {
console.log('全局辅助函数执行');
};
// 调用时通过window访问
window.globalHelper();
4. 配置Terser的全局变量保留规则
在Terser配置中通过globals选项声明需要保留的全局变量,避免被压缩移除:
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
// 声明需要保留的全局变量
global_defs: {
globalHelper: true
}
}
}
})
]
}
};
策略选择建议
如果项目只有少量全局函数需要保留,优先选择添加保留标记的方式,对打包体积影响最小;如果全局函数较多且分散,可调整Terser的压缩配置;如果是老旧项目迁移到模块模式,修改函数定义方式适配成本更低。修改配置后建议本地打包后检查压缩后的代码,确认全局函数没有被移除,再发布到生产环境。