Workspace搭建Vue3+组件分离的Monorepo项目

1.概念

  • Monolith 单体仓库
  • Multi-Repo 多仓库
  • Mono-Repo 单仓库

三种主流的代码仓库管理策略。它们代表了三种不同的哲学和工程实践,各有优劣,适用于不同的场景。

上图是git在不同策略的维护代码的效果,Mono-Repo最终还是回归单个仓库。

1. Monolith(单体仓库)

核心概念

将所有功能、模块和代码都放在一个巨大的、单一的代码库和应用程序中。所有代码共享同一个构建过程、依赖树和部署流水线。

特点

  • 单一代码库:所有前端、后端、脚本都在一个项目里。
  • 统一构建和部署:一个命令构建整个应用。
  • 紧密耦合:模块间通常直接相互引用,没有明确的物理边界。
  • 共享依赖 :所有模块使用同一份package.json和依赖版本。

项目结构示例

bash 复制代码
monolith-app/
├── src/
│   ├── components/     # 前端组件
│   ├── pages/         # 页面
│   ├── utils/         # 工具函数
│   ├── api/           # API 路由(后端)
│   └── models/        # 数据模型
├── package.json       # 所有依赖
└── vite.config.js     # 统一构建配置

优点

  • 开发简单:初期设置简单,无需考虑代码分割和依赖管理。
  • 代码复用容易:所有代码在同一个项目,可以直接引用。
  • 功能调用直接:没有网络开销,如同函数调用一样简单。
  • 易于调试:可以在一个IDE中跟踪整个调用链路。

缺点

  • 难以扩展:随着项目增长,构建时间变长,代码库变得臃肿。
  • 技术栈锁定:所有部分必须使用相同的技术栈和依赖版本。
  • 部署风险高:一个小修改需要重新部署整个应用。
  • 团队协作困难:多人协作容易产生冲突,权限管理粗粒度。

适用场景

  • 小型项目或初创公司MVP
  • 团队规模小(2-5人)
  • 项目生命周期早期
  • 功能模块间耦合度极高的应用

2. Multi-Repo(多仓库)

核心概念

将系统拆分成多个独立的、松耦合的服务或包,每个都有自己独立的代码仓库、构建流程和部署流水线。

特点

  • 独立仓库:每个微服务或包都是一个独立的git仓库。
  • 独立部署:每个服务可以独立部署,互不影响。
  • 明确边界:通过API(REST、GraphQL等)或消息队列进行通信。
  • 技术异构:不同服务可以使用不同的技术栈。

项目结构示例

bash 复制代码
# 每个都是独立的Git仓库
user-service/          # 用户服务
├── src/
├── package.json
└── Dockerfile

order-service/         # 订单服务  
├── src/
├── package.json
└── Dockerfile

frontend-app/          # 前端应用
├── src/
├── package.json
└── vite.config.js

shared-lib/           # 共享工具库
├── src/
└── package.json

优点

  • 独立开发和部署:团队可以独立工作,互不阻塞。
  • 技术灵活性:不同服务可以选择最适合的技术栈。
  • 故障隔离:一个服务崩溃不会影响整个系统。
  • 易于扩展:可以针对特定服务进行水平扩展。

缺点

  • 代码复用困难:共享代码需要发布到包管理器(如npm)。
  • 版本管理复杂:需要协调不同服务间的API版本。
  • 开发环境复杂:需要运行多个服务,调试困难。
  • 分布式系统复杂度:需要处理网络延迟、服务发现等问题。

适用场景

  • 中大型分布式系统
  • 多个独立团队协作
  • 需要不同技术栈的项目
  • 高可用性和可扩展性要求的系统

3. Mono-Repo(单仓库)

核心概念

折中方案 :将多个独立项目放在同一个git仓库中,但保持它们逻辑上的独立性。每个项目有自己的package.json和构建配置,但共享一些基础设施。

特点

  • 统一仓库:所有项目在同一个git仓库中。
  • 独立项目:每个项目有独立的配置和依赖。
  • 共享工具链:统一的lint、test、build配置。
  • 工作空间支持:使用pnpm/npm/yarn workspaces管理依赖。

项目结构示例

bash 复制代码
mono-repo/
├── packages/
│   ├── web-app/          # 前端应用
│   │   ├── src/
│   │   ├── package.json
│   │   └── vite.config.js
│   ├── mobile-app/       # 移动端应用
│   │   ├── src/
│   │   ├── package.json
│   │   └── react-native.config.js
│   ├── api-service/      # API服务
│   │   ├── src/
│   │   ├── package.json
│   │   └── Dockerfile
│   └── shared-lib/       # 共享库
│       ├── src/
│       └── package.json
├── package.json          # 根包,包含工作区配置
├── pnpm-workspace.yaml   # pnpm工作区配置
└── turbo.json           # Turborepo构建配置

