你以为的 tree shaking,其实根本没生效

一次真实的打包分析,揭开"按需引入"到底骗了我们多久

前端团队最爱说"我们用的是 lodash-es,按需引入,tree shaking 肯定没问题",但项目打包结果却往往让人失望。

我自己的项目里,明明代码只用到了 debounceisEqual,打包后 lodash-es 却依然占了 60+ KB!这到底怎么回事?

先看个简单的示例,演示 tree shaking 失败的几种典型情况。


tree shaking 失效场景

1. Default export 导出对象,无法被 shake

js 复制代码
// utils.js
export default {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b
}

主文件只用到 add

js 复制代码
import utils from './utils.js'

console.log(utils.add(1, 2))

打包结果 :整个 utils 对象连同 subtractmultiply 函数都会被打包进 bundle。


2. 正确写法 --- 使用命名导出才能有效 shake 掉未用代码

js 复制代码
// utils.js
export const add = (a, b) => a + b
export const subtract = (a, b) => a - b
export const multiply = (a, b) => a * b

调用端:

js 复制代码
import { add } from './utils.js'

console.log(add(1, 2))

这时构建工具可以静态分析,只把 add 编译进去,未用的代码被摇掉。


3. Babel 默认转成 CommonJS,tree shaking 失效

项目 .babelrc 配置:

json 复制代码
{
  "presets": [
    ["@babel/preset-env", {
      "modules": "commonjs"
    }]
  ]
}

把 ES Module 的 import/export 全部转成了 require,这让 webpack 无法静态分析依赖,导致没有任何代码被摇掉


4. 修复方法:设置 babel 不转 module

json 复制代码
{
  "presets": [
    ["@babel/preset-env", {
      "modules": false
    }]
  ]
}

保持 ES Module 语法,交由 webpack/rollup 做 tree shaking。


5. sideEffects 标记不正确,导致整个模块保留

  • package.json 里写:
json 复制代码
{
  "sideEffects": false
}

告诉 webpack 这个包没副作用,能安全摇树。

  • 但如果代码引入了 CSS、polyfill 这类副作用,必须把对应文件标记出来:
json 复制代码
{
  "sideEffects": ["*.css"]
}

否则 webpack 会保留整个包,不摇树。


6. 动态 import 路径,静态分析无效

js 复制代码
const moduleName = 'foo'
import(`./modules/${moduleName}.js`).then(m => {
  m.doSomething()
})

webpack 无法确定要打包哪个文件,只能保守地打包 ./modules 文件夹所有内容。


tree shaking 效果对比

情况 打包大小 备注
default export + babel 转 commonjs 120 KB 未摇掉,函数全打包
命名导出 + babel modules:false + sideEffects:false 30 KB 只有用到的代码被打包
动态 import 路径未限制 150 KB 相关目录全部打包
sheel 复制代码
┌──────────────────────────────┐
│       bundle-analysis         │
│ ┌─────────────┐  ┌─────────┐ │
│ │ lodash-es   │  │ utils.js│ │
│ │ 60 KB       │  │ 20 KB   │ │
│ └─────────────┘  └─────────┘ │
│ ...                         │
└──────────────────────────────┘

如何排查自己项目的 tree shaking 状态?

  1. 使用 webpack --json > stats.json 生成详细构建信息
  2. webpack-bundle-analyzer stats.json 可视化依赖树
  3. 关注大体积模块,特别是常用工具库如 lodash、moment 等
  4. source-map-explorer dist/main.js 查看源码映射和各模块占比
  5. 比较按需引入前后的体积变化

实用 checklist

  • 确认 babel 配置中 "modules": false,让构建工具做 tree shaking
  • 检查依赖包 package.jsonsideEffects 配置,保证无副作用
  • 尽量避免 default export 导出大对象,使用命名导出
  • 动态 import 要尽量静态化,或者用 webpack magic comments 限定范围
  • 使用按需加载插件(vite 插件、babel-plugin-import、unplugin-vue-components)
  • 持续用 bundle analyzer 监控打包产物,发现异常及时调整

Done√

📌 你可以继续看我的系列文章

相关推荐
Novlan16 分钟前
@tdesign/uniapp 图标瘦身
前端
ManThink Technology10 分钟前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
铅笔侠_小龙虾23 分钟前
Flutter 实战: 计算器
开发语言·javascript·flutter
大模型玩家七七42 分钟前
梯度累积真的省显存吗?它换走的是什么成本
java·javascript·数据库·人工智能·深度学习
. . . . .1 小时前
shadcn组件库
前端
2501_944711431 小时前
JS 对象遍历全解析
开发语言·前端·javascript
发现一只大呆瓜2 小时前
虚拟列表:支持“向上加载”的历史消息(Vue 3 & React 双版本)
前端·javascript·面试
css趣多多2 小时前
ctx 上下文对象控制新增 / 编辑表单显示隐藏的逻辑
前端
阔皮大师2 小时前
INote轻量文本编辑器
java·javascript·python·c#
lbb 小魔仙2 小时前
【HarmonyOS实战】React Native 表单实战:自定义 useReactHookForm 高性能验证
javascript·react native·react.js