要真正精通 JavaScript 中的 Tree Shaking,关键在于理解其生效条件并系统排查链路:依赖 ES 模块静态可解析性,禁用 CommonJS,优先选用 ESM 第三方库,采用命名导出,精确声明副作用,最后通过构建产物验证。

要真正精通 JavaScript 中的 Tree Shaking,关键不是记住几条规则,而是理解它“为什么只在某些条件下生效”,并能系统性地排查、验证和加固整个链路。它本质上是一场人与打包器之间的静态分析协作——你提供可分析的代码结构,工具才敢安全地删。
确保模块系统干净且全程可控
Tree Shaking 的底层依赖是 ES 模块的静态可解析性。一旦混入 CommonJS 或动态逻辑,整条链就断了。
- 所有自定义模块必须用
export/import,禁用module.exports和require(包括配置文件、工具脚本里) - 第三方库优先选 ESM 原生版本:用
lodash-es替代lodash,date-fns替代moment,nanoid替代旧版 UUID 工具 - 若必须引入 CommonJS 库(如某些遗留 SDK),不要指望 Tree Shaking 能处理它;要么找 ESM 封装版,要么用插件(如
webpack.NormalModuleReplacementPlugin)做转换,但效果不保证
导出方式决定可摇粒度
命名导出(named export)是 Tree Shaking 的黄金标准;默认导出(default export)和对象聚合导出天然阻碍精细化剔除。
- ✅ 推荐写法:
export const debounce = () => {};、export function throttle() {} - ❌ 避免写法:
export default { debounce, throttle }—— 即使只解构用了debounce,整个对象仍被标记为已使用 - ⚠️ 警惕“伪命名导出”:
const utils = { a: () => {} }; export { utils }不行,因为utils是变量,不是字面量导出;必须直接导出函数或常量
副作用声明必须精确到文件级
Webpack 不会猜你有没有副作用。它只看两件事:模块顶层是否执行了副作用语句;package.json 中的 sideEffects 字段是否允许它删。
- 把所有副作用操作(
console.log、localStorage.setItem、new Audio()、样式注入、全局 polyfill 注册)全部移到函数内部,而不是模块顶层 -
package.json中设"sideEffects": false表示全项目无副作用;如有真实副作用,必须显式列出,例如:"sideEffects": ["./src/polyfills.js", "./src/styles/index.css", "*.scss"] - CSS 文件默认视为有副作用(影响全局样式),所以即使只用了某个组件的样式,只要它在模块顶层
import './Button.css',该文件就不会被摇掉——应改由组件内部按需导入,或统一收口到入口文件
验证不能靠感觉,得靠产物反推
配置写了、代码改了,不代表它真生效。必须用工具确认哪些代码被保留、哪些被标记为未使用。
- 启用 Webpack 的详细统计:
stats: 'verbose',构建后搜索unused harmony export,看目标函数是否出现在列表中 - 用
source-map-explorer可视化 bundle,定位某段逻辑是否真的没被打进去,还是被意外保留(比如通过间接引用、import *等) - 对关键模块做“隔离测试”:新建一个极简入口,只导入并调用一个函数,观察最终 bundle 是否只含该函数及其依赖——这是检验模块是否真正可摇的金标准


