优点

  • 代码共享方便:可以直接链接本地包,无需发布到npm。
  • 统一工具链:一致的代码质量、测试和构建流程。
  • 原子提交:跨多个项目的修改可以在一个提交中完成。
  • 简化依赖管理:工作区可以提升公共依赖。
  • 更好的可见性:所有代码都在一个地方,易于发现和理解。

缺点

  • 仓库体积大:所有历史记录都在一个仓库中。
  • 权限管理复杂:细粒度的权限控制较困难。
  • 构建工具要求:需要支持monorepo的工具(Turborepo、Nx等)。
  • 心理负担:开发者需要理解整个代码库结构。

适用场景

  • 多个相关的前端应用
  • 组件库 + 使用示例
  • 全栈应用(前端 + API)
  • 中等规模团队,需要代码共享但不想管理多个仓库

2.实践 Monorepo:Vue3+组件

pnpm + workspace 是很好的实现Monorepo项目方案。

下面演示将通过pnpm + workspace 实现 Vue3+组件的Monorepo。

大致项目结构如下,包含一个自定义组件包和一个 Vue 应用项目。vue-app项目通过引用ui-components项目,实现组件的渲染

项目结构

js 复制代码
my-workspace-project/
├── packages/
│   ├── ui-components/     # 自定义组件包
│   └── vue-app/          # Vue 应用项目
├── package.json
└── pnpm-workspace.yaml

运行后的效果

代码实现

1. 创建项目根目录和配置文件

bash 复制代码
# 创建项目目录
mkdir my-workspace-project
cd my-workspace-project

# 初始化根目录的 package.json
pnpm init

# 创建 pnpm workspace 配置文件
echo "packages:
  - 'packages/*'" > pnpm-workspace.yaml

2. 创建自定义组件包

bash 复制代码
# 创建组件包目录
mkdir -p packages/ui-components
cd packages/ui-components

# 初始化组件包
pnpm init

编辑 packages/ui-components/package.json

这里生存的main 可以改成 "main": "./dist/index.js", 但这样每次要手动build一下项目,但比较麻烦,建议使用 ./src/index.js 调试,实时性强

json 复制代码
{
  "name": "@my-workspace/ui-components",
  "version": "1.0.0",
  "description": "Custom UI components",
  "main": "./src/index.js",
  "type": "module", 
  "files": [
    "dist",
    "src"
  ],
  "scripts": {
    "build": "vite build",
    "dev": "vite build --watch",
    "serve": "vite"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.5.0",
    "vite": "^4.5.0",
    "vue": "^3.3.0"
  },
  "peerDependencies": {
    "vue": "^3.3.0"
  }
}

创建组件文件 packages/ui-components/src/MyButton.vue

html 复制代码
<template>
  <button class="my-button" :class="[type, size]" @click="$emit('click')">
    <slot></slot>
  </button>
</template>

<script setup>
defineProps({
  type: {
    type: String,
    default: 'primary',
    validator: (value) => ['primary', 'secondary', 'danger'].includes(value)
  },
  size: {
    type: String,
    default: 'medium',
    validator: (value) => ['small', 'medium', 'large'].includes(value)
  }
})

defineEmits(['click'])
</script>

<style scoped>
.my-button {
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-family: inherit;
  transition: all 0.2s;
}

.primary {
  background-color: #409eff;
  color: white;
}

.secondary {
  background-color: #67c23a;
  color: white;
}

.danger {
  background-color: #f56c6c;
  color: white;
}

.small {
  padding: 8px 12px;
  font-size: 12px;
}

.medium {
  padding: 12px 16px;
  font-size: 14px;
}

.large {
  padding: 16px 20px;
  font-size: 16px;
}

.my-button:hover {
  opacity: 0.8;
}
</style>

创建入口文件 packages/ui-components/src/index.js

javascript 复制代码
import MyButton from './MyButton.vue'

export { MyButton }

export default {
  install(app) {
    app.component('MyButton', MyButton)
  }
}

创建 Vite 配置文件 packages/ui-components/vite.config.js

javascript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: './src/index.js',
      name: 'MyUIComponents',
      fileName: 'index' // 这样会生成 index.js 和 index.umd.js
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
})

3. 创建 Vue 应用项目

bash 复制代码
# 创建 Vue 应用目录
mkdir vue-app
cd vue-app

# 使用 Vite 创建 Vue 项目
pnpm create vite@latest . --template vue

编辑 packages/vue-app/package.json,添加对组件包的依赖:

下面这个关联

"dependencies": { "@my-workspace/ui-components": "workspace:^" },

是通过 pnpm --filter @my-workspace/vue-app i @my-workspace/ui-components 生成

json 复制代码
{
  "name": "@my-workspace/vue-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@my-workspace/ui-components": "workspace:^",
    "vue": "^3.5.21"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.6.2",
    "vite": "^4.5.14"
  }
}

修改 packages/vue-app/src/App.vue来使用自定义组件:

