你以为的 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√

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

相关推荐
雨季6664 分钟前
Flutter 三端应用实战:OpenHarmony “呼吸灯”——在焦虑时代守护每一次呼吸的数字禅修
开发语言·前端·flutter·ui·交互
切糕师学AI5 分钟前
Vue 中如何修改地址栏参数并重新加载?
前端·javascript·vue.js
软弹5 分钟前
Vue3如何融合TS
前端·javascript·vue.js
2501_920931707 小时前
React Native鸿蒙跨平台采用ScrollView的horizontal属性实现横向滚动实现特色游戏轮播和分类导航
javascript·react native·react.js·游戏·ecmascript·harmonyos
0思必得08 小时前
[Web自动化] Selenium处理动态网页
前端·爬虫·python·selenium·自动化
东东5169 小时前
智能社区管理系统的设计与实现ssm+vue
前端·javascript·vue.js·毕业设计·毕设
catino9 小时前
图片、文件的预览
前端·javascript
2501_9209317010 小时前
React Native鸿蒙跨平台实现推箱子游戏,完成玩家移动与箱子推动,当所有箱子都被推到目标位置时,玩家获胜
javascript·react native·react.js·游戏·ecmascript·harmonyos
layman052811 小时前
webpack5 css-loader:从基础到原理
前端·css·webpack
半桔11 小时前
【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典
前端·css·html