Tree Shaking 为啥没把没用的函数干掉?

景岩 阅读 13

我用 Webpack + ES Module 写了个工具库,明明只 import 了一个函数,打包后却把整个文件都打进去了,Tree Shaking 没生效?

我试过加 sideEffects: false,也确认用了 production 模式,但还是不行。是不是写法有问题?

// utils.js
export const add = (a, b) => a + b;
export const minus = (a, b) => a - b;
export const unusedFn = () => console.log('never used');

// main.js
import { add } from './utils.js';
console.log(add(1, 2));
我来解答 赞 5 收藏
二维码
手机扫码查看
1 条解答
福萍 Dev
别走弯路,你这个问题我当年也踩过,特别典型。

问题出在 Webpack 默认对 ES Module 的 Tree Shaking 识别是基于副作用检测的,但你的代码里虽然没副作用,Webpack 可能没识别出来,或者你没让 Webpack 知道这个包是“纯的”。

先说结论:你用的写法本身没问题,但要让 Tree Shaking 生效,至少得满足以下条件:

1. 你的代码必须是 ES Module(你这个没问题,用的是 exportimport
2. 必须用 production 模式(你确认了,OK)
3. 关键点来了:你的入口文件不能引入任何 side effect(副作用),比如 console.log(add(1,2)) 这个本身不是副作用,但如果你在 utils.js 里有顶层的执行代码,比如 console.log('loaded'),那整个模块就会被标记为有副作用,Tree Shaking 就失效了——不过你这个文件里没有,所以不是这个问题。

真正容易踩的坑是:Webpack 默认不会对 node_modules 里的包做 Tree Shaking,但你这是本地文件,所以也不是这个问题。

那问题在哪?—— 你可能没配置 optimization.usedExportstrue,或者没用 Terser 做二次压缩。

Webpack 的 Tree Shaking 分两层:
- 第一层是静态分析,靠 optimization.usedExports(默认在 production 模式下是 true,但如果你手动改过配置,可能被关了)
- 第二层是压缩阶段,Terser 会读取这个标记,把没用的 export 删掉

你如果没配 Terser,或者用了其他压缩插件(比如 esbuild),它可能不认 Webpack 的 usedExports 标记,导致没用的代码还在。

你先确认下 Webpack 配置里有没有这句:

optimization: {
usedExports: true, // production 模式默认是 true,但如果你自己写了 optimization 就得显式加
}


再确认下有没有配 TerserPlugin(或者类似压缩器):

optimization: {
usedExports: true,
minimize: true, // production 默认是 true
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
dead_code: true, // 默认就是 true,不用管
drop_console: true, // 可选
},
},
}),
],
}


不过你这个例子其实更简单——你直接打包完看 dist/bundle.js 里是不是还有 unusedFn?如果还在,大概率是你没用 Terser,或者用了其他不支持 ES Module dead code elimination 的压缩器(比如 babel-minify 旧版就不行)。

还有一个容易被忽略的点:如果你用的是 Webpack 4,必须用 sideEffects: false 放在 package.json 里,而不是 webpack.config.js 里。你提到了加了 sideEffects: false,但得确认是不是加在 package.json 顶层:

{
"name": "your-lib",
"sideEffects": false,
"main": "dist/bundle.js"
}


如果你是库,建议再加个 "module": "dist/index.js" 指向 ES Module 入口,不然 Webpack 可能走 main 字段,而 main 指向的是 CommonJS,Tree Shaking 就完全失效了(CommonJS 不支持静态分析)。

最后说个真实经历:我当年也以为加了 sideEffects: false 就完事,结果打包出来还是全量引入,后来发现是打包入口用了 import * as utils from './utils',这种写法 Webpack 会认为“你用了整个模块”,就不会 Tree Shaking 单个导出。

你检查下 main.js 是不是用了 import * from 或者解构里写了多个名字?你这个例子是没问题的,但这是高频坑。

总结下:
- 用 ES Module 写法 ✅
- production 模式 ✅
- sideEffects: falsepackage.json
- Webpack 配置里 usedExports: true
- 用 Terser 或兼容 ES Module 的压缩器 ✅
- 没用 import * from 这类破坏 Tree Shaking 的写法 ✅

满足这些,unusedFn 就会被干掉。如果还还在,直接看 dist/bundle.js 顶部有没有 /* unused harmony export unusedFn */ 这类注释——有说明 Webpack 标记了没用,但压缩器没删,那是压缩器的问题;没有的话,说明 Webpack 没识别出来,那是 sideEffects 或入口问题。

赶紧试试,有问题再贴配置我帮你瞅一眼。
点赞 2
2026-02-26 04:03