前言
构建和打包是 Monorepo 项目中的关键环节。如何配置构建工具,优化构建性能,生成正确的类型声明,是每个 Monorepo 项目必须解决的问题。
本文以 Vue Ace Admin 项目为例,详细介绍 Monorepo 中的构建优化策略。
如果你还不熟悉 Monorepo 基础配置,建议先阅读:
Vite 库模式配置
什么是库模式
Vite 的库模式(Library Mode)用于构建可发布的库,而不是应用。它会:
- 生成多种格式(ESM、UMD、CJS)
- 外部化依赖(不打包到库中)
- 生成类型声明文件
- 优化 Tree Shaking
Hooks 包构建配置
packages/hooks/vite.config.ts:
import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [
dts({
insertTypesEntry: true, // 在 package.json 中插入 types 字段
copyDtsFiles: true, // 复制 .d.ts 文件
outDir: 'dist' // 类型文件输出目录
})
],
build: {
lib: {
entry: 'src/index.ts', // 入口文件
name: 'AceAdminHooks', // UMD 格式的全局变量名
fileName: 'index', // 输出文件名
formats: ['es'] // 只构建 ESM 格式
},
rollupOptions: {
external: ['vue'], // 外部化 Vue,不打包
output: {
globals: {
vue: 'Vue' // UMD 格式的全局变量映射
}
}
}
}
})
配置说明:
formats: ['es']:Hooks 包只输出 ESM 格式(纯逻辑,无需 UMD)external: ['vue']:Vue 由使用方提供,不打包dts插件:自动生成 TypeScript 类型声明
UI 包构建配置
packages/ui/vite.config.ts:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import dts from 'vite-plugin-dts'
import { resolve } from 'path'
export default defineConfig({
plugins: [
vue(),
vueJsx(),
dts({
outDir: 'dist/types',
insertTypesEntry: true,
rollupTypes: true, // 合并类型文件
staticImport: true // 使用静态导入
})
],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'AceAdminUi',
fileName: (format) => `ace-admin-ui.${format}.js`,
formats: ['es', 'umd'] // 同时构建 ESM 和 UMD
},
rollupOptions: {
external: ['vue', 'ant-design-vue'],
output: {
globals: {
vue: 'Vue',
'ant-design-vue': 'AntDesignVue'
},
exports: 'named', // 命名导出
assetFileNames: (assetInfo) => {
// CSS 文件统一命名
if (assetInfo.name?.endsWith('.css')) {
return 'ace-admin-ui.css'
}
return assetInfo.name || 'assets/[name][extname]'
}
}
}
}
})
配置说明:
formats: ['es', 'umd']:同时输出两种格式- ESM:供现代构建工具使用(Vite、Webpack 5+)
- UMD:供浏览器直接使用(CDN)
rollupTypes: true:合并多个类型文件为一个assetFileNames:统一 CSS 文件命名
构建输出格式详解
ESM 格式(ES Module)
特点:
- 现代标准,Tree Shaking 友好
- 支持动态导入
- 浏览器原生支持(现代浏览器)
输出示例:
// dist/ace-admin-ui.es.js
import { defineComponent } from 'vue'
export const ProTable = defineComponent({...})
使用方式:
// 现代构建工具自动识别
import { ProTable } from '@codexlin/ace-admin-ui'
UMD 格式(Universal Module Definition)
特点:
- 兼容多种模块系统(AMD、CommonJS、全局变量)
- 可直接在浏览器中使用
- 体积较大(包含所有代码)
输出示例:
// dist/ace-admin-ui.umd.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) :
typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.AceAdminUi = {}, global.Vue));
})(this, (function (exports, vue) { 'use strict';
// ...
}));
使用方式:
<!-- 浏览器直接使用 -->
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/@codexlin/ace-admin-ui"></script>
<script>
const { ProTable } = AceAdminUi
</script>
TypeScript 类型声明生成
vite-plugin-dts 配置
vite-plugin-dts 是生成 TypeScript 类型声明的官方插件。
基础配置:
import dts from 'vite-plugin-dts'
dts({
outDir: 'dist/types', // 类型文件输出目录
insertTypesEntry: true, // 在 package.json 中插入 types 字段
rollupTypes: true, // 合并类型文件
staticImport: true // 使用静态导入
})
高级配置:
dts({
outDir: 'dist/types',
insertTypesEntry: true,
rollupTypes: true,
staticImport: true,
include: ['src/**/*'], // 包含的文件
exclude: ['src/**/__tests__/*', 'src/**/*.test.*'], // 排除的文件
compilerOptions: {
skipLibCheck: true, // 跳过库检查
declaration: true, // 生成声明文件
emitDeclarationOnly: true // 只生成声明,不编译
}
})
package.json 类型入口配置
{
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/ace-admin-ui.es.js",
"require": "./dist/ace-admin-ui.umd.js"
}
}
}
配置说明:
types:TypeScript 类型入口exports.types:ESM 导入时的类型定义- 确保 IDE 能正确识别类型
Turbo 构建优化
Turbo 是什么
Turbo 是 Vercel 开发的构建系统,专为 Monorepo 优化:
- 增量构建:只构建变更的包
- 缓存机制:未变更的包使用缓存
- 并行构建:无依赖关系的包并行构建
- 依赖管理:自动处理构建顺序
turbo.json 配置
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [
"**/.env.*local",
"tsconfig.json",
"pnpm-workspace.yaml"
],
"tasks": {
"build:hooks": {
"outputs": ["packages/hooks/dist/**"],
"inputs": [
"packages/hooks/src/**",
"packages/hooks/tsconfig.json"
],
"cache": true
},
"build:ui": {
"dependsOn": ["build:hooks"],
"outputs": ["packages/ui/dist/**"],
"inputs": [
"packages/ui/src/**",
"packages/ui/tsconfig.json"
],
"cache": true
},
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"env": ["NODE_ENV", "VITE_*"]
}
}
}
配置说明:
-
dependsOn:构建依赖关系["build:hooks"]:UI 包依赖 hooks 包["^build"]:构建所有依赖包
-
outputs:构建输出目录- Turbo 根据输出判断是否需要重新构建
-
inputs:输入文件- 文件变更时触发重新构建
-
cache:启用缓存- 未变更的包直接使用缓存
Turbo 缓存机制
工作原理:
- 计算哈希:根据输入文件计算哈希值
- 检查缓存:如果哈希匹配,使用缓存
- 执行构建:如果哈希不匹配,执行构建
- 保存缓存:构建结果保存到缓存
缓存优势:
# 第一次构建(完整构建)
pnpm build
# 耗时:30s
# 第二次构建(使用缓存)
pnpm build
# 耗时:2s(只构建变更的包)
# 只修改 hooks 包
# UI 包使用缓存,不重新构建
构建顺序优化
依赖关系图:
build:hooks (无依赖)
↓
build:ui (依赖 hooks)
↓
build (依赖所有包)
Turbo 自动处理:
- hooks 包先构建
- hooks 构建完成后,UI 包并行构建
- 主应用最后构建
构建性能优化
1. 外部化依赖
配置 external:
rollupOptions: {
external: ['vue', 'ant-design-vue']
}
优势:
- 减小包体积(不打包依赖)
- 加快构建速度(跳过依赖处理)
- 避免版本冲突(使用宿主环境的依赖)
2. Tree Shaking 优化
使用 ES Module:
// ✅ 支持 Tree Shaking
export { ProTable } from './pro-table'
export { ProButton } from './pro-button'
// ❌ 不支持 Tree Shaking
export default { ProTable, ProButton }
配置 sideEffects:
{
"sideEffects": false
}
告诉打包工具:这个包没有副作用,可以安全地进行 Tree Shaking。
3. 代码分割策略
按需导入:
// ✅ 按需导入,只打包使用的组件
import { ProTable } from '@codexlin/ace-admin-ui'
// ❌ 全量导入,打包所有组件
import * as AceAdminUI from '@codexlin/ace-admin-ui'
4. 构建缓存优化
使用 Turbo 缓存:
{
"tasks": {
"build": {
"cache": true,
"outputs": ["dist/**"]
}
}
}
清理缓存:
# 清理 Turbo 缓存
pnpm turbo clean
# 清理特定包的缓存
pnpm turbo build --force
构建脚本配置
根目录构建脚本
{
"scripts": {
"build": "pnpm run build:hooks && pnpm run build:ui && vite build",
"build:hooks": "pnpm -C packages/hooks build",
"build:ui": "pnpm -C packages/ui build",
"build:all": "turbo build"
}
}
脚本说明:
build:hooks:构建 hooks 包build:ui:构建 UI 包(依赖 hooks)build:构建所有包和主应用build:all:使用 Turbo 并行构建
包内构建脚本
// packages/ui/package.json
{
"scripts": {
"build": "vite build",
"dev": "vite build --watch"
}
}
开发模式:
vite build --watch:监听文件变更,自动重新构建- 适合包开发时的快速迭代
构建产物管理
输出目录结构
packages/ui/dist/
├── ace-admin-ui.es.js # ESM 格式
├── ace-admin-ui.umd.js # UMD 格式
├── ace-admin-ui.css # 样式文件
└── types/ # 类型声明
├── index.d.ts
├── pro-table/
│ └── ProTable.d.ts
└── ...
.gitignore 配置
# 构建输出
dist/
*.tsbuildinfo
# 缓存
.turbo/
node_modules/
原则:
- 不提交构建产物
- 构建产物通过 CI/CD 生成
- 发布时自动构建
发布前构建
{
"scripts": {
"prepublishOnly": "pnpm build"
}
}
工作流程:
- 运行
pnpm publish - 自动执行
prepublishOnly - 构建最新代码
- 发布构建产物
实际案例:Vue Ace Admin 构建流程
完整构建流程
# 1. 安装依赖
pnpm install
# 2. 构建 hooks 包
pnpm build:hooks
# 输出:packages/hooks/dist/
# 3. 构建 UI 包(依赖 hooks)
pnpm build:ui
# 输出:packages/ui/dist/
# 4. 构建主应用(依赖两个包)
pnpm build
# 输出:dist/
使用 Turbo 优化
# 使用 Turbo 并行构建
pnpm turbo build
# Turbo 自动:
# 1. 分析依赖关系
# 2. 并行构建无依赖的包
# 3. 使用缓存跳过未变更的包
# 4. 按顺序构建有依赖的包
构建时间对比
传统方式(串行):
build:hooks: 8s
build:ui: 12s
build: 15s
总计:35s
Turbo 方式(并行+缓存):
build:hooks: 8s (并行)
build:ui: 12s (并行)
build: 15s
总计:15s(如果 hooks 和 ui 并行)
使用缓存后:
build:hooks: 0.5s (缓存命中)
build:ui: 0.8s (缓存命中)
build: 15s
总计:16.3s
常见问题与解决方案
Q1: 类型声明文件缺失
问题: 构建后没有生成类型声明文件
解决方案:
// 确保 vite-plugin-dts 配置正确
dts({
outDir: 'dist/types',
insertTypesEntry: true
})
// 检查 tsconfig.json
{
"compilerOptions": {
"declaration": true
}
}
Q2: 外部依赖被打包
问题: peerDependencies 被打包到最终产物
解决方案:
rollupOptions: {
external: ['vue', 'ant-design-vue'] // 明确声明外部依赖
}
Q3: Turbo 缓存失效
问题: 即使文件未变更,Turbo 也重新构建
解决方案:
{
"tasks": {
"build": {
"inputs": ["src/**"], // 明确指定输入文件
"outputs": ["dist/**"] // 明确指定输出目录
}
}
}
总结
Monorepo 的构建优化需要综合考虑多个因素:
- Vite 库模式:正确配置构建格式和外部依赖
- 类型声明:使用 vite-plugin-dts 生成类型文件
- Turbo 缓存:利用缓存机制提升构建速度
- 构建顺序:通过 dependsOn 确保正确的构建顺序
通过合理的构建配置,可以实现:
- ✅ 更快的构建速度
- ✅ 更小的包体积
- ✅ 更好的类型支持
- ✅ 更灵活的发布方式
下一步,你可以学习:
如果你对 Vue 3 企业级工程实践 感兴趣,可以查看我们的 架构实践分类 下的其他文章。