Monorepo
以下是对 Monorepo、Turborepo、pnpm 这三个概念及其组合使用方式的详细介绍,每个部分都有深入的解释和实际的例子说明。
1. Monorepo 概念与优势
Monorepo 是一种软件开发策略,将多个项目存放在同一个代码仓库(Repository)中,而不是为每个项目创建单独的仓库。这种做法在大型公司和复杂系统开发中越来越受欢迎,因为它有助于统一管理、共享代码和简化依赖管理。
优点详解:
共享代码和资源:
- 问题:在多仓库(Multirepo)设置中,不同项目间的代码共享往往通过发布库到 npm、Maven 等外部仓库实现,这增加了额外的维护和版本管理负担。
- 解决方案:在 Monorepo 中,所有项目共享同一个代码库,可以直接引用和复用其他项目中的代码或资源。例如,一个
shared-library
可以被多个项目如project-a
和project-b
直接使用,无需发布和管理外部版本。
示例:
1
2
3
4
5
6
7
8
9
10
11/my-monorepo
/packages
/project-a
/src
package.json
/project-b
/src
package.json
/shared-library
/src
package.json在
project-a
中使用shared-library
的代码:1
import { utilityFunction } from 'shared-library';
简化依赖管理:
- 问题:在多仓库中,不同项目的依赖版本可能不一致,导致不可预测的兼容性问题,特别是在使用共享库时。
- 解决方案:Monorepo 中可以集中管理所有项目的依赖。通过在根目录下的
package.json
中统一配置依赖版本,确保各项目之间的依赖版本一致。例如,React
或TypeScript
的版本可以在 Monorepo 根目录下统一管理。
示例:
1
2
3
4
5
6
7
8
9{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*"],
"devDependencies": {
"typescript": "^4.0.0",
"eslint": "^7.0.0"
}
}协作和工作流效率提升:
- 问题:多仓库管理会导致团队之间的协作复杂化,尤其是跨项目变更和代码审查。不同项目的代码可能分布在不同的仓库中,沟通和协作需要跨仓库进行,降低了效率。
- 解决方案:Monorepo 提供了一个统一的代码库,所有团队成员在同一个仓库中工作,极大简化了代码审查和协作流程。团队可以在同一平台上进行跨项目的变更、测试和发布操作,提高了整体开发效率。
示例:
在 Monorepo 中进行 Pull Request(PR),团队可以一次性审查跨多个项目的变更,而不需要在多个仓库间切换。
目录结构示例:
1 | /my-monorepo |
2. workspace:*
与 Monorepo 的关系
在 Monorepo 环境中,workspace:*
是一种特殊的依赖版本声明,表示该依赖项是工作区内的一个包,而不是从外部的 npm 仓库中下载。这种声明的主要作用是将本地工作区中的包作为依赖,并自动管理其版本。
作用详解:
本地开发和调试:
- 问题:传统的开发模式下,依赖本地包进行开发和调试时,需要频繁手动链接(如
npm link
)或发布本地包,非常麻烦且容易出错。 - 解决方案:使用
workspace:*
依赖,可以直接使用 Monorepo 中其他项目的最新代码,无需额外配置。这极大简化了本地开发和调试的流程。
示例:
在project-b
的package.json
中依赖@vben/access
:1
2
3
4
5
6
7{
"name": "project-b",
"version": "1.0.0",
"dependencies": {
"@vben/access": "workspace:*"
}
}这样,
project-b
会直接引用 Monorepo 中@vben/access
的最新代码,而不是从 npm 仓库获取。- 问题:传统的开发模式下,依赖本地包进行开发和调试时,需要频繁手动链接(如
自动版本对齐:
- 问题:当多个项目共享相同的依赖包时,手动管理版本可能导致版本不一致的问题,造成难以调试的 bug。
- 解决方案:
workspace:*
确保所有引用相同包的项目始终使用一致的版本,并且版本变动时会自动更新。例如,更新@vben/access
包的代码后,所有依赖它的项目都会自动使用最新的代码。
示例:
当@vben/access
更新后,project-b
会自动获取最新版本,无需手动更新版本号。
3. 本地依赖管理:确保包的更新
在 Monorepo 中,如果某个包变动了,需要确保所有依赖该包的项目能自动获取最新版本。以下是几种确保包更新的常见方法:
自动更新依赖详解:
workspace:*
自动更新:- 作用:当一个包更新后,所有使用
workspace:*
声明的依赖会自动更新到最新版本。这在 Monorepo 中非常有用,可以避免手动管理各项目的依赖版本。 - 例子:在
project-b
中依赖@vben/access
,当@vben/access
更新后,project-b
会自动使用最新代码,无需手动操作。
- 作用:当一个包更新后,所有使用
使用工具进行依赖同步:
- Yarn Workspaces:
- 命令:
yarn install
和yarn upgrade
可以自动同步和更新依赖。 - 示例:运行
yarn install
会确保所有项目的依赖都与最新的包对齐。
- 命令:
- pnpm Workspaces:
- 命令:
pnpm install
和pnpm update
可以同步更新所有依赖关系。 - 示例:
pnpm update
更新 Monorepo 中所有包的依赖版本。
- 命令:
- Lerna:
- 命令:
lerna bootstrap
重新安装依赖,并确保所有项目之间的链接是最新的。 - 示例:运行
lerna bootstrap
,会重新链接所有项目,使依赖保持最新。
- 命令:
- Yarn Workspaces:
使用变动检测工具:
- Nx:
- 作用:Nx 是一个 Monorepo 工具,可以智能检测变动的依赖关系,并触发相关项目的重建。
- 示例:当
shared-library
发生变动,Nx 可以自动触发依赖shared-library
的所有项目进行重建和测试。
- Nx:
设置自动化流程:
- CI/CD 集成:
- 作用:在 CI/CD 中集成依赖更新检测和处理流程,确保每次发布前所有依赖都保持最新。
- 示例:在 CI 流程中,设置脚本检测并安装最新的依赖版本,然后进行构建和测试。
- CI/CD 集成:
手动版本管理:
手动发布和升级:
- 步骤:先发布新版本的包,然后在 Monorepo 中运行依赖更新命令,确保所有项目都使用新版本。
- 例子:使用
lerna publish
发布新版本,然后运行pnpm install
来更新依赖。
版本号同步:
- 作用:在
package.json
中保持一致的版本号,确保手动升级时不会发生版本错乱。 - 示例:所有项目的
package.json
中都引用相同的依赖版本,减少手动维护的工作量。
- 作用:在
监控和通知:
监控依赖变动:
- 方法:使用 Git hooks 或 CI 工具监控依赖的变动,并自动触发更新。
- 示例:设置 Git hook,当 Monorepo 中的某个包发生变动时,自动触发依赖更新脚本。
通知机制:
- 作用:配置通知工具,如 Slack 或电子邮件,提醒相关开发者注意依赖包的更新。
- 示例:设置 Slack 通知
,当某个重要依赖包更新时,自动发送通知提醒开发者进行必要的操作。
4. 本地组件的处理方式
在 Monorepo 中,项目之间通常会共享一些本地组件(如 UI 组件库、工具函数库)。这些本地组件如何管理和引用,直接影响到开发效率和项目的维护性。
处理本地组件的几种方法:
提取为子模块(Submodules):
- 方法:将本地组件直接放置在 Monorepo 的
packages
目录下,并作为一个独立的包管理。 - 优点:组件可以作为独立的包进行版本管理和发布,也可以在 Monorepo 内被多个项目直接引用。
- 缺点:需要管理版本号和发布流程,适合复杂和常用的组件库。
示例:
假设有一个共享组件库shared-ui
:1
2
3
4
5
6
7/my-monorepo
/packages
/project-a
/project-b
/shared-ui # 本地组件库
/src
package.json在
project-a
中引用shared-ui
:1
2
3
4
5
6{
"name": "project-a",
"dependencies": {
"shared-ui": "workspace:*"
}
}- 方法:将本地组件直接放置在 Monorepo 的
使用
file:
依赖:- 方法:将本地组件打包为
.tgz
文件,并使用file:
依赖方式在package.json
中引用。 - 优点:适合临时或私有的组件共享,不需要发布到公共 npm 仓库。
- 缺点:每次组件更新后,需要重新打包并更新引用路径。
示例:
假设你打包了shared-ui
组件库:1
2cd packages/shared-ui
npm pack # 打包为 .tgz 文件然后在
project-b
中引用:1
2
3
4
5
6{
"name": "project-b",
"dependencies": {
"shared-ui": "file:../shared-ui/shared-ui-1.0.0.tgz"
}
}- 方法:将本地组件打包为
使用符号链接(Symlinks):
- 方法:在本地开发时,使用符号链接(如
npm link
或yarn link
)将组件库链接到依赖它的项目中。 - 优点:非常适合本地开发和调试,能快速测试组件更新效果。
- 缺点:符号链接可能在跨平台(如 Windows 和 Unix 系统)或 CI 环境中表现不一致,且生产环境中不推荐使用。
示例:
使用npm link
连接shared-ui
:1
2
3
4
5cd packages/shared-ui
npm link # 创建全局链接
cd ../project-a
npm link shared-ui # 在项目中引用- 方法:在本地开发时,使用符号链接(如
发布到私有 npm 仓库:
- 方法:将本地组件发布到公司内部的私有 npm 仓库,其他项目通过 npm install 来获取该组件。
- 优点:适合需要严格管理版本的组件,确保每次变更都经过发布流程。
- 缺点:增加了发布和管理的复杂性,需要维护私有 npm 仓库。
示例:
你可以将shared-ui
发布到私有 npm 仓库:1
npm publish --registry http://private-registry.example.com
然后在
project-b
中通过私有仓库安装:1
2
3
4
5
6
7
8
9{
"name": "project-b",
"dependencies": {
"shared-ui": "1.0.0"
},
"publishConfig": {
"registry": "http://private-registry.example.com"
}
}
5. Turborepo:优化 Monorepo 的构建与开发流程
Turborepo 是一个现代化的构建系统,专门为 Monorepo 环境设计,能够大幅提升构建和开发流程的效率。它通过增量构建、缓存、任务管道和并行执行等机制,优化大型项目的开发体验。
Turborepo 的核心特性:
增量构建:
- 概念:增量构建意味着只重新构建受影响的部分,而不是整个项目。Turborepo 能智能检测哪些部分需要重建,从而显著减少构建时间。
- 例子:当你在
shared-library
中修改了一些代码,只会触发依赖它的项目进行重新构建,而不影响其他独立的项目。
缓存机制:
- 概念:Turborepo 可以缓存构建结果和测试结果。当代码没有变动时,直接使用缓存结果,避免重复构建。
- 例子:第一次构建可能耗时较长,但第二次构建如果没有代码变动,可以瞬间完成。
任务管道:
- 概念:Turborepo 允许你定义任务的依赖关系,并以最优顺序执行。例如,构建任务可以依赖测试任务,确保所有代码都通过测试后才进行构建。
- 例子:你可以定义一个管道,让所有项目先通过单元测试,然后再进行构建,最后进行发布。
并行执行:
- 概念:Turborepo 能够并行执行任务,充分利用多核 CPU,提升执行效率。
- 例子:当你在多个项目上运行测试时,可以同时进行,而不是一个接一个地执行。
与 Monorepo 的集成:
Turborepo 与 Monorepo 的配合非常紧密,特别是在复杂的项目结构中。通过 Turborepo 的管道配置和缓存管理,可以极大提高 Monorepo 的开发和构建效率。
配置 Turborepo 的步骤:
安装 Turborepo:
- 步骤:在你的 Monorepo 根目录下安装 Turborepo。
1
pnpm add turbo -D
- 步骤:在你的 Monorepo 根目录下安装 Turborepo。
定义
turbo.json
配置:- 步骤:在根目录下创建
turbo.json
配置文件,定义构建和测试的任务管道。1
2
3
4
5
6
7
8
9
10
11
12
13{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
}
}
} - 解释:上面配置定义了两个任务
build
和test
,其中build
任务依赖于其他项目的build
,而test
任务依赖于build
完成后执行。
- 步骤:在根目录下创建
运行 Turborepo:
- 步骤:使用 Turborepo 来执行任务,如构建或测试。
1
pnpm turbo run build
- 解释:此命令会按照管道顺序依次构建各个项目,并根据需要并行或串行执行任务。
- 步骤:使用 Turborepo 来执行任务,如构建或测试。
使用缓存:
- 步骤:配置缓存策略,在重复构建时使用缓存以节省时间。
1
pnpm turbo run build --force
- 解释:通过配置缓存策略,可以跳过不必要的构建任务,直接使用上一次的结果。
- 步骤:配置缓存策略,在重复构建时使用缓存以节省时间。
6. pnpm:高效的依赖管理工具
pnpm 是一个高效的包管理工具,特别适合在 Monorepo 环境中使用。相比 npm 和 Yarn,pnpm 的主要优势在于其独特的符号链接机制,极大减少了磁盘空间的占用,并且加快了依赖的安装速度。
pnpm 的核心特性:
符号链接机制:
- 概念:pnpm 通过将依赖包安装在共享的全局存储中,并在每个项目中使用符号链接指向这些依赖,避免重复安装相同的依赖包。
- 优点:减少磁盘空间占用,加快安装速度。
- 例子:在一个项目中安装了
react
,其他项目依赖react
时,不会再次下载,而是直接链接到全局存储中的react
。
工作区支持:
- 概念:pnpm 支持工作区(Workspaces),允许在 Monorepo 中统一管理多个项目的依赖关系。
- 优点:简化了跨项目的依赖管理,使得 Monorepo 内的各项目能够共享依赖。
- **例子
例子:在 pnpm-workspace.yaml
中定义工作区,将 Monorepo 中的各个项目统一管理:
1
2
3packages:
- 'packages/*'
- 'shared/*'
- 快速的依赖安装:
- 概念:pnpm 通过并行处理和缓存机制,加快了依赖安装的速度,特别是在大规模项目中表现尤为突出。
- 优点:即使在初次安装时,pnpm 的安装速度也比 npm 和 Yarn 更快,极大提升了开发效率。
- 例子:运行
pnpm install
安装所有依赖包时,比 npm 或 Yarn 快得多,特别是在依赖项复杂或数量庞大时。
pnpm 与 Turborepo 的集成:
pnpm 与 Turborepo 一起使用,可以极大优化 Monorepo 的开发和构建流程。pnpm 负责高效地管理和安装依赖,而 Turborepo 负责优化构建和任务执行,两者结合可以使 Monorepo 的开发体验达到最佳状态。
构建完整系统的步骤:
设置 Monorepo:
- 步骤:在项目根目录下初始化一个 Monorepo,创建
packages
目录来存放各个子项目。1
2
3mkdir my-monorepo
cd my-monorepo
mkdir packages
- 步骤:在项目根目录下初始化一个 Monorepo,创建
配置 pnpm 工作区:
- 步骤:在 Monorepo 根目录下创建
pnpm-workspace.yaml
,定义工作区路径。1
2packages:
- 'packages/*' - 解释:这个配置将
packages
目录中的所有项目纳入到工作区中,方便统一管理。
- 步骤:在 Monorepo 根目录下创建
安装 pnpm 和 Turborepo:
- 步骤:安装 pnpm 和 Turborepo 作为开发依赖。
1
pnpm add -D pnpm turbo
- 步骤:安装 pnpm 和 Turborepo 作为开发依赖。
管理依赖:
- 步骤:在各个子项目的
package.json
中使用workspace:*
或直接引用根目录的依赖版本,实现统一管理。1
2
3
4
5
6
7{
"name": "project-a",
"version": "1.0.0",
"dependencies": {
"shared-library": "workspace:*"
}
}
- 步骤:在各个子项目的
配置 Turborepo 管道:
- 步骤:在 Monorepo 根目录下创建
turbo.json
,定义任务管道,如构建、测试、发布等。1
2
3
4
5
6
7
8
9
10
11
12
13{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
}
}
}
- 步骤:在 Monorepo 根目录下创建
执行构建与测试:
- 步骤:使用 Turborepo 的命令来运行构建和测试任务,确保所有项目按依赖顺序执行任务。
1
pnpm turbo run build
- 步骤:使用 Turborepo 的命令来运行构建和测试任务,确保所有项目按依赖顺序执行任务。
优化与维护:
- 步骤:使用 Turborepo 的缓存和增量构建特性,优化构建速度;使用 pnpm 的工作区和符号链接机制,优化依赖管理。定期更新依赖并使用
pnpm update
确保所有项目使用最新版本的包。
示例:
1
pnpm update --recursive
这个命令会递归更新 Monorepo 中所有项目的依赖到最新版本。
- 步骤:使用 Turborepo 的缓存和增量构建特性,优化构建速度;使用 pnpm 的工作区和符号链接机制,优化依赖管理。定期更新依赖并使用
总结:
- Monorepo 是一种将多个项目存放在同一个仓库中的策略,具有共享代码、简化依赖管理和提升协作效率的优点。
workspace:*
是 Monorepo 中用来引用本地包的依赖声明,确保所有项目使用最新的代码版本。- Turborepo 是一个为 Monorepo 优化的构建工具,支持增量构建、缓存、任务管道和并行执行,极大提升了构建效率。
- pnpm 是一个高效的包管理工具,特别适合在 Monorepo 中使用,通过符号链接机制节省磁盘空间并加快依赖安装速度。
- 通过将 Monorepo、Turborepo 和 pnpm 结合使用,可以构建出一个高效、可扩展且易于维护的开发系统,适合管理复杂的大型项目。
install_url
to use ShareThis. Please set it in _config.yml
.