什么是Monorepo?
Monorepo(单一代码仓库) 是一种软件开发策略,它将多个相关项目或包存储在同一个版本控制仓库中。与传统的每个项目一个仓库(Multi-repo)不同,Monorepo 允许在单一仓库中管理多个独立但相互关联的模块。
核心特征:
-
统一代码库:所有项目共享同一个根目录。
-
集中化管理:共享依赖、配置和工具链。
-
原子提交:跨项目的修改可以在一次提交中完成。
-
隐式依赖:项目间可直接引用,无需发布到包管理器。
简单来说,Monorepo可以帮助我们统一管理多个相关联的项目,多个项目可以共享同一个配置,同时可以使我们在本地开发的工具在多个项目间直接使用,不必每个项目都进行多次编写,也不必发布到包管理器,保证了项目的闭源。
同时与pnpm对包的软链接和硬链接特性,大大节省了磁盘空间,同时避免多仓库的版本依赖冲突。
关于pnpm的软链接和硬链接,可以看看这个包管理工具pnpm。
使用pnpm搭建monorepo项目
我们先初始化一个项目:pnpm init
。
然后创建一个pnpm-workspace.yaml
文件,在这个文件里指定每个项目的包名,如下:
1 2 3 4 5 6 7 8 9
| packages: - 'my-app' - 'packages/*' - 'components/**' - '!**/test/**'
|
然后创建一个.npmrc
文件,这个文件是pnpm操作时读取配置的配置文件,替代命令行输入。
在里面填写:
1
| link-workspace-packages=true
|
link-workspace-packages:将 monorepo 工作空间中本地可用的包链接到 node_modules,而不是从注册源中重新下载它们。就是设置这个以后,我们可以在项目中通过 pnpm 下载本地包了。
然后根据pnpm-workspace.yaml
中的配置创建文件夹:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| test ├─ .npmrc ├─ package.json ├─ workspace.yaml ├─ packages │ └─ utils │ └─ package.json ├─ my-app │ ├─ .gitignore │ ├─ index.html │ ├─ jsconfig.json │ ├─ package.json │ ├─ README.md │ ├─ vite.config.js │ ├─ src │ │ ├─ App.vue │ │ └─ main.js │ └─ public │ └─ favicon.ico └─ components └─ package.json
|
这里我在packages下创建了utils文件夹,作为一个工具包。
然后在每个包下面进行pnpm初始化
或创建一个package.json
文件自行填写内容。
对package.json
中的name
项进行修改,这一步是为了避免包名的name
相同,导致下载冲突,比如修改为:@xxx/包名
。
将my-app
作为总项目运行包,初始化一个vue项目:pnpm create vue@latest
。
在utils
包下创建一个测试文件,如:count.js
。
填写一个测试函数:
1 2 3
| export function count(a, b) { return a + b; }
|
在命令行中切换到my-app
项目下,下载utils
:
下载成功如下:
测试一下:
my-app/src/main.js1 2 3 4 5 6 7
| import { createApp } from 'vue'; import App from './App.vue'; import { count } from '@zrb/utils/count';
console.log(count(1, 2));
createApp(App).mount('#app');
|
普通文件测试完了,我们在components
包中创建一个Button.vue
文件进行测试:
目录结构如下:
1 2 3 4 5 6
| components ├─ index.js ├─ package.json └─ Button ├─ Button.vue └─ index.js
|
各个文件内容如下:
components/Button/Button.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script setup> defineOptions({ name: 'ZButton', }); </script>
<template> <button class="z-button"> <slot>Button</slot> </button> </template>
<style scoped> .z-button { width: 120px; height: 60px; border-radius: 10px; box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, 0.1); } </style>
|
components/Button/index.js1 2 3 4 5 6 7
| import Button from './Button.vue';
Button.install = (app) => { app.component(Button.name, Button); };
export default Button;
|
components/index.js1 2 3 4 5 6 7
| import Button from './Button';
export default { install(app) { app.use(Button); }, };
|
components/package.json1 2 3 4 5 6 7 8 9 10 11 12 13
| { "name": "@zrb/components", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "packageManager": "pnpm@10.6.4" }
|
然后切换到my-app
目录下,执行pnpm add @zrb/components
:
在my-app/src/main.js
中进行注册:
1 2 3 4 5 6 7 8
| import { createApp } from 'vue'; import App from './App.vue'; import { count } from '@zrb/utils/count'; import component from '@zrb/components';
console.log(count(1, 2));
createApp(App).use(component).mount('#app');
|
在my-app/src/App.vue
中进行测试:
1 2 3 4 5 6 7 8 9
| <script setup lang="ts"></script>
<template> <div> <z-button></z-button> </div> </template>
<style scoped></style>
|
测试结果成功如下:
然后进行打包测试:
注意:Vite默认打包资源的引入是绝对路径的引入,如果打包的项目运行在服务器设置的根目录下,是没问题的,但如果不是根目录,就会出现404错误。
我们需要修改vit.config.js
的base
为'./'
,表示以运行的默认html路径,如:index.html
的当前路径寻找'./'
作为相对路径引入资源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import vueDevTools from 'vite-plugin-vue-devtools';
export default defineConfig({ base: './', plugins: [vue(), vueDevTools()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, });
|
修改之后进行打包测试就没问题了。