Monorepo 构建与打包优化:Vite 库模式与 Turbo 缓存策略

约 13 分钟阅读

前言

构建和打包是 Monorepo 项目中的关键环节。如何配置构建工具,优化构建性能,生成正确的类型声明,是每个 Monorepo 项目必须解决的问题。

本文以 Vue Ace Admin 项目为例,详细介绍 Monorepo 中的构建优化策略。

如果你还不熟悉 Monorepo 基础配置,建议先阅读:

Vite 库模式配置

什么是库模式

Vite 的库模式(Library Mode)用于构建可发布的库,而不是应用。它会:

  • 生成多种格式(ESM、UMD、CJS)
  • 外部化依赖(不打包到库中)
  • 生成类型声明文件
  • 优化 Tree Shaking

Hooks 包构建配置

packages/hooks/vite.config.ts

typescript
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

typescript
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 友好
  • 支持动态导入
  • 浏览器原生支持(现代浏览器)

输出示例:

javascript
// dist/ace-admin-ui.es.js
import { defineComponent } from 'vue'
export const ProTable = defineComponent({...})

使用方式:

typescript
// 现代构建工具自动识别
import { ProTable } from '@codexlin/ace-admin-ui'

UMD 格式(Universal Module Definition)

特点:

  • 兼容多种模块系统(AMD、CommonJS、全局变量)
  • 可直接在浏览器中使用
  • 体积较大(包含所有代码)

输出示例:

javascript
// 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';
  // ...
}));

使用方式:

html
<!-- 浏览器直接使用 -->
<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 类型声明的官方插件。

基础配置:

typescript
import dts from 'vite-plugin-dts'

dts({
  outDir: 'dist/types',        // 类型文件输出目录
  insertTypesEntry: true,      // 在 package.json 中插入 types 字段
  rollupTypes: true,            // 合并类型文件
  staticImport: true            // 使用静态导入
})

高级配置:

typescript
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 类型入口配置

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 配置

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_*"]
    }
  }
}

配置说明:

  1. dependsOn:构建依赖关系

    • ["build:hooks"]:UI 包依赖 hooks 包
    • ["^build"]:构建所有依赖包
  2. outputs:构建输出目录

    • Turbo 根据输出判断是否需要重新构建
  3. inputs:输入文件

    • 文件变更时触发重新构建
  4. cache:启用缓存

    • 未变更的包直接使用缓存

Turbo 缓存机制

工作原理:

  1. 计算哈希:根据输入文件计算哈希值
  2. 检查缓存:如果哈希匹配,使用缓存
  3. 执行构建:如果哈希不匹配,执行构建
  4. 保存缓存:构建结果保存到缓存

缓存优势:

bash
# 第一次构建(完整构建)
pnpm build
# 耗时:30s

# 第二次构建(使用缓存)
pnpm build
# 耗时:2s(只构建变更的包)

# 只修改 hooks 包
# UI 包使用缓存,不重新构建

构建顺序优化

依赖关系图:

text
build:hooks (无依赖)
    ↓
build:ui (依赖 hooks)
    ↓
build (依赖所有包)

Turbo 自动处理:

  • hooks 包先构建
  • hooks 构建完成后,UI 包并行构建
  • 主应用最后构建

构建性能优化

1. 外部化依赖

配置 external:

typescript
rollupOptions: {
  external: ['vue', 'ant-design-vue']
}

优势:

  • 减小包体积(不打包依赖)
  • 加快构建速度(跳过依赖处理)
  • 避免版本冲突(使用宿主环境的依赖)

2. Tree Shaking 优化

使用 ES Module:

typescript
// ✅ 支持 Tree Shaking
export { ProTable } from './pro-table'
export { ProButton } from './pro-button'

// ❌ 不支持 Tree Shaking
export default { ProTable, ProButton }

配置 sideEffects:

json
{
  "sideEffects": false
}

告诉打包工具:这个包没有副作用,可以安全地进行 Tree Shaking。

