要让老旧JS项目支持Tree Shaking,核心是消除全局变量污染、改用ES模块规范、确保无副作用的导出/导入形式;需重构IIFE为命名导出、替换CommonJS为import/export、声明sideEffects: false,并验证未使用代码是否被剔除。

要让老旧 JS 项目支持 Tree Shaking,核心不是“启用”某个开关,而是**消除全局变量污染、改用 ES 模块规范、确保无副作用的导出/导入形式**。Webpack 或 Vite 等打包工具本身支持 Tree Shaking,但前提是代码满足前提条件——否则即使配置正确,也不会真正“摇掉”未使用代码。
1. 消除 IIFE + 全局挂载(最常见污染源)
老项目常把模块包在立即执行函数里,并手动挂到 window 或 global 上,比如:
(function (global) {
function utils() { /* ... */ }
function ajax() { /* ... */ }
global.utils = utils;
global.ajax = ajax;
})(typeof window !== 'undefined' ? window : global);
重构做法:
- 把每个逻辑单元拆成独立文件,统一用
export导出(命名导出优先) - 删除所有
window.xxx = xxx或this.xxx = xxx类型赋值 - 如有依赖顺序要求(如工具库被其他模块依赖),用
import显式声明,而非靠执行顺序隐式依赖
2. 替换 CommonJS(require/module.exports)为 ES Module
Webpack 5+ 对 CommonJS 的 Tree Shaking 支持非常有限(尤其动态 require、module.exports = {...} 等),必须转为 export/import。
注意点:
- 避免
export default function() {}后再加export const xxx = ...混用(部分旧 loader 可能处理异常);推荐统一用命名导出:export function utils() {} - 不要在导出前修改导出对象(如
const obj = {}; obj.a = 1; export { obj }),这会被视为有副作用 - 第三方 CJS 包可通过
imports-loader或resolve.alias+ ESM 封装层桥接,但优先升级为 ESM 版本(如 lodash-es)
3. 标记无副作用(sideEffects: false)并验证
在项目根目录 package.json 中声明:
"sideEffects": false
表示所有模块默认无副作用(即仅导出、不执行逻辑)。如果某些文件确实有副作用(如 polyfill、CSS 导入、全局样式注入),需显式列出:
"sideEffects": ["*.css", "src/polyfills.js"]
验证是否生效:
- 构建后查看生成的 bundle(如用
webpack-bundle-analyzer),搜索未引用的导出名是否还存在 - 在代码中写一个只导入但不使用的函数:
import { unusedHelper } from './utils';,检查最终打包结果里是否还有该函数体 - 开启 Webpack 的
optimization.usedExports: true(默认开启),配合mode: 'production'触发标记检测
4. 处理遗留的 script 标签与内联脚本
HTML 中直接写的 <script src="xxx.js"></script> 或内联 <script>xxx</script> 会绕过模块系统,导致无法分析依赖、无法 Tree Shaking。
改造方式:
- 将这些脚本内容迁入 ES 模块文件,通过入口
main.js统一import - 若必须保留 HTML 内联(如 CMS 插入的统计代码),将其包裹为自调用函数且不污染全局,或改用动态
import()延迟加载 - 移除所有
<script type="text/javascript"></script>中的全局变量定义,改由模块化方式提供
重构本质是“让静态分析可推导依赖关系”。没有魔法配置,只有清理全局状态、回归模块契约、保持导出纯净。做完上述几步,Tree Shaking 就会自然生效。











