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

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

相关推荐
GISer_Jing21 分钟前
50道JavaScript基础面试题:从基础到进阶
开发语言·javascript·ecmascript
爷_6 小时前
字节跳动震撼开源Coze平台!手把手教你本地搭建AI智能体开发环境
前端·人工智能·后端
charlee447 小时前
行业思考:不是前端不行,是只会前端不行
前端·ai
Amodoro8 小时前
nuxt更改页面渲染的html,去除自定义属性、
前端·html·nuxt3·nuxt2·nuxtjs
Wcowin8 小时前
Mkdocs相关插件推荐(原创+合作)
前端·mkdocs
伍哥的传说9 小时前
CSS+JavaScript 禁用浏览器复制功能的几种方法
前端·javascript·css·vue.js·vue·css3·禁用浏览器复制
lichenyang4539 小时前
Axios封装以及添加拦截器
前端·javascript·react.js·typescript
Trust yourself2439 小时前
想把一个easyui的表格<th>改成下拉怎么做
前端·深度学习·easyui
三口吃掉你9 小时前
Web服务器(Tomcat、项目部署)
服务器·前端·tomcat
Trust yourself24310 小时前
在easyui中如何设置自带的弹窗,有输入框
前端·javascript·easyui