Tree Shaking 在 Webpack 和 Vite 中均依赖 ES 模块静态分析,但 Webpack 在 production 打包阶段通过 AST 全局分析,Vite 生产构建则交由 Rollup 实现更激进、粒度更细的剔除;两者 sideEffects 配置逻辑一致但仅影响生产构建,且 Vite 开发期不执行 Tree Shaking。

Tree Shaking 在 Webpack 和 Vite 中都用于剔除未使用的导出代码,但实现路径、依赖前提和默认行为有明显差异。核心区别不在“要不要摇”,而在于“谁来摇”“什么时候摇”“能不能摇准”。
原理基础:静态分析能力是否原生支撑
两者都依赖 ES 模块的 静态 import/export 语法做静态分析,这是 Tree Shaking 的前提。CommonJS(require/module.exports)无法被静态判定,因此混用会直接导致失效。
- Webpack:通过 AST 解析整个模块图,在打包阶段(production 模式)执行一次全局静态分析;需配合
mode: 'production'和optimization.usedExports: true(默认开启)才能启用。 - Vite:开发期不打包,不触发 Tree Shaking;生产构建交由 Rollup 完成——Rollup 是 Tree Shaking 的先行者,从设计之初就以“精准剔除”为目标,静态分析更激进、粒度更细,对命名导出、重导出(re-export)、默认导出等场景处理更稳健。
副作用控制:sideEffects 配置逻辑一致但影响范围不同
两者都支持 package.json 中的 sideEffects 字段,用来标记哪些文件有副作用(如 CSS 导入、polyfill 执行),避免被误删。
- Webpack:若设为
false,会跳过所有模块的副作用检查,仅保留被显式引用的导出;若为数组(如["*.css", "*.scss"]),则只保留匹配文件。 - Vite(生产构建):沿用 Rollup 行为,
sideEffects含义完全相同;但注意——Vite 的开发服务器不走 Rollup,所以该配置在 dev 阶段无任何作用,只影响最终 build 输出。
实际效果差异:常见失效场景表现不同
Tree Shaking 不是“开箱即用就全生效”,很多项目体积没变小,问题常出在边界情况:
-
动态导入(import()):Webpack 和 Vite 都会将
import('./utils.js')视为运行时调用,其内部导出不会参与主包的 Tree Shaking;但各自支持代码分割,可单独优化子 chunk。 -
具名导入 + 默认导出混用:比如
import { foo } from 'lib',而库中同时有export default {...}和export const foo = ...,Rollup(Vite 生产)通常能更好识别并剔除未用的default对象属性;Webpack 在某些嵌套重导出场景下可能保守保留。 -
第三方库未用 ESM 版本:Vite 默认用 esbuild 预构建
node_modules中的 CommonJS 库为 ESM 格式,为 Tree Shaking 创造条件;Webpack 则依赖库自身发布 ESM 入口(如"module": "dist/lib.esm.js"),否则无法分析。
验证与调试建议
别只看打包体积数字,要确认代码真被删了:
- Webpack:启用
stats: 'verbose'或使用 webpack-bundle-analyzer 查看模块引用关系;检查usedExports是否标记为true。 - Vite:构建后查看
dist目录产物,配合vite build --report生成report.html;也可临时在vite.config.js中加build.rollupOptions.treeshake: { moduleSideEffects: false, propertyReadSideEffects: false }进一步收紧策略(慎用)。











