tree shaking 能否生效,关键在于子包的 package.json 是否正确定义 exports/module 字段、声明 "type": "module",以及构建产物是否输出标准 esm 格式并采用具名导出;vite 仅是执行者,真正决定能否摇树的是子包自身的模块规范与导出方式。

TreeShaking 能否生效,关键不在于 Vite 本身,而在于 子包的 package.json 如何声明入口、模块类型,以及构建产物是否符合 ESM 规范。在 Monorepo 中,每个子包(如 packages/ui-components 或 packages/utils)必须被正确配置,才能让上层应用或其它子包在 import 时真正触发摇树。
明确 main 和 module 字段的分工
-
main: 指向 CommonJS 入口(如dist/index.cjs.js),主要供 Node.js 环境或不支持 ESM 的打包器使用,不参与前端 TreeShaking。 -
module: 指向 ES Module 入口(如dist/index.es.js),这是 Web 打包器(Vite、Rollup、Webpack)优先读取的字段,只有它被引用时,才可能触发 TreeShaking。 -
exports: 更现代、更精确的入口控制方式(推荐替代main/module),可按条件区分环境、格式、子路径。
✅ 正确示例(子包 package.json):
{
"name": "@my-org/utils",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs.js",
"module": "./dist/index.es.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.cjs.js"
},
"./date": {
"import": "./dist/date.es.js",
"require": "./dist/date.cjs.js"
}
},
"files": ["dist"]
}
⚠️ 注意:
"type": "module"是必需的,否则.js文件默认按 CommonJS 解析,即使写了export也可能被忽略。
构建输出必须保留 ESM 结构
Vite 库模式(build.lib)默认生成 ESM + CJS 双格式,但需确保:
-
vite.config.ts中启用lib构建,并显式指定formats: ['es', 'cjs'] -
entry是一个对象(支持多入口),且每个入口都导出具名/默认 export(避免export * from 'xxx'这类模糊导出) - 不要
rollupOptions.output.preserveModules: true(这会破坏摇树)
✅ 推荐配置片段:
// packages/utils/vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: {
index: './src/index.ts',
date: './src/date.ts',
},
formats: ['es', 'cjs'],
fileName: (format, entryName) => `./dist/${entryName}.${format}.js`,
},
rollupOptions: {
// 显式 external 非本包依赖(如 lodash),避免被打包进去干扰摇树
external: ['lodash'],
output: {
exports: 'named', // 强制具名导出,利于摇树识别
}
}
}
})
子包内部导出方式影响摇树效果
- ✅ 好:具名导出 + 默认导出分离
// src/index.ts export function formatDate() { /* ... */ } export function parseDate() { /* ... */ } export default { formatDate, parseDate } - ❌ 差:全部
export * from './xxx'或export default Object.assign(...)
→ Rollup/Vite 无法静态分析哪些导出被实际使用,导致整块保留。
上层项目引用时也要配合
在 apps/web-main 中,应使用具名导入,而非默认导入整个模块:
// ✅ 可摇树
import { formatDate } from '@my-org/utils'
// ❌ 不可摇树(整个 utils 包都会被打包)
import utils from '@my-org/utils'
如果子包还提供了子路径导出(如 "./date"),也应优先使用:
import { parseDate } from '@my-org/utils/date' // 直接命中子入口,更精准
类型声明与摇树无关,但影响开发体验
-
types字段指向.d.ts文件(如./dist/index.d.ts) - 使用
vite-plugin-dts自动生成类型,确保.d.ts与.js导出结构一致(尤其注意export *是否被正确映射) - 类型文件不会影响打包体积,但错误的类型声明可能导致 IDE 误判可用 API,间接阻碍正确导入方式
不复杂但容易忽略











