vite 构建时默认通过 rollup 进行 tree-shaking,剔除未引用的 es 模块导出;需满足 esm 语法、模块为 esm 格式、无隐式副作用等前提,动态 import 或命名空间导入会导致失效。

Vite 在构建阶段直接复用 Rollup 的静态分析能力,对未引用的模块导出进行剔除,整个过程默认开启、无需手动干预。关键在于它把 Rollup 当作底层打包引擎,而 Rollup 本身就是为 ESM 设计的、以 Tree-shaking 为核心特性的工具。
依赖 Rollup 的原生 ESM 分析流程
Vite 构建时(vite build)会将项目源码交由 Rollup 处理。Rollup 按以下步骤完成剔除:
- 从入口文件(如
main.js)开始,递归解析所有import语句,构建完整的模块依赖图 - 沿着 import 链向上追踪每个被实际使用的
export标识符(比如import { debounce } from './utils'→ 只标记debounce) - 同一模块中未被任何 import 引用的导出(如
export const unusedHelper = () => {}),会被标记为死代码 - 最终生成 bundle 时,这些未标记的导出内容不进入输出代码
必须满足的前提条件
不是所有代码都能被摇掉,Vite(通过 Rollup)只对符合以下条件的部分生效:
- 使用标准 ES 模块语法:仅
import/export有效;require或module.exports不参与分析 - 模块本身是 ESM 格式:第三方包需提供
"type": "module"或"exports"中指定 ESM 入口(例如用lodash-es而非lodash) - 没有隐式副作用:若某模块仅执行副作用(如
axios.defaults.baseURL = '/api')但无export,需在package.json中声明"sideEffects": ["./globals.js"],否则可能被误删
如何确认剔除真正发生
不能只看配置是否开启,要验证结果:
- 运行
vite build,检查输出目录(默认dist/)下的 JS 文件 - 用文本编辑器或命令行搜索某个明确未被 import 的函数名(如
subtract),结果应为空 - 对比开发环境(
vite dev)与构建产物:dev 模式基于浏览器原生 ESM,不做剔除,体积大且完整;build 后体积变小、无冗余函数体 - 避免被 HMR 或 source map 干扰判断——它们只存在于内存编译中,不影响最终 dist 文件
常见失效场景与应对
即使配置正确,也可能“摇不掉”:
-
动态 import + 字符串拼接:如
import(`./pages/${page}.js`),Rollup 无法静态分析,整块模块保留 -
导出但未命名引入:写成
import * as utils from './utils',会导致全部导出被保留;应改用具名导入import { throttle } from './utils' -
全局挂载函数未 export:像 Laravel 项目中在
global.js定义window.Toast = () =>{},因无export且未被 import,会被清除;解决方式是显式export并在主入口引入,或通过sideEffects声明该文件有副作用











