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

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

相关推荐
Pedantic28 分钟前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘44 分钟前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆1 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师2 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆2 小时前
VSCode自动格式化三要素
前端
爱勇宝3 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen3 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user20585561518136 小时前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端
LiaCode6 小时前
Redis 在生产项目的使用
前端·后端