3. 代码分割策略

按需导入:

typescript
// ✅ 按需导入,只打包使用的组件
import { ProTable } from '@codexlin/ace-admin-ui'

// ❌ 全量导入,打包所有组件
import * as AceAdminUI from '@codexlin/ace-admin-ui'

4. 构建缓存优化

使用 Turbo 缓存:

json
{
  "tasks": {
    "build": {
      "cache": true,
      "outputs": ["dist/**"]
    }
  }
}

清理缓存:

bash
# 清理 Turbo 缓存
pnpm turbo clean

# 清理特定包的缓存
pnpm turbo build --force

构建脚本配置

根目录构建脚本

json
{
  "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 并行构建

包内构建脚本

json
// packages/ui/package.json
{
  "scripts": {
    "build": "vite build",
    "dev": "vite build --watch"
  }
}

开发模式:

  • vite build --watch:监听文件变更,自动重新构建
  • 适合包开发时的快速迭代

构建产物管理

输出目录结构

text
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 配置

gitignore
# 构建输出
dist/
*.tsbuildinfo

# 缓存
.turbo/
node_modules/

原则:

  • 不提交构建产物
  • 构建产物通过 CI/CD 生成
  • 发布时自动构建

发布前构建

json
{
  "scripts": {
    "prepublishOnly": "pnpm build"
  }
}

工作流程:

  1. 运行 pnpm publish
  2. 自动执行 prepublishOnly
  3. 构建最新代码
  4. 发布构建产物

实际案例:Vue Ace Admin 构建流程

完整构建流程

bash
# 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 优化

bash
# 使用 Turbo 并行构建
pnpm turbo build

# Turbo 自动:
# 1. 分析依赖关系
# 2. 并行构建无依赖的包
# 3. 使用缓存跳过未变更的包
# 4. 按顺序构建有依赖的包

构建时间对比

传统方式(串行):

bash
build:hooks: 8s
build:ui: 12s
build: 15s
总计:35s

Turbo 方式(并行+缓存):

bash
build:hooks: 8s (并行)
build:ui: 12s (并行)
build: 15s
总计:15s(如果 hooks 和 ui 并行)

使用缓存后:

bash
build:hooks: 0.5s (缓存命中)
build:ui: 0.8s (缓存命中)
build: 15s
总计:16.3s

常见问题与解决方案

Q1: 类型声明文件缺失

问题: 构建后没有生成类型声明文件

解决方案:

typescript
// 确保 vite-plugin-dts 配置正确
dts({
  outDir: 'dist/types',
  insertTypesEntry: true
})

// 检查 tsconfig.json
{
  "compilerOptions": {
    "declaration": true
  }
}

Q2: 外部依赖被打包

问题: peerDependencies 被打包到最终产物

解决方案:

typescript
rollupOptions: {
  external: ['vue', 'ant-design-vue']  // 明确声明外部依赖
}

Q3: Turbo 缓存失效

问题: 即使文件未变更,Turbo 也重新构建

解决方案:

json
{
  "tasks": {
    "build": {
      "inputs": ["src/**"],  // 明确指定输入文件
      "outputs": ["dist/**"] // 明确指定输出目录
    }
  }
}

总结

Monorepo 的构建优化需要综合考虑多个因素:

  1. Vite 库模式:正确配置构建格式和外部依赖
  2. 类型声明:使用 vite-plugin-dts 生成类型文件
  3. Turbo 缓存:利用缓存机制提升构建速度
  4. 构建顺序:通过 dependsOn 确保正确的构建顺序

通过合理的构建配置,可以实现:

  • ✅ 更快的构建速度
  • ✅ 更小的包体积
  • ✅ 更好的类型支持
  • ✅ 更灵活的发布方式

下一步,你可以学习:

如果你对 Vue 3 企业级工程实践 感兴趣,可以查看我们的 架构实践分类 下的其他文章。

相关文章