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

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

相关推荐
OEC小胖胖4 小时前
去中心化身份:2025年Web3身份验证系统开发实践
前端·web3·去中心化·区块链
Cacciatore->5 小时前
Electron 快速上手
javascript·arcgis·electron
vvilkim5 小时前
Electron 进程间通信(IPC)深度优化指南
前端·javascript·electron
某公司摸鱼前端6 小时前
ES13(ES2022)新特性整理
javascript·ecmascript·es13
ai小鬼头7 小时前
百度秒搭发布:无代码编程如何让普通人轻松打造AI应用?
前端·后端·github
漂流瓶jz7 小时前
清除浮动/避开margin折叠:前端CSS中BFC的特点与限制
前端·css·面试
前端 贾公子7 小时前
在移动端使用 Tailwind CSS (uniapp)
前端·uni-app
散步去海边7 小时前
Cursor 进阶使用教程
前端·ai编程·cursor
清幽竹客7 小时前
vue-30(理解 Nuxt.js 目录结构)
前端·javascript·vue.js
weiweiweb8887 小时前
cesium加载Draco几何压缩数据
前端·javascript·vue.js