为什么我的Tree Shaking没生效?providedExports配置有问题吗?

一光纬 阅读 50

我在项目里用rollup打包时遇到了奇怪的问题。明明只导入了utils.js里的add函数,但打包结果里却包含了整个math模块:


// utils.js
import { add } from './math'; // math.js里还有subtract/multiply等函数

export function sum(a, b) {
  return add(a, b);
}

检查rollup配置时发现,虽然设置了”exports”: “auto”,但生成的package.json里providedExports还是空的。难道导出路径写错了?还是需要额外配置?

试过手动指定providedExports:”./math”: [“add”],但报错说找不到对应模块。是不是路径匹配有问题?或者Tree Shaking在这里失效的其他原因?

我来解答 赞 5 收藏
二维码
手机扫码查看
2 条解答
UX-玉杰
UX-玉杰 Lv1
你这个情况应该是 Tree Shaking 没有正确识别到模块导出项导致的。先说重点:providedExports 是给外部依赖用的,不是让你手动指定内部模块的。你手动加 ./math 肯定会报错,因为 Rollup 会去 node_modules 找对应模块。

Rollup 的 Tree Shaking 是基于 ES Module 的静态导入导出分析的,有几个关键点:

1. 你的 utils.js 导出了 sum 函数,而 sum 又调用了 add,Rollup 会自动保留 add 和整个 math.js 模块。
2. **Tree Shaking 并不会自动排除未使用的模块导出**。比如 math.js 中的 subtractmultiply,如果没在任何地方使用,Rollup 应该能排除掉。但如果整个 math.js 都被打包进去了,可能是因为它里面有副作用(比如直接执行了某些代码),或者你的构建流程中某些插件“激活”了整个模块。
3. 确保你用了 terser 或者 @rollup/plugin-terser,因为真正的 Dead Code Elimination 是在压缩阶段完成的。
4. exports: 'auto' 是正确的配置,providedExports 不是你应该操心的字段,它用于外部依赖(比如 peerDependencies)防止被打包进来。

优化一下你的配置试试:

// rollup.config.js
import { terser } from 'rollup-plugin-terser';

export default {
input: 'src/utils.js',
output: {
dir: 'dist',
format: 'esm',
exports: 'auto',
},
plugins: [terser()],
};


然后再看打包结果。如果 math.js 依然整个被打包进来,检查 math.js 是否有副作用或动态导入行为,比如:

// math.js
console.log('I have side effects'); // 这会导致整个模块不被 Tree Shaking 掉

export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }


去掉副作用代码后,再重新打包试试。如果还是一样,可以贴一下 math.js 内容我帮你分析。

一句话总结:Tree Shaking 依赖静态导入和无副作用模块,providedExports 不是你应该手动干预的字段。
点赞 5
2026-02-04 17:01
爱玲
爱玲 Lv1
这个问题我之前也踩过坑,咱们先搞清楚几个关键点。rollup的tree shaking对模块导出方式和依赖引用方式都有严格要求。

首先看你的math.js导出方式:
// math.js
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export function multiply(a, b) { return a * b; }


如果使用默认导出或者混合导出,会导致rollup无法正确分析依赖。这里应该改成命名导出。

然后检查你的rollup配置,需要添加这两个关键配置:
export default {
input: 'src/index.js',
output: {
dir: 'dist',
format: 'esm',
},
plugins: [
// 需要这个插件处理node_modules
nodeResolve({
// 确保esnext版本支持treeshaking
moduleDirectories: ['node_modules'],
modulesOnly: true,
extensions: ['.js', '.mjs']
}),
commonjs(), // 处理commonjs模块
],
// 关键配置
external: (id) => {
// 标记所有node_modules为external
return id.startsWith('lodash') || id.includes('node_modules');
},
treeshake: {
moduleSideEffects: false, // 自动排除无副作用模块
propertyReadSideEffects: false, // 提升属性读取优化
}
};


关于providedExports为空的问题,这其实是rollup的特性不是bug。需要满足:
1. 必须是外部依赖(external)
2. 需要配置output.exports为"auto"或"named"
3. 模块必须有具名导出

你手动指定providedExports时报错,大概率是路径问题。正确的写法应该是:
providedExports: {
// 注意路径必须和模块id一致
// 可以通过打印模块id确认
'./math.js': ['add']
}


但实际项目中不建议手动配置providedExports,建议改用外部化处理:
external: (id) => {
// 把math模块标记为外部依赖
return id === './math.js' || id.includes('node_modules');
}


需要注意的是:如果math.js是本地文件而不是npm包,这种treeshaking不会生效。因为rollup只对npm包做treeshaking。要优化本地文件的treeshaking,需要整个项目都改用ESM格式+按需导出。

总结处理步骤:
1. 确认math.js使用命名导出
2. 在rollup.config.js中添加nodeResolve和commonjs插件
3. 配置treeshake选项为严格模式
4. 把math.js标记为external
5. 输出格式必须使用esm

如果打包后math.js依然完整保留,建议检查构建产物里的模块id是否和external配置匹配。可以用以下命令调试:
npx rollup -c --watch --verbose


看日志输出的模块id路径是否和你在external函数里写的匹配。这个是最容易出错的地方,有时候路径会带../或者绝对路径,需要根据实际调整。
点赞 6
2026-02-03 16:02