一次真实的打包分析,揭开"按需引入"到底骗了我们多久
前端团队最爱说"我们用的是 lodash-es,按需引入,tree shaking 肯定没问题",但项目打包结果却往往让人失望。
我自己的项目里,明明代码只用到了 debounce
和 isEqual
,打包后 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
对象连同 subtract
和 multiply
函数都会被打包进 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 状态?
- 使用
webpack --json > stats.json
生成详细构建信息 - 用
webpack-bundle-analyzer stats.json
可视化依赖树 - 关注大体积模块,特别是常用工具库如 lodash、moment 等
- 用
source-map-explorer dist/main.js
查看源码映射和各模块占比 - 比较按需引入前后的体积变化
实用 checklist
- 确认 babel 配置中
"modules": false
,让构建工具做 tree shaking - 检查依赖包
package.json
中sideEffects
配置,保证无副作用 - 尽量避免 default export 导出大对象,使用命名导出
- 动态 import 要尽量静态化,或者用 webpack magic comments 限定范围
- 使用按需加载插件(vite 插件、babel-plugin-import、unplugin-vue-components)
- 持续用 bundle analyzer 监控打包产物,发现异常及时调整
Done√