前言
Monorepo 架构已经成为现代前端项目的主流组织方式,它能够有效解决代码复用、依赖管理和开发效率等问题。本文将以 Vue Ace Admin 项目为例,详细介绍如何从零搭建一个 Monorepo 项目。
如果你对 Monorepo 架构还不熟悉,建议先阅读 Monorepo 架构总览 了解基本概念。
第一步:项目初始化
1.1 创建项目目录
bash
mkdir vue-ace-admin
cd vue-ace-admin
1.2 初始化根 package.json
bash
pnpm init
编辑 package.json:
json
{
"name": "vue-ace-admin",
"version": "1.0.0",
"private": true,
"type": "module",
"packageManager": "pnpm@10.14.0",
"scripts": {
"dev": "vite",
"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"
}
}
关键配置说明:
private: true:防止意外发布根包type: "module":使用 ES 模块packageManager:锁定 pnpm 版本
第二步:配置 pnpm Workspace
2.1 创建 pnpm-workspace.yaml
在项目根目录创建 pnpm-workspace.yaml:
yaml
packages:
- 'packages/*'
- 'docs'
配置说明:
packages/*:匹配所有packages/下的子目录docs:文档站点也是独立的包
2.2 验证 Workspace 配置
bash
pnpm install
如果配置正确,pnpm 会识别所有 workspace 包。
第三步:创建包结构
3.1 创建 Hooks 包
bash
mkdir -p packages/hooks/src
cd packages/hooks
pnpm init
编辑 packages/hooks/package.json:
json
{
"name": "@codexlin/ace-admin-hooks",
"version": "1.0.0",
"description": "Pure Vue 3 Composition API Hooks",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": ["dist"],
"peerDependencies": {
"vue": "^3.0.0"
},
"scripts": {
"build": "vite build"
}
}
关键配置:
name:使用 scoped 包名,避免命名冲突peerDependencies:声明 Vue 为 peer 依赖,避免重复安装files:只发布dist目录exports:定义包的入口点
3.2 创建 UI 组件库包
bash
mkdir -p packages/ui/src
cd packages/ui
pnpm init
编辑 packages/ui/package.json:
json
{
"name": "@codexlin/ace-admin-ui",
"version": "0.3.0",
"description": "Production-ready Vue 3 component library",
"type": "module",
"main": "./dist/ace-admin-ui.umd.js",
"module": "./dist/ace-admin-ui.es.js",
"types": "./dist/types/index.d.ts",
"style": "./dist/ace-admin-ui.css",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/ace-admin-ui.es.js",
"require": "./dist/ace-admin-ui.umd.js"
},
"./dist/ace-admin-ui.css": "./dist/ace-admin-ui.css"
},
"files": ["dist"],
"peerDependencies": {
"ant-design-vue": ">=4.0.0",
"vue": ">=3.4.0"
},
"scripts": {
"build": "vite build"
}
}
关键配置:
main:UMD 格式入口(浏览器直接使用)module:ESM 格式入口(现代构建工具使用)types:TypeScript 类型定义style:CSS 样式文件入口
第四步:配置 TypeScript
4.1 根目录 tsconfig.json
json
{
"compilerOptions": {
"baseUrl": ".",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"target": "ES2020",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*", "packages/*/src/**/*"],
"exclude": ["node_modules", "dist"]
}
4.2 Hooks 包 tsconfig.json
json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"composite": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
关键配置:
composite: true:启用项目引用declaration: true:生成类型声明文件extends:继承根配置
4.3 UI 包 tsconfig.json
json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist/types",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"composite": true
},
"references": [
{ "path": "../hooks" }
],
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
关键配置:
references:引用 hooks 包,实现跨包类型检查- 当 hooks 包类型变更时,UI 包会自动检测到类型错误
第五步:配置 Vite 构建
5.1 Hooks 包 vite.config.ts
typescript
import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [
dts({
insertTypesEntry: true,
copyDtsFiles: true
})
],
build: {
lib: {
entry: 'src/index.ts',
name: 'AceAdminHooks',
fileName: 'index',
formats: ['es'] // 只构建 ESM 格式
},
rollupOptions: {
external: ['vue'], // 外部化 Vue,不打包
output: {
globals: {
vue: 'Vue'
}
}
}
}
})
5.2 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
})
],
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'
},
assetFileNames: (assetInfo) => {
if (assetInfo.name?.endsWith('.css')) {
return 'ace-admin-ui.css'
}
return assetInfo.name || 'assets/[name][extname]'
}
}
}
}
})
关键配置:
lib:库模式构建配置external:外部化依赖,减小包体积formats:输出格式(ESM 用于构建工具,UMD 用于浏览器)
第六步:创建包入口文件
6.1 Hooks 包入口
packages/hooks/src/index.ts:
typescript
export { useList } from './useList'
export { usePagination } from './usePagination'
export { useDebouncedRef } from './useDebouncedRef'
export { useLoading } from './useLoading'
// 导出类型
export type { UseListOptions, UseListResult } from './useList'
export type { UsePaginationOptions } from './usePagination'
6.2 UI 包入口
packages/ui/src/index.ts:
typescript
import type { App } from 'vue'
import ProTable from './pro-table/ProTable.vue'
import ProButton from './pro-button/ProButton.vue'
// 导出组件
export { ProTable, ProButton }
// 导出类型
export type { ProTableProps } from './pro-table/type'
export type { ProButtonProps } from './pro-button/type'
// Vue 插件
export default {
install(app: App) {
app.component('ProTable', ProTable)
app.component('ProButton', ProButton)
}
}
第七步:配置主应用
7.1 根 package.json 添加依赖
json
{
"dependencies": {
"@codexlin/ace-admin-hooks": "workspace:*",
"@codexlin/ace-admin-ui": "workspace:*",
"vue": "^3.5.0"
}
}
workspace:* 的作用:
- 自动链接到本地
packages/目录 - 开发时直接使用源码
- 修改立即生效,无需重新安装
7.2 主应用使用包
src/main.ts:
typescript
import { createApp } from 'vue'
import { ProTable, ProButton } from '@codexlin/ace-admin-ui'
import { useList } from '@codexlin/ace-admin-hooks'
import '@codexlin/ace-admin-ui/dist/ace-admin-ui.css'
const app = createApp(App)
app.use(ProTable)
app.mount('#app')
第八步:配置 Turbo(可选)
8.1 安装 Turbo
bash
pnpm add -D turbo
8.2 创建 turbo.json
json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build:hooks": {
"outputs": ["packages/hooks/dist/**"],
"cache": true
},
"build:ui": {
"dependsOn": ["build:hooks"],
"outputs": ["packages/ui/dist/**"],
"cache": true
},
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
Turbo 优势:
- 依赖管理:
dependsOn确保构建顺序 - 增量构建:只构建变更的包
- 缓存机制:未变更的包使用缓存
项目结构最佳实践
推荐的目录结构
text
vue-ace-admin/
├── packages/
│ ├── hooks/ # 纯逻辑包
│ │ ├── src/
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ │
│ └── ui/ # UI 组件库
│ ├── src/
│ │ ├── components/
│ │ └── index.ts
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts
│
├── src/ # 主应用
├── docs/ # 文档站点
├── pnpm-workspace.yaml # Workspace 配置
├── turbo.json # Turbo 配置(可选)
├── tsconfig.json # 根 TypeScript 配置
└── package.json # 根 package.json
命名规范
包命名:
- 使用 scoped 包名:
@your-org/package-name - 避免命名冲突
- 便于识别包的作用域
目录命名:
packages/:所有包放在这里src/:主应用代码dist/:构建输出(添加到 .gitignore)
常见问题与解决方案
Q1: workspace 包无法识别
问题: pnpm 无法识别 workspace 包
解决方案:
- 检查
pnpm-workspace.yaml配置 - 确保每个包都有
package.json - 运行
pnpm install重新安装
Q2: TypeScript 类型错误
问题: 跨包类型检查失败
解决方案:
- 确保
tsconfig.json中composite: true - 使用
references引用依赖包 - 运行
pnpm build生成类型文件
Q3: 构建顺序问题
问题: UI 包构建时找不到 hooks 包
解决方案:
- 使用 Turbo 的
dependsOn配置 - 或手动指定构建顺序:
pnpm build:hooks && pnpm build:ui
总结
通过以上步骤,你已经成功搭建了一个基础的 Monorepo 项目。关键要点:
- pnpm workspace:实现包之间的依赖管理
- TypeScript 项目引用:实现跨包类型检查
- Vite 库模式:构建可发布的包
- Turbo:优化构建性能
下一步,你可以:
如果你对 Vue 3 企业级工程实践 感兴趣,可以查看我们的 架构实践分类 下的其他文章。