Tree Shaking 为什么没生效?按需引入 lodash 还是打包了整个库?
我用 Webpack + Babel 开发 React 项目,想按需引入 lodash 的 debounce 方法,但打包后发现整个 lodash 库都被打进去了,体积一点没减。
我试过直接 import { debounce } from ‘lodash’,也试过装 babel-plugin-lodash 插件,但都没用。是不是我的配置哪里有问题?
这是我的 .babelrc 配置:
{
"plugins": ["lodash"]
}
还有 webpack 里 mode 是 production,应该默认开启 Tree Shaking 了吧?为啥还是不行?
先解释下为什么:Tree Shaking 的前提是你用的是 ES Module 的静态导入语法(比如
import { debounce } from 'lodash'),而且被导入的模块本身也要导出成 ES Module 的形式(也就是每个函数要单独 export 出来),Webpack 才能通过静态分析把没用到的代码“摇掉”。lodash 官方从 v4.17.0 开始提供了 ES Module 的构建文件(路径是lodash-es),但默认的lodash包本身是 CommonJS 格式的,里面是module.exports = require('./debounce')这种动态依赖,Webpack 根本没法做静态分析——所以你写import { debounce } from 'lodash',Webpack 只能认命把整个 lodash 打进去。你试了
babel-plugin-lodash,这个插件确实能帮你在 Babel 编译阶段把import { debounce } from 'lodash'转成import debounce from 'lodash/debounce',但它有个前提:你必须用的是 ES Module 的入口,也就是lodash-es,或者你得让 Webpack 能正确 resolve 到 lodash 的 ES Module 版本。但你现在的配置没做这些,所以插件也白搭。解决方案分三步走:
第一步:换用 lodash-es 包,它是 lodash 的 ES Module 版本,每个函数都单独 export,Tree Shaking 才有戏。先卸载旧的 lodash,装新的:
第二步:继续用你原来的
import { debounce } from 'lodash'这种写法,但改成指向lodash-es。这里有个坑:你不能直接把 import 里的lodash换成lodash-es就完事,因为有些第三方库可能依赖lodash的 CommonJS 版本,所以更稳妥的做法是配置 Webpack 的resolve.alias,把lodash的引用强制指向lodash-es:这样你代码里所有
import { debounce } from 'lodash'都会被 Webpack 指向到 lodash-es,而 lodash-es 是 ES Module 的,Tree Shaking 就能生效了。第三步:确认 Babel 配置和 Webpack 的优化配置都对。你现在的
.babelrc只写了["lodash"],这个插件其实可以不加,因为你用了 alias + lodash-es 后,Tree Shaking 能靠静态分析自己搞定。但如果你坚持要用这个插件(比如你项目里有很多嵌套调用_.debounce的地方),那要加个配置让它也支持 lodash-es:另外检查下 Webpack 的
mode是production,它默认会开启optimization.usedExports = true(标记副作用),配合 lodash-es 的 ES Module 导出,Tree Shaking 就能工作了。但如果你项目里用了sideEffects: false,记得在package.json里加上"sideEffects": false,或者确保 lodash 不在副作用列表里(lodash 本身没副作用,可以放心关掉)。最后验证一下:打包完看看
dist/lazy.js或main.js里有没有 lodash 的完整代码。可以用webpack-bundle-analyzer插件看包的组成,或者直接在打包后的文件里搜debounce,如果只看到一个函数的代码,没看到throttle、debounce、cloneDeep这些没用到的函数,就说明成功了。补充个常见坑:如果你用了
import _ from 'lodash'这种默认导入,Tree Shaking 就失效了——因为这样会把整个模块默认导出整个对象,Webpack 不知道你用没用内部函数。一定要用命名导入:import { debounce } from 'lodash'。我之前也踩过这坑,以为写了按需引入就完事了,结果打包体积没变,最后发现是 CommonJS 和 ES Module 混着用导致的。lodash-es 就是专门干这事的,记住用它就对了。
babel-plugin-lodash的使用方式上——它只对import _ from 'lodash'这种默认导入生效,你用的是import { debounce } from 'lodash',插件根本没触发。改成这样就搞定:
或者用
lodash-es+ Webpack 自带 Tree Shaking: