一、什么是Monorepo
Monorepo是指在一个代码仓库中管理多个项目,每个项目可以拆分成不同的模块,统一的依赖管理,共享代码和配置等。相比于多个独立的仓库,在monorepo中可以更容易地实现代码复用和协同开发。
在实际应用中,常见的使用Monorepo的场景有:
- 多个相关的项目有共享代码的需求,例如同一公司或同一部门的多个项目。
- 前端应用拆分成多个模块或组件,共享依赖。
- 微服务架构中,多个服务共享公共的代码和配置。
而在monorepo中,一个典型的项目结构通常如下:
root/
├── package.json
├── packages/
│ ├── app/
│ ├── lib/
│ └── common/
└── scripts/
其中,根目录下的package.json
包含所有项目的相关信息和依赖,packages/
目录下存放不同的项目或模块,scripts/
目录下存放一些脚本和配置文件。
二、Monorepo的优缺点
1. 优点
(1)统一的依赖管理
在Monorepo中,所有的项目和模块共享同一个package.json
,可以大大简化依赖管理。例如,对于共同依赖的库,只需要在package.json
中进行一次安装即可,而不需要每个项目单独安装。
(2)共享代码
在Monorepo中,多个项目之间可以共享代码和模块,避免重复编写和维护代码,提高了代码的复用性和可维护性。此外,共享的代码也可以更容易地进行版本控制和更新。
(3)容易进行协同开发
由于所有的代码都在一个仓库中,可以更容易地进行代码协同开发,例如可以使用git子模块、git subtree等技术来让开发者只克隆需要关注的部分,方便团队协同开发。
2. 缺点
(1)构建速度慢
由于Monorepo中会有多个项目和模块的代码,每次构建都需要对每个模块进行编译和构建,如果项目非常庞大,构建速度会明显变慢。
(2)发布部署复杂
在Monorepo中,不同项目和模块之间存在依赖关系,如果需要对某个模块进行修改或升级,需要考虑依赖关系对其他模块的影响,因此发布和部署可能会更加复杂。
三、使用Monorepo管理项目
在本节中,我们将通过一个简单的示例来演示如何使用Monorepo管理项目。
1. 初始化项目
首先,我们需要在本地创建一个新的文件夹,例如monorepo-test
,并执行以下命令初始化项目:
mkdir monorepo-test
cd monorepo-test
npm init
在创建package.json
时,可以将Monorepo相关的配置设置成项目的初始化信息:
{
"name": "monorepo-test",
"private": true,
"workspaces": [
"packages/*"
]
}
private: true
表示这是一个私有库,workspaces: ["packages/*"]
表示我们的工作区设置在packages/
目录下。
2. 创建项目
现在我们来创建一个新的项目,可以使用lerna
命令来快捷地创建:
npx lerna create app --dependencies=react
你会看到一个新的packages/app
目录创建出来。
现在,我们需要在app
目录下添加一个React组件,例如:
// packages/app/src/Hello.js
import React from 'react';
export default function Hello({ name }) {
return Hello, {name}!
;
}
然后,我们需要在packages/app
目录下创建package.json
,并在其中添加依赖和构建指令:
npm init -y
npm install --save react
// packages/app/package.json
{
"name": "app",
"version": "0.0.1",
"scripts": {
"build": "npm run build:lib",
"build:lib": "rollup -c",
"start": "react-scripts start"
},
"devDependencies": {
"@babel/core": "^7.12.3",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/preset-env": "^7.12.1",
"@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-node-resolve": "^10.0.0",
"rollup": "^2.32.1",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-terser": "^7.0.2"
},
"dependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
}
在这里,我们使用了rollup
进行打包和构建,同时添加了一些常见的依赖和插件。
3. 创建依赖模块
现在,我们需要创建一个独立的依赖模块,用于在不同的项目中共享代码和逻辑。
我们可以使用lerna
来快速创建一个空的模块:
npx lerna create common
然后,在packages/common
目录下,创建一个空的JavaScript文件index.js
,它将作为我们的共享代码的入口点。
// packages/common/index.js
由于common模块是一个纯JavaScript模块,因此我们只需要在common/package.json
中添加必要的依赖和构建脚本即可:
// packages/common/package.json
{
"name": "common",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"build": "npm run build:lib",
"build:lib": "babel src --out-dir lib",
"test": "echo \"No tests yet...\""
},
"devDependencies": {
"@babel/core": "^7.12.3",
"@babel/preset-env": "^7.12.1",
"babel-jest": "^26.6.3",
"jest": "^26.6.3"
},
"dependencies": {}
}
在这里,我们使用了babel
来进行编译和构建,同时添加了一些常见的依赖和插件。
4. 在app中使用common模块
现在,我们可以在app模块中引入common模块,并使用common模块中的函数。
首先,我们需要在app/package.json
中添加对common模块的依赖:
"dependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1",
"common": "^0.0.1"
}
然后,在app
目录下的任意JavaScript文件中,可以轻松地引入和使用common模块中的函数:
// packages/app/src/Hello.js
import React from 'react';
import common from 'common';
export default function Hello({ name }) {
return {common.sayHello(name)}
;
}
在这里,我们使用common.sayHello
来获取在common模块中定义的函数,并将结果渲染到页面中。
5. 构建和测试
现在,我们可以使用lerna
来进行对我们的项目的构建和测试了:
npx lerna run build
这将会使用rollup
和babel
来构建我们的app和common模块。
npx lerna run test
这将会使用jest
来运行我们的测试用例。
四、总结
Monorepo虽然在一些场景下使用起来会有些复杂,但是其优点同样非常明显,特别是在多个项目和模块之间有共享代码和统一依赖管理的需求时,使用Monorepo可以显著提高开发效率和可维护性。
在实际应用中,使用Monorepo需要清晰的项目结构和依赖关系管理,动态调整不同项目和模块的关系需要仔细考虑,同时需要合理地使用各种工具和技术实现代码隔离和部署。