为什么我的Webpack配置了Tree Shaking还是没摇掉未使用的代码?

程序员雨诺 阅读 17

我在项目里按教程配置了Webpack的Tree Shaking,把mode设成production,也设置了optimization.usedExports为true,但打包后的bundle里还是能看到没用的lodash函数。比如在某个组件里只用了_.get,但打包结果里包含了整个lodash。我甚至在package.json加了”sideEffects”: false也没用,这是哪里出问题了?

尝试过用terserPlugin压缩,但控制台警告说某些代码无法删除。检查代码时发现有些第三方库是通过CommonJS引入的,比如:

const _ = require('lodash');  
const get = _.get;

是不是CommonJS语法导致Tree Shaking失效?或者需要额外配置babel?

另外,如果依赖的库没有提供ES模块版本,该怎么处理才能让Tree Shaking生效?

我来解答 赞 3 收藏
二维码
手机扫码查看
2 条解答
闲人俊熙
这个问题的关键是Tree Shaking只对ES6模块(ESM)有效,而CommonJS模块(require/module.exports)在静态分析阶段无法被完全解析依赖关系。你提到的const _ = require('lodash')正是罪魁祸首。Webpack只能在编译时做静态代码分析来标记未使用的导出,但CommonJS是运行时加载,根本没法确定哪些函数真正被用了。

更具体点说,当你这样写:

const _ = require('lodash');
const value = _.get(obj, 'path');


Webpack看到的是“我引入了一个叫 lodash 的包”,但它不知道你只用了 get 方法。整个 lodash 被当作一个整体导入,即使你只用了一个方法,打包时也会把整个库包含进去——这不是Webpack的问题,而是模块系统的限制。

解决办法分三步走:

第一步:改用ESM语法 + 按需引入
不要直接引入整个lodash,换成 import { get } from 'lodash-es'。注意这里要用 lodash-es 而不是 lodash,因为官方lodash主版本虽然提供了ESM,但在某些构建环境下仍然会回退到CommonJS行为。lodash-es 是专门为ES构建设计的版本,确保输出的是真正的ES模块。

// 改成这样
import { get } from 'lodash-es';

// 而不是
// const _ = require('lodash');
// 或 import _ from 'lodash';


第二步:确认你的babel配置不会把ESM转成CommonJS
很多人踩坑在这里。如果你用了Babel,并且.babelrc或者babel.config.js里有@babel/preset-env,默认情况下它会把ES模块转换成CommonJS,除非你明确告诉它不要。

检查你的babel配置:

{
"presets": [
["@babel/preset-env", {
"modules": false // 关键!必须设为false,否则ESM会被转成CommonJS
}]
]
}


这个modules: false的作用就是保留import/export语法原样不动,交给Webpack去做模块处理。如果没关掉,你代码里写的import最后变成require,Tree Shaking直接失效。

第三步:确保第三方库支持sideEffects标注并且正确使用
你在package.json加了"sideEffects": false是对的,但这表示整个项目没有副作用,适用于你自己写的代码。对于node_modules里的库,它们自己得声明sideEffects。比如一些组件库或工具函数库会在自己的package.json里写:

{
"sideEffects": [
"./dist/style.css",
"./some-polyfill.js"
]
}


这样Webpack就知道除了这些文件外其他都可以摇。但如果库本身没提供ESM版本或者没有正确标注,你就没法摇。

那如果依赖的库压根不提供ES模块版本怎么办?

有两个方案:

方案一:用webpack的module.rules手动把某些CommonJS转成ESM
不太现实,因为这需要重写AST,复杂度高,容易出错。

方案二:推荐做法——换库或使用插件
例如针对lodash,你可以直接上 lodash-webpack-plugin 配合 babel-plugin-lodash 做自动按需引入。

先装两个包:
npm install --save-dev babel-plugin-lodash lodash-webpack-plugin


然后改babel配置:
{
"plugins": ["lodash"],
"presets": [
["@babel/preset-env", { "modules": false }]
]
}


再在webpack.config.js里加上插件:
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');

module.exports = {
// ...其他配置
plugins: [
new LodashModuleReplacementPlugin()
],
optimization: {
usedExports: true,
minimize: true
}
};


这样就算你还用const _ = require('lodash'),babel会在编译时把它重写成只引入你需要的方法,相当于自动做了import { get } from 'lodash/get'这种事。

最后提醒一点:Terser压缩阶段报“某些代码无法删除”的警告,通常是因为检测到了可能有副作用的语句,比如全局变量赋值、原型链修改等。Tree Shaking不只是删代码,还要保证程序行为不变。所以哪怕标记了unused,如果有潜在副作用,Terser也不敢动。

总结一下:
1. Tree Shaking依赖ESM + 静态分析,CommonJS天然不支持
2. 必须用import/export语法,且babel不能转成CommonJS(modules: false)
3. 第三方库要提供ESM版本,最好配合sideEffects声明
4. 对于不支持的库,优先考虑替换或使用专用插件(如lodash系列方案)

你现在回去查一下打包后的bundle,大概率发现lodash还是被完整引入,就是因为require触发了CommonJS路径。改成import { get } from 'lodash-es'之后,再看chunk大小,应该能明显变小。
点赞 2
2026-02-12 19:18
艺诺~
艺诺~ Lv1
你这个问题很典型,其实Tree Shaking能不能生效,关键看代码模块的引入方式和依赖本身的格式。你提到用了 const _ = require('lodash'),这就是问题所在——CommonJS的导入方式会让Webpack无法静态分析模块依赖,Tree Shaking直接失效。因为 require 是运行时加载,Webpack在打包阶段根本没法确定你到底用了哪些方法。

通用的做法是改用ES6的 import 语法:

import { get } from 'lodash-es';


注意要用 lodash-es 而不是 lodash,因为原版 lodash 发布的是编译后的CommonJS模块,而 lodash-es 提供了ES模块格式,才能被静态分析,从而支持Tree Shaking。

如果你坚持用 lodash,至少得这样按需引入:

import get from 'lodash/get';


这种方式也能摇掉没用的代码,因为Webpack能明确知道你只用了哪个模块。

另外你说在package.json加了 "sideEffects": false 没用,那是因为你的引入方式本身已经让整个模块被标记为“可能有副作用”了。只有当项目依赖的库本身是ESM且没有副作用时,这个配置才起作用。

总结一下关键点:

1. 别用 require 引入第三方库,改用 import
2. 优先选提供ESM的库,比如 lodash-esramda 这些天然支持Tree Shaking
3. 如果库不支持ESM,就手动按需引入路径,比如 import debounce from 'lodash/debounce'
4. 确保babel不要把ESM转成CommonJS,检查 .babelrcbabel.config.js 里有没有 @babel/plugin-transform-modules-commonjs,有的话在Webpack环境下得关掉

最后,Webpack的Tree Shaking不是魔法,它依赖静态可分析的模块结构。只要有一环是CommonJS,整条链就断了。所以看到bundle里还是有整个lodash,基本可以断定是你用了 require('lodash') 或者 babel 配置把 import 给转没了。
点赞 5
2026-02-08 23:11