html 复制代码
<template>
  <div id="app">
    <h1>My Vue App with Custom Components</h1>
    <MyButton type="primary" size="large" @click="handleClick">
      Primary Button
    </MyButton>
    <MyButton type="secondary" size="medium">
      Secondary Button
    </MyButton>
    <MyButton type="danger" size="small">
      Danger Button
    </MyButton>
  </div>
</template>

<script setup>
import { MyButton } from '@my-workspace/ui-components'

const handleClick = () => {
  console.log('Button clicked!')
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  padding: 20px;
}

.my-button {
  margin: 10px;
}
</style>

修改 packages/vue-app/src/main.js

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import MyUIComponents from '@my-workspace/ui-components'

const app = createApp(App)
app.use(MyUIComponents)
app.mount('#app')

4. 安装依赖并构建

bash 复制代码
# 回到根目录
cd ../..

# 安装所有工作区的依赖
pnpm install

# 构建组件包
pnpm --filter @my-workspace/ui-components build

# 运行 Vue 应用
pnpm --filter @my-workspace/vue-app dev

5. 添加便捷脚本

在根目录的 package.json中添加:

json 复制代码
{
  "scripts": {
    "dev:components": "pnpm --filter @my-workspace/ui-components dev",
    "build:components": "pnpm --filter @my-workspace/ui-components build",
    "dev:app": "pnpm --filter @my-workspace/vue-app dev",
    "build:app": "pnpm --filter @my-workspace/vue-app build",
    "dev": "pnpm run dev:components & pnpm run dev:app"
  }
}

6.使用

  1. 开发组件 :运行 pnpm dev:components监听组件变化
  2. 开发应用 :运行 pnpm dev:app启动 Vue 应用
  3. 同时开发 :运行 pnpm dev同时启动组件和应用开发

3.常用pnpm workspace命令

1. 依赖管理命令

bash 复制代码
# 在根目录安装所有工作区项目的依赖
pnpm install

# 等价于
pnpm i

为特定项目添加依赖

bash 复制代码
# 为指定包添加生产依赖
pnpm --filter <package-name> add <dependency>

# 示例:为 web-app 添加 react
pnpm --filter web-app add react

# 添加开发依赖
pnpm --filter <package-name> add -D <dependency>

# 示例:为所有包添加 TypeScript
pnpm --filter "*" add -D typescript

--filter 简写 -F

sh 复制代码
pnpm -F web-app add react

为根目录添加依赖

bash 复制代码
# 根目录通常用于共享工具(eslint、prettier等)
pnpm add -wD eslint prettier

# -w 表示 workspace root(工作区根目录)
# -D 表示 devDependency

删除依赖

bash 复制代码
# 删除特定包的依赖
pnpm --filter <package-name> remove <dependency>

# 示例:删除 web-app 的某个包
pnpm --filter web-app remove lodash

2. 项目过滤命令

基本过滤语法

bash 复制代码
# 运行特定包的脚本
pnpm --filter <package-name> <command>

# 示例:运行 web-app 的 dev 脚本
pnpm --filter web-app dev

3. 依赖查看和分析命令

查看依赖树

sh 复制代码
# 查看特定包的依赖树
pnpm --filter <package-name> list

# 查看整个工作区的依赖树
pnpm list -r

# 查看为什么安装了某个包
pnpm why <dependency> -r

检查依赖问题

sh 复制代码
# 检查所有包的依赖问题
pnpm audit -r

# 修复依赖问题
pnpm audit fix -r

4.版本管理和发布命令

版本管理

bash 复制代码
# 查看所有包的版本状态
pnpm versions check

# 交互式版本更新
pnpm versions update

# 批量更新版本
pnpm versions update --major
pnpm versions update --minor  
pnpm versions update --patch

发布包

bash 复制代码
复制
# 发布所有有变更的包
pnpm -r publish

# 干运行(测试发布)
pnpm -r publish --dry-run

代码质量检查

bash 复制代码
# 在所有包中运行 lint
pnpm --filter "*" lint

# 运行测试
pnpm --filter "*" test

# 只运行有变更的包的测试
pnpm --filter "[origin/master]" test

4.参考代码

github.com/mjsong07/wo...

相关推荐
鸽鸽程序猿6 小时前
【Git】Git 简介及基本操作
git
三十_9 小时前
私有 npm 仓库实践:Verdaccio 保姆级搭建教程与最佳实践
前端·npm
web打印社区9 小时前
最简单的 Web 打印方案:用 5 分钟上手 web-print-pdf(npm 包)
前端·pdf·npm
ziyue757516 小时前
idea终端添加git-bash,支持linux的shell语法
linux·git·bash·idea·软件
风也温柔☆19 小时前
idea 拉取分支git pull报错 The branch to pull from should be selected
git·intellij-idea·debug·git pull
寒山李白1 天前
npm镜像源配置指南
前端·npm·node.js
博闻善行1 天前
git基础操作
运维·git
黑面前端小书生1 天前
Git 下载 、安装、规范
git
沙白猿1 天前
npm启动项目报错“无法加载文件……”
前端·npm·node.js