Monorepo是一种项目代码管理方法,指在单个代码仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署的复杂性,并提供更好的可重用性和协作性。
简单理解:所有项目都在一个代码仓库中 📦,但这并不意味着所有代码都组织在一个文件夹中 🗂️。 事实上,一个好的Monorepo与单体代码库恰恰相反;它应该结构良好且模块化。
发展历程
单体时期
单一代码仓库:传统的单体应用程序通常将所有功能和模块打包在一起,形成单一的代码库和部署单元。这个单一代码库包含应用程序的所有部分,从前端界面到后端逻辑,甚至包括数据库架构和配置文件。
问题:
难以实现局部更新和独立扩展的灵活性 🛠️
高度耦合,代码臃肿 🧩
MultiRepo时代
多个代码仓库:不同的功能模块、组件或服务存储在独立的仓库中,可以独立进行版本控制、构建、部署和发布,使不同的团队或开发者能够独立开发、测试和维护自己的模块,更容易实现并行开发和团队协作。
问题:
跨仓库开发:多仓库维护成本高
开发调试:npm包(修改->发布->安装成本高),调试麻烦 🐛
版本管理:依赖版本同步和升级管理麻烦
项目基建:脚手架升级难以保证新老项目规范统一 🏗️
MonoRepo时代
随着业务复杂度增加,模块仓库越来越多。虽然MultiRepo在业务上解耦,但增加了项目工程管理的难度。当模块仓库达到一定程度时,会出现以下几个问题:
跨仓库代码难以共享
单一仓库中模块依赖管理分散复杂
构建时间增加
因此,将多个项目整合到一个仓库中,共享项目配置,快速共享模块代码,已成为一种趋势。
Monorepo的优势
代码复用:因为多个项目共享一个代码库,避免了在不同项目中重复编写相同功能代码的问题,提高开发效率。
提高协作效率:多个项目在同一个代码库中开发,可以方便地共享代码和文档,避免了不同项目之间的沟通和协调成本。
集中管理:在Monorepo架构中,不同的应用程序都在同一个代码库中,便于管理和监控。这一点很重要,特别是在需要同时修改和维护多个版本时。
统一构建:Monorepo的一个重要特征是可以共享一套构建系统和工具链进行构建和部署,提高了构建效率。
问题可以快速定位:由于所有代码都在同一个代码库中开发,调试器可以快速找到问题所在的代码文件和行号,方便开发人员调试问题。
一个版本:不用担心你的项目依赖冲突版本的第三方库而导致不兼容。
Monorepo的陷阱
幻影依赖
当npm/yarn安装依赖时,存在依赖提升。一个项目使用的依赖即使没有在其package.json中声明也可以直接使用。 这种现象称为"幻影依赖"。随着项目迭代,这个依赖不再被其他项目使用而不再安装。使用幻影依赖的项目会因为找不到依赖而报错 😤。 基于npm/yarn的Monorepo方案仍然存在"幻影依赖"问题。我们可以通过pnpm完全解决这个问题。
依赖安装耗时长
MonoRepo中的每个项目都有自己的package.json依赖列表。随着MonoRepo总依赖数量增长,每次install
都会耗费更长时间 😭。 相同版本的依赖会提升到Monorepo根目录,以减少重复依赖安装。所以使用pnpm进行按需安装和依赖缓存。
Pnpm包管理
为什么选择pnpm?
Monorepo
单仓库模块划分的需求要求仓库中的模块不仅要处理与外部模块的关系,还要处理内部依赖。因此,我们需要选择一个强大的包管理工具来帮助处理这些任务。 2022年后,我们推荐使用pnpm来管理项目依赖。它pnpm
涵盖了大部分的能力,并在多个维度上大大提升了体验 💯。
Monorepo环境搭建
通过以上内容,我们了解了Monorepo的优势以及选择pnpm的原因。 那么如何搭建Monorepo呢? 接下来,让我们通过Element Plus来学习如何搭建Monorepo环境 🤝
首先全局安装pnpm
npm install pnpm -g
然后使用pnpm init在项目中初始化package.json。这与npm init相同。
pnpm init
获取package.json的初始内容,然后删除package.json中的name属性并添加"private": true
属性,因为它不需要发布。
{
"private": true,
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
配置pnpm的monorepo工作空间
在我们的仓库中,我们需要管理多个项目,所以我们可以使用pnpm的monorepo。我们在仓库根目录创建一个pnpm-workspace.yaml
文件,我们可以在pnpm-workspace.yaml
配置文件中指定仓库中有多少个项目。
packages:
- play # 存放组件测试代码
- docs # 存放组件文档
- packages/* # packages目录下的所有包都是组件包
在packages目录中,我们可以放置很多package项目目录,如组件包目录:
components
主题包目录:theme-chalk
工具包目录:utils等
然后每个包目录也需要一个package.json
文件来声明这是一个NPM包目录。所以我们需要进入每个包目录初始化一个package.json
文件。
以components包为例,我们进入components目录,初始化一个package.json
文件,并更改包名:@elemnet-plus/components
。文件内容如下:
{
"name": "@elemnet-plus/components",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
其他两个包分别命名为@elemnet-plus/theme-chalk
和@elemnet-plus/utils
,创建过程与上述相同。
到目前为止,我们初步的项目目录结构如下:
├── README.md
├── package.json
├── packages
│ ├── components
│ │ └── package.json
│ ├── theme-chalk
│ │ └── package.json
│ └── utils
│ └── package.json
├── play
└── pnpm-workspace.yaml
仓库项目包之间相互调用
如果这些包要相互调用,需要将@elemnet-plus/components
、@elemnet-plus/theme-chalk
、@elemnet-plus/utils
安装到仓库根目录下的node_modules
目录中。
然后我们在根目录中安装:
pnpm install @elemnet-plus/components -w
pnpm install @elemnet-plus/theme-chalk -w
pnpm install @elemnet-plus/utils -w
-w
表示安装到公共模块的packages.json
中,即根目录的packages.json
中。
安装后根目录的package.json
内容为:
{
"dependencies": {
"@elemnet-plus/components": "workspace:*",
"@elemnet-plus/theme-chalk": "workspace:*",
"@elemnet-plus/utils": "workspace:*"
},
}
注意:workspace:*
这个在将来发布时会被转换成具体的版本号。
总结
至此,一个通过pnpm配置的monorepo基础环境已经搭建完成。
什么才是真正的工程化。在配置这个开发环境的过程中,我们似乎只是用一堆工具进行各种配置,那是不是意味着前端工程化就是工具化呢?
实际上,它不仅仅是工具。工程化注重使用工具作为手段来规范工作流程——表达思想、规范项目、有效管理编写代码的团队。
最后:
React Hook 深入浅出
CSS技巧与案例详解
vue2与vue3技巧合集
VueUse源码解读