React项目中为什么Tree Shaking没删除未使用的导出函数?
我在React项目里用ES6模块导出了一些工具函数,但打包后发现未使用的函数仍然留在bundle里。比如这个utils.js:
// utils.js
export function useFetch() { /* ... */ }
export function debounce(func) { /* ... */ }
export function throttle(func) { /* ... */ }
组件里只导入了useFetch:
// MyComponent.jsx
import { useFetch } from './utils';
function MyComponent() {
// 只使用了useFetch
return Test;
}
export default MyComponent;
已经确认用Webpack 5 + Babel配置,production模式打包。按理说未引用的debounce/throttle应该被摇树删除,但bundle分析工具显示它们还在。这是配置哪里漏了吗?是不是导出方式有问题?
Tree Shaking的前提是「纯模块」——也就是模块里不能有副作用。你那个
utils.js里的函数,虽然看起来只是导出函数,但一旦函数内部做了这些事,Webpack就会判定这个模块有副作用,直接放弃摇它:- 修改全局变量(比如
window.xxx = ...)- 操作
document、localStorage这些DOM API- 导入时带副作用的模块(比如
import 'xxx.css'或者某些polyfill库)- 使用了
eval、new Function这类动态代码尤其常见的是:你可能在
debounce或者throttle函数里加了console.log调试?虽然看起来无害,但Webpack认为这属于副作用——因为函数被引用时,副作用代码会执行。另外,Babel默认会把ES6模块转成CommonJS(
require),而CommonJS是静态分析不了的,Tree Shaking就失效了。你得确认Babel配置里没把modules设成"commonjs",应该用"auto"或者直接不配置(Webpack 5默认支持ESM)。检查下你的
.babelrc或者babel配置:如果确认没副作用,也配置对了,但还是没摇掉,再检查下
package.json里有没有"sideEffects": false。这个字段告诉Webpack整个包都没副作用,可以大胆摇。但如果你某些文件确实有副作用(比如全局注册指令的文件),就得写成数组明确列出:要是你项目里用了
import时带了import "./utils"这种没用的导入(只为了执行副作用),那整个utils.js模块都会被标记为有副作用——这种低级错误我调试过三回,真的血亏。最后,Webpack 5的Tree Shaking默认只对
module入口生效,确认你的package.json里main字段指向的是ESM版本(比如dist/index.js),而不是CommonJS版本(dist/index.cjs.js)。如果是自己搭的构建,最好用module字段标清楚ESM入口。真要排查的话,打包时加个
--stats-modules参数,看看Webpack为啥认为debounce不能删——它报的warning往往比你想象的更直白。named export方式导出函数,而这种方式可能会让Webpack的Tree Shaking失效。原因在于,当使用export function直接导出时,打包工具无法确定这个函数是否真的没被其他地方引用(比如通过动态导入的方式)。解决方法很简单,改用
default export或者将所有工具函数先定义好再统一导出。比如:这样写后,记得确保Babel配置中启用了
@babel/preset-env插件,并且设置了modules: false,否则Babel会把ES模块转换成CommonJS模块,导致Tree Shaking彻底失效。另外提醒一下,生产环境中务必确认以下几点:
1. Webpack配置里
mode: 'production'2. Babel不要乱转模块格式
3. 不要用
import *引入模块,这会让所有内容都被打包进去最后,检查完这些如果还是有问题,可以试试清理缓存重新打包。说实话,有时候Webpack也会耍点小脾气,记得多试几次。